Or: How I spent 3 hours debugging a single word in my config
My Docusaurus Confusion Story
Picture this: I'm upgrading the documentation site for Tolgee (a localization platform I work on), following the Docusaurus migration guide, and I see this line:
presets: [['classic', { /* options */ }]]

Me staring at 'classic' like it personally offended me
"What the hell is 'classic'?" I thought. "Is this some built-in JavaScript thing I missed? A global variable? Magic?"
I spent the next hour:
- 🔍 Searching my
node_modulesfor anything named "classic" - 📖 Reading Docusaurus docs trying to find where this comes from
- 🤔 Questioning my understanding of JavaScript imports
- 😤 Getting increasingly frustrated
Then I discovered the truth: 'classic' is actually @docusaurus/preset-classic in disguise. Mind = blown.
But here's the kicker - this "magic" isn't unique to Docusaurus. It's EVERYWHERE in the JavaScript ecosystem.
What Are Module Shorthands?
Module shorthands are a convenience feature that lets you use abbreviated names instead of full package names in configuration files. When a tool encounters a shorthand, it automatically resolves it to the full package name using predefined rules.
The Pattern
Most JavaScript tools follow a similar resolution strategy:
-
Try exact name first:
classic -
Try official scoped package:
@toolname/type-classic -
Try community convention:
toolname-type-classic
The first match wins.
The Plot Twist: It's Everywhere!
Once I understood what was happening with Docusaurus, I started seeing this pattern EVERYWHERE. Let me blow your mind with some examples:
Babel - The OG Shorthand Master
Remember that innocent "env" in your Babel config? Yeah, that's not what you think it is.
// babel.config.js
{
"presets": [
"env", // → @babel/preset-env (SURPRISE!)
"react", // → @babel/preset-react
"typescript" // → @babel/preset-typescript
],
"plugins": [
"transform-runtime", // → @babel/plugin-transform-runtime
"proposal-decorators" // → @babel/plugin-proposal-decorators
]
}

Drake pointing: Writing @babel/preset-env
Drake approving: Writing "env"
Babel has the most sophisticated name normalization system with detailed rules:
- Unscoped packages:
"mod"→"babel-plugin-mod" - Scoped packages:
"@babel/mod"→"@babel/plugin-mod" - Custom scopes:
"@scope/mod"→"@scope/babel-plugin-mod"
ESLint - The "Obvious" One (Once You Know)
ESLint actually has the clearest naming convention, but it still trips people up:
// .eslintrc.js
{
"extends": [
"airbnb", // → eslint-config-airbnb
"prettier", // → eslint-config-prettier
"react-app" // → eslint-config-react-app
],
"plugins": [
"react", // → eslint-plugin-react
"import", // → eslint-plugin-import
"jsx-a11y" // → eslint-plugin-jsx-a11y
]
}

Me realizing every config I've ever written is full of hidden magic
ESLint's plugin configuration rules are actually predictable:
- Configs:
"name"→"eslint-config-name" - Plugins:
"name"→"eslint-plugin-name" - Scoped:
"@scope/name"→"@scope/eslint-plugin-name"
Back to My Docusaurus Nightmare
Now that I know the pattern, let's revisit my original confusion:
// docusaurus.config.js - The scene of the crime
{
presets: [
['classic', { // → @docusaurus/preset-classic (THE REVEAL!)
docs: { /* */ },
blog: { /* */ }
}]
],
plugins: [
'sitemap', // → @docusaurus/plugin-sitemap
'content-pages' // → @docusaurus/plugin-content-pages
]
}

Brain 1: "classic" is magic
Brain 2: "classic" is a shorthand
Brain 3: All JS tools use shorthands
Brain 4: I've been using shorthands everywhere without knowing
Docusaurus has comprehensive documentation about this, but finding it when you're confused? Good luck!
PostCSS - Simple Resolution
// postcss.config.js
{
plugins: [
"autoprefixer", // → autoprefixer (exact match)
"cssnano", // → cssnano (exact match)
"tailwindcss" // → tailwindcss (exact match)
]
}
Rollup - Scoped Packages
// rollup.config.js
{
plugins: [
"resolve", // → @rollup/plugin-node-resolve
"commonjs", // → @rollup/plugin-commonjs
"typescript" // → @rollup/plugin-typescript
]
}
Vite - Following the Pattern
// vite.config.js
{
plugins: [
"react", // → @vitejs/plugin-react
"legacy" // → @vitejs/plugin-legacy
]
}
Why Does This Pattern Even Exist?
After my 3-hour debugging session, I had to ask: WHY do JavaScript tools do this to us?
Convention Over Configuration (The Philosophy)

If a shorthand resolves in a config file and no developer understands it, does it make a sound?
The JavaScript ecosystem LOVES "convention over configuration":
-
Less typing:
'env'vs'@babel/preset-env'(saved 15 characters!) - Cleaner configs: Your babel.config.js doesn't look like XML
- Predictable patterns: Once you know the rules, you can guess package names
-
Flexibility: Both
'env'and'@babel/preset-env'work
The Ecosystem Benefits (When It Works)
- Consistency: Learn it once, recognize it everywhere
- Discoverability: "I bet there's an eslint-plugin-react"
- Maintainability: Shorter configs are easier to scan
- Developer happiness: Less boilerplate = more time for actual coding
The Dark Side: Why It's Absolutely Confusing
Hidden Magic Everywhere
// What you see
presets: [['classic']]
// What actually happens (behind the scenes)
presets: [['@docusaurus/preset-classic']]

