Senior 3 min · March 05, 2026

ES Modules — Barrel Files Kill Tree-Shaking

An update added 35KB to your bundle? A barrel file with a default export likely blocked tree-shaking.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • ESM uses export to expose and import to consume — no more global pollution
  • Named exports (export const) force explicit naming at import site, improving refactoring
  • Default exports (export default) are for the file's primary responsibility, rename without as
  • Dynamic import() returns a Promise — lazy load modules on user interaction
  • In Node.js, enable ESM with "type": "module" in package.json; browsers use
    Module System Comparison
    PropertyESMCommonJSAMDUMD
    IntroducedES2015 (2015)2009 (Node.js)2011 (RequireJS)2014 (Universal)
    Static/DynamicStaticDynamicDynamicDynamic
    Syntaximport / exportrequire / module.exportsdefine([...], function)wrapping pattern
    Tree-shakableYesNoNoNo
    Top-level awaitYesNoNoNo
    Browser nativeYes (modern)No (needs bundler)Yes (RequireJS)Yes (AMD or CJS fallback)
    Node.js nativeYes (v12+)Yes (default)NoNo

    Key takeaways

    1
    Named exports (export const X) require curly braces and support Tree Shaking better than default exports.
    2
    Default exports (export default X) are best for primary classes or components; they don't require braces.
    3
    Dynamic import() is a function that returns a Promise—ideal for code splitting and reducing initial bundle size.
    4
    ES Modules are static
    imports must be at the top level and are hoisted. CommonJS require() is dynamic and can be called inside loops.
    5
    In ESM, global variables like __dirname are replaced by import.meta.url patterns.
    6
    Set `"sideEffects"
    false` in package.json to enable tree shaking for your library.
    7
    Avoid barrel files
    they block tree shaking and introduce fragile re-export chains.

    Common mistakes to avoid

    4 patterns
    ×

    Using `import` without adding `type="module"` to the script tag

    Symptom
    Browser throws SyntaxError: Cannot use import statement outside a module. App fails to load with no clear stack trace in production because minifiers may swallow the error.
    Fix
    Add type="module" to the <script> tag. If using a bundler, ensure it outputs in a format that doesn't require the attribute (bundled output usually runs as normal scripts).
    ×

    Forgetting the file extension in imports when targeting browsers

    Symptom
    Browser returns a 404 for the import path because the server can't resolve extensionless modules. Node.js may also fail if resolve.extensions doesn't include .js.
    Fix
    Always include the full file extension in browser imports: import { helper } from './helpers.js'. In bundlers, configure resolve.extensions appropriately. For Node.js ESM, extensions are required unless using experimental-specifier-resolution=node.
    ×

    Mixing default and named exports in a barrel file

    Symptom
    Tree-shaking fails, bundle grows. Developers are confused about which syntax to use when importing. Some tools (like ESLint import/export rules) may report false positives.
    Fix
    Avoid barrel files entirely. If you must use them, export only named exports and avoid default exports in the barrel. Alternatively, export a single default object containing all named exports.
    ×

    Trying to use `require()` inside an ES Module file

    Symptom
    Node.js throws ReferenceError: require is not defined because ESM does not have require in the global scope. Some older patterns like const fs = require('fs') stop working.
    Fix
    Replace require() with import. If you need dynamic behaviour, use await import() instead. For interop, consider using the createRequire function from module module to create a local require function.
    INTERVIEW PREP · PRACTICE MODE

    Interview Questions on This Topic

    Q01SENIOR
    Explain 'Tree Shaking' and why ES Modules make it possible while CommonJ...
    Q02SENIOR
    What is the 'Static Analysis' benefit of ESM over CJS?
    Q03SENIOR
    How do you simulate `__dirname` in a Node.js ES Module environment?
    Q04SENIOR
    What happens if two modules have a circular dependency in ESM vs. Common...
    Q05SENIOR
    Implement a dynamic import loader that retries the network request 3 tim...
    Q06SENIOR
    Compare 'Default' and 'Named' exports in terms of refactorability in a l...
    Q01 of 06SENIOR

    Explain 'Tree Shaking' and why ES Modules make it possible while CommonJS makes it difficult.

    ANSWER
    Tree shaking is dead code elimination that removes unused exports from the final bundle. ES Modules enable it because their import/export statements are static — they appear at the top level and are never conditional. This allows bundlers to statically analyse which exports are actually imported. CommonJS requires() can be dynamic (called inside if-blocks, loops, or functions), so the bundler cannot safely determine which exports are used without executing the code. Therefore, CommonJS modules are included in their entirety.
    FAQ · 6 QUESTIONS

    Frequently Asked Questions

    01
    When should I use a default export vs a named export?
    02
    Can I import a CommonJS module in an ES Module file?
    03
    Why do I get 'Uncaught SyntaxError: Cannot use import statement outside a module'?
    04
    Does 'import' copy the value or create a reference?
    05
    How do I use dynamic import in Node.js without bundlers?
    06
    What is a barrel file and why is it problematic?
    🔥

    That's Advanced JS. Mark it forged?

    3 min read · try the examples if you haven't

Previous
Arrow Functions in JavaScript
10 / 27 · Advanced JS
Next
Symbol and BigInt in JavaScript