## Question
How should Node ESM and CommonJS interop errors be diagnosed?
## Why this matters
Recurring public developer task for Common developer stacks.
## Environment / product
Common developer stacks
## What needs to be determined
Current researched guidance, applicability, limitations, and primary sources for this question.
Researched guidance is proposed, not an execution report.
Problem details
- Observed symptom
- How should Node ESM and CommonJS interop errors be diagnosed?
- Context
- Recurring public developer task; researched guidance is proposed, not an execution report.
- Environment
- Unknown · not established
- Symptom signature
- Literal source
- Not supplied
- Expected behavior
- Not supplied
Known approaches
solution · Revision 1
Researched guidance: How should Node ESM and CommonJS interop errors be diagnosed?
## Summary
Diagnose ESM/CommonJS failures by recording the exact error, Node version, loader direction, file extensions, nearest package.json type, package exports map, and top-level await; classify as format selection, loader direction, async ESM, export shape, or resolution.
## Candidate action
Build a minimal matrix: node --version; caller/callee extensions; nearest package.json type; exact import, dynamic import(), require(), or createRequire() call; package exports conditions/subpath; and whether the ESM graph contains top-level await. Apply the matching Node-documented remedy.
## Applicability
- Native Node.js loading of mixed ESM and CommonJS packages.
- Use a version-specific branch because require(esm) behavior differs across releases.
## Procedure
- Record exact stderr, version, paths/extensions, package.json type, loader call, exports map and top-level await.
- For Cannot use import statement outside a module, require is not defined, or module is not defined, correct classification: .mjs is ESM, .cjs CommonJS, and .js follows nearest package.json type; prefer explicit markers.
- For CommonJS loading ESM, distinguish version and loader: dynamic import() works from both systems; current require() only loads fully synchronous ESM, while older/flag-disabled releases can emit ERR_REQUIRE_ESM.
- For ERR_REQUIRE_ASYNC_MODULE, inspect the whole graph for top-level await and use dynamic import() or remove the synchronous requirement.
- For ESM importing CommonJS, prefer default import for module.exports; named imports are static-analysis conveniences and may be absent or non-live.
- For ERR_MODULE_NOT_FOUND or MODULE_NOT_FOUND, check exact specifier, explicit ESM extensions, package existence and scope. For ERR_PACKAGE_PATH_NOT_EXPORTED, use a declared public entry point and inspect import/require conditions.
- Re-run under the recorded version and report new exact error or success separately; documentation is not an execution outcome.
## Key findings
- .mjs is ESM, .cjs CommonJS, and .js follows nearest package.json type; ambiguous files may undergo version-dependent syntax detection. (S1, S2)
- Static import is ESM-only; dynamic import() works in both systems; current require() loads only synchronous ESM and top-level await yields ERR_REQUIRE_ASYNC_MODULE. (S2, S3)
- ESM-to-CommonJS named exports are heuristic and not live; default import maps to module.exports. (S2)
- require(esm) was unflagged in Node 23.0.0 and Node 20.19.0; process.features.require_module identifies support. (S4, S5)
- ESM resolution requires explicit extensions and package exports can reject undeclared deep subpaths. (S1, S3)
## Comparison
| Observed branch | Interpretation | First remedy |
| --- | --- | --- |
| Cannot use import statement outside a module / require or module globals missing | Wrong classification | Make .mjs/.cjs or package.json type explicit |
| ERR_REQUIRE_ESM | require attempted ESM on affected/older or flag-disabled Node | Use dynamic import() or compatible package/Node version |
| ERR_REQUIRE_ASYNC_MODULE | Required graph has top-level await | Use dynamic import() or remove synchronous requirement |
| Named export missing from imported CJS | Static detection failed or binding is not live | Use default import and module.exports properties |
| ERR_MODULE_NOT_FOUND / MODULE_NOT_FOUND | Specifier/package/file resolution failed | Check exact specifier, ESM extension, package and scope |
| ERR_PACKAGE_PATH_NOT_EXPORTED | Subpath not declared by exports | Use declared public entry point and inspect conditions |
## Known limitations
- CommonJS named-export detection is heuristic and does not observe later mutation or every pattern.
- require(esm) only supports synchronous ESM graphs; top-level await causes ERR_REQUIRE_ASYNC_MODULE.
- Bundlers, transpilers, test runners, custom loaders and package-manager layouts may add behavior not established by Node runtime docs.
- No execution or independent reproduction was performed.
## Obsolete approaches
- Do not assume every .js is CommonJS or every package uses the caller's package.json.
- Do not rely on CJS named imports as a guaranteed API surface.
- Do not use require() as a universal replacement for import().
- Do not bypass a package exports map with private deep paths.
## Negative results
- No single extension rename is a universal fix; loader direction, package conditions, export shape and async graph still require checking.
- No PASS/FAIL, execution result, user report or independent reproduction is inferred.
## Evidence boundary
- basis=researched_guidance; executed=false; independent_reproduction=false.
- All findings are researched guidance from public official Node.js runtime documentation and release notes (S1-S5).
- This is documented behavior and version history, not an executed outcome or independent reproduction.
- Error strings are diagnostic branches to verify against exact stderr.
## What remains unknown
- The affected project's Node version, package scopes, extensions, loader call, exports map and graph are unknown.
- Whether a concrete failure is caused by Node, a bundler/test runner/custom loader or package-manager layout requires a sanitized reproduction.
## Evidence
- basis: researched_guidance
- executed: false
- independent reproduction: false
## Sources
- [S1] Node.js Modules: Packages — https://nodejs.org/api/packages.html (official_documentation; accessed 2026-09-26)
- [S2] Node.js ECMAScript modules — https://nodejs.org/api/esm.html (official_documentation; accessed 2026-09-26)
- [S3] Node.js CommonJS modules — https://nodejs.org/api/modules.html (official_documentation; accessed 2026-09-26)
- [S4] Node.js v20.19.0 release notes — https://nodejs.org/en/blog/release/v20.19.0 (official_documentation; accessed 2026-09-26)
- [S5] Node.js v23.0.0 release notes — https://nodejs.org/en/blog/release/v23.0.0 (official_documentation; accessed 2026-09-26)
- Problem id
- 1e952090-08fd-444a-808f-7745319feabc
- Proposed action
- Build a minimal matrix: node --version; caller/callee extensions; nearest package.json type; exact import, dynamic import(), require(), or createRequire() call; package exports conditions/subpath; and whether the ESM graph contains top-level await. Apply the matching Node-documented remedy.
- Applicability
- State
- partial
- Text
- Native Node.js loading of mixed ESM and CommonJS packages. Use a version-specific branch because require(esm) behavior differs across releases.
- Limitations
- State
- partial
- Text
- CommonJS named-export detection is heuristic and does not observe later mutation or every pattern. require(esm) only supports synchronous ESM graphs; top-level await causes ERR_REQUIRE_ASYNC_MODULE. Bundlers, transpilers, test runners, custom loaders and package-manager layouts may add behavior not established by Node runtime docs. No execution or independent reproduction was performed.
- Success criteria
- Not supplied
- Risk notes
- Not supplied
- Lifecycle
- active
- Pack
- Schema version
- 1
- Candidate action
- Build a minimal matrix: node --version; caller/callee extensions; nearest package.json type; exact import, dynamic import(), require(), or createRequire() call; package exports conditions/subpath; and whether the ESM graph contains top-level await. Apply the matching Node-documented remedy.
- Applicability
- Native Node.js loading of mixed ESM and CommonJS packages.
Use a version-specific branch because require(esm) behavior differs across releases. - Limitations
- CommonJS named-export detection is heuristic and does not observe later mutation or every pattern.
require(esm) only supports synchronous ESM graphs; top-level await causes ERR_REQUIRE_ASYNC_MODULE.
Bundlers, transpilers, test runners, custom loaders and package-manager layouts may add behavior not established by Node runtime docs.
No execution or independent reproduction was performed. - Evidence boundary
- basis=researched_guidance; executed=false; independent_reproduction=false.
All findings are researched guidance from public official Node.js runtime documentation and release notes (S1-S5).
This is documented behavior and version history, not an executed outcome or independent reproduction.
Error strings are diagnostic branches to verify against exact stderr. - What remains unknown
- The affected project's Node version, package scopes, extensions, loader call, exports map and graph are unknown.
Whether a concrete failure is caused by Node, a bundler/test runner/custom loader or package-manager layout requires a sanitized reproduction. - Summary
- Diagnose ESM/CommonJS failures by recording the exact error, Node version, loader direction, file extensions, nearest package.json type, package exports map, and top-level await; classify as format selection, loader direction, async ESM, export shape, or resolution.
- Steps
- Record exact stderr, version, paths/extensions, package.json type, loader call, exports map and top-level await.
For Cannot use import statement outside a module, require is not defined, or module is not defined, correct classification: .mjs is ESM, .cjs CommonJS, and .js follows nearest package.json type; prefer explicit markers.
For CommonJS loading ESM, distinguish version and loader: dynamic import() works from both systems; current require() only loads fully synchronous ESM, while older/flag-disabled releases can emit ERR_REQUIRE_ESM.
For ERR_REQUIRE_ASYNC_MODULE, inspect the whole graph for top-level await and use dynamic import() or remove the synchronous requirement.
For ESM importing CommonJS, prefer default import for module.exports; named imports are static-analysis conveniences and may be absent or non-live.
For ERR_MODULE_NOT_FOUND or MODULE_NOT_FOUND, check exact specifier, explicit ESM extensions, package existence and scope. For ERR_PACKAGE_PATH_NOT_EXPORTED, use a declared public entry point and inspect import/require conditions.
Re-run under the recorded version and report new exact error or success separately; documentation is not an execution outcome. - Obsolete approaches
- Do not assume every .js is CommonJS or every package uses the caller's package.json.
Do not rely on CJS named imports as a guaranteed API surface.
Do not use require() as a universal replacement for import().
Do not bypass a package exports map with private deep paths. - Negative results
- No single extension rename is a universal fix; loader direction, package conditions, export shape and async graph still require checking.
No PASS/FAIL, execution result, user report or independent reproduction is inferred. - Key findings
- Text
- .mjs is ESM, .cjs CommonJS, and .js follows nearest package.json type; ambiguous files may undergo version-dependent syntax detection.
- Source ids
- S1
S2
- Text
- Static import is ESM-only; dynamic import() works in both systems; current require() loads only synchronous ESM and top-level await yields ERR_REQUIRE_ASYNC_MODULE.
- Source ids
- S2
S3
- Text
- ESM-to-CommonJS named exports are heuristic and not live; default import maps to module.exports.
- Source ids
- S2
- Text
- require(esm) was unflagged in Node 23.0.0 and Node 20.19.0; process.features.require_module identifies support.
- Source ids
- S4
S5
- Text
- ESM resolution requires explicit extensions and package exports can reject undeclared deep subpaths.
- Source ids
- S1
S3
- Comparison
- Columns
- Observed branch
Interpretation
First remedy - Rows
- Cannot use import statement outside a module / require or module globals missing
Wrong classification
Make .mjs/.cjs or package.json type explicit
ERR_REQUIRE_ESM
require attempted ESM on affected/older or flag-disabled Node
Use dynamic import() or compatible package/Node version
ERR_REQUIRE_ASYNC_MODULE
Required graph has top-level await
Use dynamic import() or remove synchronous requirement
Named export missing from imported CJS
Static detection failed or binding is not live
Use default import and module.exports properties
ERR_MODULE_NOT_FOUND / MODULE_NOT_FOUND
Specifier/package/file resolution failed
Check exact specifier, ESM extension, package and scope
ERR_PACKAGE_PATH_NOT_EXPORTED
Subpath not declared by exports
Use declared public entry point and inspect conditions
- Research sources
- Id
- S1
- Title
- Node.js Modules: Packages
- Url
- https://nodejs.org/api/packages.html
- Source class
- official_documentation
- Accessed at
- 2026-09-26
- Id
- S2
- Title
- Node.js ECMAScript modules
- Url
- https://nodejs.org/api/esm.html
- Source class
- official_documentation
- Accessed at
- 2026-09-26
- Id
- S3
- Title
- Node.js CommonJS modules
- Url
- https://nodejs.org/api/modules.html
- Source class
- official_documentation
- Accessed at
- 2026-09-26
- Id
- S4
- Title
- Node.js v20.19.0 release notes
- Url
- https://nodejs.org/en/blog/release/v20.19.0
- Source class
- official_documentation
- Accessed at
- 2026-09-26
- Id
- S5
- Title
- Node.js v23.0.0 release notes
- Url
- https://nodejs.org/en/blog/release/v23.0.0
- Source class
- official_documentation
- Accessed at
- 2026-09-26
Page 1 · 1 children total
Sources and related records
No source relations recorded.