DEV Community

Kamil Dzieniszewski
Kamil Dzieniszewski Subscriber

Posted on

When 'env' Isn't Just Environment Variables: My Journey Through JavaScript's Module Shorthand Rabbit Hole

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 */ }]]
Enter fullscreen mode Exit fullscreen mode

Confused developer meme
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_modules for 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:

  1. Try exact name first: classic
  2. Try official scoped package: @toolname/type-classic
  3. 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
  ]
}
Enter fullscreen mode Exit fullscreen mode

Drake meme
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
  ]
}
Enter fullscreen mode Exit fullscreen mode

This is fine meme
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
  ]
}
Enter fullscreen mode Exit fullscreen mode

Expanding brain meme
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)
  ]
}
Enter fullscreen mode Exit fullscreen mode

Rollup - Scoped Packages

// rollup.config.js
{
  plugins: [
    "resolve",       // → @rollup/plugin-node-resolve
    "commonjs",      // → @rollup/plugin-commonjs
    "typescript"     // → @rollup/plugin-typescript
  ]
}
Enter fullscreen mode Exit fullscreen mode

Vite - Following the Pattern

// vite.config.js
{
  plugins: [
    "react",         // → @vitejs/plugin-react
    "legacy"         // → @vitejs/plugin-legacy
  ]
}
Enter fullscreen mode Exit fullscreen mode

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)

Philosophy Raptor meme
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)

  1. Consistency: Learn it once, recognize it everywhere
  2. Discoverability: "I bet there's an eslint-plugin-react"
  3. Maintainability: Shorter configs are easier to scan
  4. 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']]
Enter fullscreen mode Exit fullscreen mode

Iceberg meme
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'
Enter fullscreen mode Exit fullscreen mode

Confused Travolta meme
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}`);
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

How to Survive the Shorthand Apocalypse

For Tool Authors (Please Listen!)

Drake meme
Drake rejecting: Showing only shorthands in examples
Drake approving: Showing both shorthand AND full names

  1. Document both forms: Show 'classic' AND @docusaurus/preset-classic in examples
  2. Better error messages: Instead of "Cannot resolve 'classic'", show "Tried: classic, @docusaurus/preset-classic, docusaurus-preset-classic"
  3. Cross-reference docs: Link to your shorthand explanation from EVERY config example
  4. Consistent patterns: Don't reinvent the wheel - follow Babel/ESLint conventions

For Developers (My Hard-Learned Lessons)

Disaster Girl meme
Me after learning about shorthands: Everything is fine

  1. When confused, use full names: @docusaurus/preset-classic never lies
  2. Check your package.json: See what's actually installed vs what you're referencing
  3. Use IDE superpowers: Many editors show resolved names on hover
  4. Debug systematically: Replace shorthands with full names when things break
  5. Learn the patterns: Once you know Babel's rules, you can guess others

Debugging Shorthand Issues

Common Problems

  1. Package not installed: Shorthand resolves but package missing
  2. Wrong resolution: Resolves to unexpected package
  3. Typos: Small mistakes in shorthand names
  4. Version conflicts: Multiple packages match pattern

Debugging Steps

  1. Try the full name: Replace shorthand with full package name
  2. Check installation: Verify package exists in node_modules
  3. Check resolution: Use tool's debug mode if available
  4. 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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Better documentation: Always show both forms
  2. Improved tooling: Better IDE support for resolution
  3. Error message standards: Show resolution attempts
  4. Community education: More articles like this one!

The Final Revelation

Galaxy brain meme
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:

  1. Don't panic: It's probably a shorthand
  2. Check the tool's docs: Look for "module resolution" or "name normalization"
  3. Try the full name: When in doubt, be explicit
  4. 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!

References

Top comments (0)