Tip of iceberg: Your config
Underwater: All the module resolution happening
There's literally NO visual indication that 'classic' becomes something else entirely.
Documentation Roulette
The docs are inconsistent:
- Tutorial shows:
'classic' - API docs show:
'@docusaurus/preset-classic' - Stack Overflow shows: Both, with no explanation
- Your brain: 🤯
Debugging Hell
When things break, you get errors like:
Cannot resolve module 'classic'

Me trying to figure out which of the 3 resolution attempts failed
The error doesn't tell you:
- Which resolution step failed
- What was actually attempted
- Whether the package is missing or the name is wrong
Every Tool Is Special
Each tool has its own special snowflake implementation:
- Babel: Most complex rules
- ESLint: Clear patterns but different prefixes
- Docusaurus: Scoped packages only
- PostCSS: Mostly exact matches
- Rollup: Different scope handling
Implementation Deep Dive
How Resolution Actually Works
Most tools implement something like this:
function resolveModule(name, moduleType) {
const attempts = [
name, // exact
`@${toolName}/${moduleType}-${name}`, // official
`${toolName}-${moduleType}-${name}` // community
];
for (const attempt of attempts) {
try {
return require.resolve(attempt);
} catch (e) {
continue;
}
}
throw new Error(`Cannot resolve ${name}`);
}
Scoped Package Handling
For scoped packages like @my-company/awesome:
function resolveScopedModule(name, moduleType) {
const [scope, packageName] = name.split('/');
if (!packageName) {
// @scope only → @scope/docusaurus-plugin
return `${scope}/${toolName}-${moduleType}`;
}
// @scope/name → try both forms
const attempts = [
name, // @scope/name
`${scope}/${toolName}-${moduleType}-${packageName}` // @scope/docusaurus-plugin-name
];
// ... resolution logic
}
How to Survive the Shorthand Apocalypse
For Tool Authors (Please Listen!)

Drake rejecting: Showing only shorthands in examples
Drake approving: Showing both shorthand AND full names
-
Document both forms: Show
'classic'AND@docusaurus/preset-classicin examples - Better error messages: Instead of "Cannot resolve 'classic'", show "Tried: classic, @docusaurus/preset-classic, docusaurus-preset-classic"
- Cross-reference docs: Link to your shorthand explanation from EVERY config example
- Consistent patterns: Don't reinvent the wheel - follow Babel/ESLint conventions
For Developers (My Hard-Learned Lessons)

Me after learning about shorthands: Everything is fine
-
When confused, use full names:
@docusaurus/preset-classicnever lies - Check your package.json: See what's actually installed vs what you're referencing
- Use IDE superpowers: Many editors show resolved names on hover
- Debug systematically: Replace shorthands with full names when things break
- Learn the patterns: Once you know Babel's rules, you can guess others
Debugging Shorthand Issues
Common Problems
- Package not installed: Shorthand resolves but package missing
- Wrong resolution: Resolves to unexpected package
- Typos: Small mistakes in shorthand names
- Version conflicts: Multiple packages match pattern
Debugging Steps
- Try the full name: Replace shorthand with full package name
-
Check installation: Verify package exists in
node_modules - Check resolution: Use tool's debug mode if available
- Verify naming: Double-check official package names
Debug Tools
# Check what's installed
npm list | grep preset
# Try resolving manually
node -e "console.log(require.resolve('@docusaurus/preset-classic'))"
# Enable debug mode (tool-specific)
DEBUG=docusaurus* npm start
The Future
Trends
- More tools adopting: Pattern spreading to newer tools
- Better error messages: Tools improving debugging experience
- IDE integration: Better autocomplete and resolution hints
- Standardization: Some movement toward common patterns
Recommendations
For the ecosystem to improve:
- Better documentation: Always show both forms
- Improved tooling: Better IDE support for resolution
- Error message standards: Show resolution attempts
- Community education: More articles like this one!
The Final Revelation

Small brain: Getting confused by 'classic'
Medium brain: Understanding it's a shorthand
Large brain: Recognizing the pattern everywhere
Galaxy brain: Writing an article to help other confused developers
After my 3-hour journey down the shorthand rabbit hole, I've learned that:
- It's not magic: Just very well-hidden conventions
- It's everywhere: Once you see it, you can't unsee it
- It's actually helpful: When you understand the rules
- It's poorly documented: The connection between examples and explanations is often missing
What This Means for You
Next time you see 'env', 'classic', 'airbnb', or any other mysterious string in a JavaScript config:
- Don't panic: It's probably a shorthand
- Check the tool's docs: Look for "module resolution" or "name normalization"
- Try the full name: When in doubt, be explicit
- Learn the pattern: Each tool follows similar rules
The JavaScript ecosystem loves its conventions, but sometimes forgets to explain them to newcomers. Now you're in on the secret!
Have you been confused by JavaScript module shorthands? Share your war stories in the comments - let's make the ecosystem more welcoming for everyone! 🚀
About the Author: I work on Tolgee, an open-source localization platform that makes i18n simple for developers. If you're building multilingual apps and tired of complex translation workflows, check us out!
Top comments (0)