LEARN · DEBUGGING GUIDE

Debugging Rollup Named Export Not Found Errors

When Rollup says a named export isn't found, it's usually a mismatch between what you import and what the module actually exports. This guide cuts through the confusion with concrete diagnostics and fixes.

IntermediateBuild tools8 min read

What this usually means

Rollup is stricter than other bundlers about static analysis. It only respects `export` statements that it can statically determine at parse time. The most common cause is a mismatch between the import specifier and the actual export name—often due to typos, case sensitivity, or using a default export when a named export is expected. Another frequent cause is the interaction with tree-shaking: Rollup may remove an export if it determines it's unused or if the export is conditionally defined (e.g., inside an if block). Barrel files (index.js that re-exports) can also cause issues if the re-exported binding is not directly resolvable. Additionally, some plugins (like `@rollup/plugin-commonjs`) may not correctly map CommonJS exports to named ES module exports, leading to 'not found' errors.

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Run `rollup -c` with `--bundleConfigAsCjs` and inspect the full error stack trace to see which file and line triggers the error.
  • 2Use `rollup --debug` or set `process.env.ROLLUP_WATCH` to see more verbose output about module resolution.
  • 3Check the exact import statement against the exported names in the target module. Pay attention to case: 'MyExport' vs 'myexport'.
  • 4Add a temporary default export to the module and see if the error resolves (confirms tree-shaking issue).
  • 5Disable all Rollup plugins and retry the build to isolate plugin interference.
  • 6Inspect the module's AST using `rollup-plugin-visualizer` or manually with `acorn` to see what Rollup sees.
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchThe exact file and line number in the error message: Rollup always reports the importer and the missing export name.
  • searchThe module's export statements: look for `export const` or `export function` vs `export default`.
  • searchBarrel files (index.js/ts) that re-export: check that the re-exported name matches exactly.
  • searchRollup configuration file: especially `output.format`, `external`, and `plugins` order.
  • searchPackage.json `exports` field: if set, only exports listed there are available.
  • searchPlugin transformations: e.g., `@rollup/plugin-typescript` may strip exports if isolatedModules is true.
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningTypo or case mismatch in the import or export name.
  • warningUsing `export default` when the consumer expects a named export (and vice versa).
  • warningConditional or dynamic exports inside loops or if statements that Rollup cannot statically analyze.
  • warningRe-exporting through a barrel file that re-exports the name incorrectly (e.g., `export { default as X }` vs `export { X }`).
  • warningCommonJS module not properly converted by `@rollup/plugin-commonjs`; named exports from CJS are often treated as `default`.
  • warningTree-shaking removing the export because Rollup thinks it's unused (often due to side-effect-free annotations).
  • warningTypeScript `isolatedModules` or `verbatimModuleSyntax` causing exports to be omitted in compiled output.
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildCorrect the export name in the import statement to match exactly what the module exports.
  • buildIf using a barrel file, explicitly re-export the named export: `export { MyExport } from './source'` instead of relying on default.
  • buildAdd `/*#__PURE__*/` comment or side effects flag in package.json to prevent tree-shaking from removing the export.
  • buildFor CommonJS modules, use `@rollup/plugin-commonjs` with `namedExports` option to manually map CJS exports to named ES exports.
  • buildIf the export is conditionally defined, refactor to always define it (e.g., move `export` to top level).
  • buildDisable tree-shaking for specific modules via `treeshake.moduleSideEffects` config.
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedRe-run the Rollup build and confirm the error no longer appears.
  • verifiedCheck the output bundle for the presence of the named export (e.g., grep for the export name in the dist file).
  • verifiedWrite a simple test script that imports the named export from the bundle and uses it.
  • verifiedUse `rollup-plugin-analyzer` to visualize the bundle and see if the export is included.
  • verifiedRun TypeScript compiler (`tsc --noEmit`) to ensure type definitions match.
  • verifiedTest the module in isolation: create a minimal Rollup config that only bundles that module.
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningBlindly adding `namedExports` to `@rollup/plugin-commonjs` without verifying the CJS module's actual exports.
  • warningUsing `export * from` in barrel files without understanding that it re-exports all named exports, which can cause collisions.
  • warningIgnoring the error and trying to suppress it with `external` or `treeshake: false` without understanding the root cause.
  • warningAssuming the error is a Rollup bug without checking the module's actual exports via `node -e "console.log(require('./module'))"`.
  • warningOverusing `output.exports: 'named'` when the module only has a default export—this changes behavior globally.
  • warningApplying fixes based on the first search result without confirming the specific context (e.g., CJS vs ESM vs TypeScript).
( 07 )War story

The Case of the Missing Named Export in a Monorepo

Frontend Infrastructure EngineerRollup 3.20, TypeScript 5.0, React 18, pnpm workspaces

Timeline

  1. 14:00Merge a PR that adds a new shared utility function `formatDate` in a shared package.
  2. 14:15CI build fails with error: 'Module "@shared/utils" does not export "formatDate" (imported by "app/main.ts")'.
  3. 14:20Check the shared package's source: `export { formatDate } from './date'` in index.ts exists.
  4. 14:30Check the dist of the shared package (it's built separately with tsc): `formatDate` is present in the .d.ts and .js files.
  5. 14:45Run `rollup -c` with `--bundleConfigAsCjs` and see the error points to `app/main.ts` importing from `@shared/utils`.
  6. 15:00Temporarily disable tree-shaking in Rollup config: error still occurs.
  7. 15:10Inspect the shared package's `package.json`: there's an `exports` field that only lists `./utils` as a subpath, but the import path is `@shared/utils` (no subpath).
  8. 15:15Fix: update the import path to `@shared/utils/date` or adjust the `exports` field to include a root export.
  9. 15:20Build passes.

I had just merged a PR that added a new utility function `formatDate` to our shared utilities package. The CI build immediately failed with Rollup's 'does not export' error. I checked the shared package's source and dist—everything looked fine. The export was there.

After some debugging, I noticed the error pointed to `app/main.ts` importing from `@shared/utils`, but the shared package's `package.json` had an `exports` field that only exposed a subpath `./utils`. The import path `@shared/utils` didn't match any export condition, so Rollup treated it as if the module didn't exist at all, hence 'export not found'.

The fix was to either change the import to `@shared/utils/date` (the actual file) or update the `exports` map to include a root entry like `".": "./dist/index.js"`. We opted for the latter to keep imports clean. The lesson: always verify the `exports` field in package.json when dealing with monorepos and subpath exports.

Root cause

The shared package's `package.json` `exports` field did not include a root entry, causing Rollup to fail to resolve the module correctly when importing from the bare specifier `@shared/utils`.

The fix

Updated the `exports` field in the shared package's `package.json` to include a root export: `".": "./dist/index.js"` alongside existing subpath exports.

The lesson

Always check the `exports` field in package.json when debugging module resolution issues in modern bundlers. The `exports` field can restrict which paths are available for import, and Rollup respects it strictly.

( 08 )How Rollup Resolves Exports vs Other Bundlers

Rollup uses static analysis to build a graph of module imports and exports. It parses each module's AST to find `export` statements and maps them to import specifiers. Unlike Webpack, Rollup does not rely on runtime resolution; it requires that every import matches a statically determinable export. This means that any dynamic or conditional export (e.g., `if (condition) { export const x = 1; }`) will not be recognized. Similarly, Rollup does not support `export * from` if there are naming conflicts—it will issue a warning and skip the conflicting export.

Another key difference is how Rollup handles CommonJS modules via `@rollup/plugin-commonjs`. This plugin converts CJS `module.exports` to a default export, and named exports are only available if the plugin can statically analyze them. For example, `module.exports.foo = ...` is detected as a named export, but `Object.assign(module.exports, { foo })` is not. In such cases, you may need to use the `namedExports` option to manually map property names to named exports.

( 09 )Tree-Shaking and Side Effects: The Hidden Export Killer

Rollup's tree-shaking algorithm removes exports it considers unused. However, it can incorrectly determine that an export is unused if the module has no side effects and the export is not directly referenced. This is especially common when re-exporting from a barrel file. For example, `index.js` that does `export { a } from './a'; export { b } from './b';`—if tree-shaking determines that `a` is never used, it will remove the entire `./a` module, even if `a` is re-exported. To prevent this, you can mark the barrel file as having side effects in package.json: `"sideEffects": ["**/index.js"]`.

Another scenario is when an export is defined but never imported in the application code, but the library author intended it as a public API. In that case, you need to ensure the export is 'used' by something, or configure Rollup's `output.preserveModules` to keep all exports. Alternatively, you can add a `/*#__PURE__*/` comment to hint that the export is pure and can be removed, but this is the opposite of what you want.

( 10 )Barrel Files and Re-exports: Common Pitfalls

Barrel files (index.js/ts) that re-export from other modules are a common source of 'named export not found' errors. The issue arises when the re-exported name doesn't match exactly or when Rollup cannot trace the re-export to its source due to circular dependencies or deep nesting. For example, `export { default as X } from './module'` creates a named export `X` that is the default export of `./module`. If you try to import `{ X }` but the module actually has a named export `X` (not the default), you'll get the error.

Another issue is when using `export * from './module'` and the module has both a named export and a default export. `export *` only re-exports named exports, not the default. So if you later do `export { default } from './module'`, you might accidentally export the default as a named export called `default`, which is not the same as the original default. To avoid confusion, always explicitly list the exports you want to re-export.

( 11 )TypeScript Integration and isolatedModules

When using `@rollup/plugin-typescript` or `@rollup/plugin-sucrase`, the TypeScript compilation may strip certain exports due to compiler options. For instance, `isolatedModules: true` (common in Babel-based setups) can cause issues with `const enum` exports or re-exports of types. If you have `export type { MyType } from './types'`, Rollup might not see this as a runtime export and thus ignore it. To fix, ensure that type-only exports are accompanied by runtime exports or use `rollup-plugin-dts` for type bundling.

Another pitfall is the use of `verbatimModuleSyntax` in TypeScript 5.0+. This option requires that all imports/exports used only as types be explicitly marked with the `type` keyword. If you import a value but only use it as a type, Rollup might still require the runtime export. The solution is to use `import type { ... }` for type-only imports, and ensure that any value used at runtime is exported as a value.

Frequently asked questions

Why does Rollup say 'Module does not export X' when the export exists in the source file?

Most likely a mismatch in the export name (case sensitivity, typo) or a re-export that Rollup cannot statically trace. Check if the export is conditional or dynamic. Also verify the `exports` field in package.json, which can restrict which paths are available.

Does Rollup support `export * from` with CommonJS modules?

Yes, but only if the CommonJS module is properly converted by `@rollup/plugin-commonjs`. The plugin creates a synthetic namespace object with named exports based on static analysis. However, dynamic exports (e.g., `module.exports = { ... }` inside a function) are not supported.

How do I fix a 'named export not found' error when importing from a library?

First, check if the library actually exports the name you're importing. If it's a CommonJS library, you may need to configure `namedExports` in `@rollup/plugin-commonjs`. If it's an ESM library, ensure the library's `package.json` has proper `exports` mapping and that the export name is correct.

Can tree-shaking cause a 'named export not found' error?

Indirectly, yes. If Rollup's tree-shaking removes a module entirely because it thinks the export is unused, the import will fail. But typically, the error message will say 'does not export' rather than 'module not found'. To debug, disable tree-shaking temporarily and see if the error persists.

What is the difference between `export default` and `export { default }`?

`export default` creates a default export. `export { default }` creates a named export called `default`. They are different. If you import `{ default }` from a module that has only a default export, you'll get the error because there is no named export called `default`. Use `import DefaultExport from 'module'` instead.