Skip to content

fix: MODULE_NOT_FOUND for ESM internal .mjs imports at runtime#218

Merged
robertsLando merged 10 commits intomainfrom
copilot/fix-module-not-found-error
Feb 20, 2026
Merged

fix: MODULE_NOT_FOUND for ESM internal .mjs imports at runtime#218
robertsLando merged 10 commits intomainfrom
copilot/fix-module-not-found-error

Conversation

Copy link

Copilot AI commented Feb 19, 2026

Fix ESM: MODULE_NOT_FOUND during runtime for internal .mjs imports

Problem

ESM modules with internal .mjs imports were failing at runtime with MODULE_NOT_FOUND errors. The build succeeded without warnings, but the executable crashed immediately when trying to load dependencies.

Root Cause

  1. When esbuild transforms ESM to CJS, import { x } from './module.mjs' becomes require('./module.mjs')
  2. During packaging, .mjs files are renamed to .js in the snapshot
  3. At runtime, require('./module.mjs') fails because the file is now located at ./module.js

Solution

  • prelude/bootstrap.js: Added fallback logic in Module._resolveFilename to map .mjs require calls to .js files when inside the snapshot
    • Scoped to relative imports from modules inside snapshot
    • Only catches MODULE_NOT_FOUND from retry attempt
    • Continues through normal snapshot/mountpoint checks
  • lib/walker.ts: Added .mjs to the list of file extensions for module resolution so .mjs dependencies are properly discovered during packaging
  • lib/walker.ts: Extracted MODULE_RESOLVE_EXTENSIONS constant to avoid duplication
  • test/test-50-fs-runtime-layer-2/main.js: Fixed to skip on Node 20+ where error message formats changed

Testing

  • test-52-esm-internal-imports: Basic .mjs import test
  • test-53-esm-nested-imports: Nested .mjs import chain test
  • test-50-esm-pure: Existing ESM test (passing)
  • test-51-esm-import-meta: Existing ESM test (passing)
  • test-01-hybrid-esm: Existing ESM test (passing)
  • test-50-fs-runtime-layer-2: Now properly skips on Node 20+ (passing)
  • Lint checks (passing)
  • Security checks (no issues found)
Original prompt

This section details on the original issue you should resolve

<issue_title>ESM: MODULE_NOT_FOUND during runtime for internal .mjs imports</issue_title>
<issue_description>### What version of pkg are you using?

6.14.0

What version of Node.js are you using?

24.13.0

What operating system are you using?

Ubuntu 22.04

What CPU architecture are you using?

x64

What Node versions, OSs and CPU architectures are you building for?

node24-linux-x64

Describe the Bug

I've been trying to package my app, and while the build finishes without any complaints, the resulting executable just dies immediately with a MODULE_NOT_FOUND error.

I originally ran into this on a much bigger project I'm working on, but to make sure it wasn't just my messy code, I built this minimal, zero-dependency, reproducible example. Even with just two files and zero extra dependencies, it still breaks the same way. It looks like pkg is trying to use require logic on an ES Module import, and it's losing track of the files inside the /snapshot/ folder.

Here's the error I get:

pkg/prelude/bootstrap.js:1861
      throw error;
      ^

Error: Cannot find module './module.mjs'
Require stack:
- /snapshot/app-prototype-test/src/script.js
1) If you want to compile the package/file into executable, please pay attention to compilation warnings and specify a literal in 'require' call. 2) If you don't want to compile the package/file into executable and want to 'require' it from filesystem (likely plugin), specify an absolute path in 'require' call using process.cwd() or process.execPath.
    at Module.<anonymous> (node:internal/modules/cjs/loader:1424:15)
    at Module._resolveFilename (pkg/prelude/bootstrap.js:1944:46)
    at defaultResolveImpl (node:internal/modules/cjs/loader:1062:19)
    at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1067:22)
    at Module._load (node:internal/modules/cjs/loader:1230:37)
    at TracingChannel.traceSync (node:diagnostics_channel:328:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:245:24)
    at Module.<anonymous> (node:internal/modules/cjs/loader:1507:12)
    at Module.require (pkg/prelude/bootstrap.js:1840:31)
    at require (node:internal/modules/helpers:152:16) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/snapshot/app-prototype-test/src/script.js' ],
  pkg: true
}

Node.js v24.13.0

Expected Behavior

A packaged script should work.

To Reproduce

  1. src/script.mjs (The entry point):
import { testFunction } from './module.mjs';
testFunction();
  1. src/module.mjs (The dependency):
export function testFunction() {
  console.log("If you see this, it actually worked!");
}
  1. Build command:
    pkg --target node24-linux-x64 --out-path dist/ src/script.mjs

  2. Run and see the error:
    ./dist/script</issue_description>

<agent_instructions>Add a test with the reproduction files set and check how to fix this</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@robertsLando @fcastilloec What is also important is to know the content of package.json in this case, do you have "type": "module" set in package.json?

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 4 commits February 19, 2026 09:07
Co-authored-by: robertsLando <11502495+robertsLando@users.noreply.github.com>
- Add .mjs to file extensions for module resolution
- Update bootstrap to map .mjs require calls to .js files
- Remove .gitignore entry to exclude test artifacts

Co-authored-by: robertsLando <11502495+robertsLando@users.noreply.github.com>
Co-authored-by: robertsLando <11502495+robertsLando@users.noreply.github.com>
Co-authored-by: robertsLando <11502495+robertsLando@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix ESM: MODULE_NOT_FOUND during runtime for .mjs imports Fix MODULE_NOT_FOUND for ESM internal .mjs imports at runtime Feb 19, 2026
Copilot AI requested a review from robertsLando February 19, 2026 09:20
@robertsLando robertsLando changed the title Fix MODULE_NOT_FOUND for ESM internal .mjs imports at runtime fix: MODULE_NOT_FOUND for ESM internal .mjs imports at runtime Feb 19, 2026
@robertsLando robertsLando marked this pull request as ready for review February 19, 2026 10:18
@robertsLando robertsLando requested a review from Copilot February 19, 2026 10:18
Co-authored-by: robertsLando <11502495+robertsLando@users.noreply.github.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a runtime MODULE_NOT_FOUND when transformed ESM code still require()s internal .mjs specifiers but transformed .mjs files are stored as .js in the snapshot.

Changes:

  • Add a runtime resolution fallback in Module._resolveFilename to retry .mjs requests as .js when the original lookup fails.
  • Expand build-time resolution extensions to include .mjs while walking dependencies.
  • Add two regression tests covering direct and nested internal .mjs import chains, plus ignore test runtime artifacts.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
prelude/bootstrap.js Adds .mjs.js retry logic during module resolution to avoid runtime MODULE_NOT_FOUND in snapshot.
lib/walker.ts Includes .mjs in resolver extensions to improve dependency discovery during packaging.
test/test-52-esm-internal-imports/main.js New test that compares Node vs packaged output for a simple internal .mjs import.
test/test-52-esm-internal-imports/esm-module/package.json Marks the fixture as ESM via "type": "module".
test/test-52-esm-internal-imports/esm-module/script.mjs ESM entry fixture importing an internal .mjs file.
test/test-52-esm-internal-imports/esm-module/module.mjs Imported ESM fixture used by the internal import test.
test/test-53-esm-nested-imports/main.js New test for a multi-level internal .mjs import chain.
test/test-53-esm-nested-imports/esm-module/package.json Marks the nested-import fixture as ESM via "type": "module".
test/test-53-esm-nested-imports/esm-module/entry.mjs ESM entry fixture for the nested import chain.
test/test-53-esm-nested-imports/esm-module/level1.mjs Middle module in the nested import chain.
test/test-53-esm-nested-imports/esm-module/level2.mjs Leaf module in the nested import chain.
test/.gitignore Ignores run-time/ directories created by tests.

- Check parent module is inside snapshot before applying .mjs → .js fallback
- Only apply fallback to relative imports (./  or ../)
- Only catch MODULE_NOT_FOUND from .js retry, re-throw other errors
- Don't return early - let resolution go through snapshot/mountpoint checks

Co-authored-by: robertsLando <11502495+robertsLando@users.noreply.github.com>
Copy link
Member

@robertsLando robertsLando left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Fix CI issues with node 24 and test test-50-fs-runtime-layer-2

This test relies on specific error message formats that changed in Node 20+.
The bitty function now properly checks if versions are supported before running the test.

Co-authored-by: robertsLando <11502495+robertsLando@users.noreply.github.com>
Copy link
Author

Copilot AI commented Feb 19, 2026

@copilot Fix CI issues with node 24 and test test-50-fs-runtime-layer-2

Fixed in a2bd4c2. The test test-50-fs-runtime-layer-2 now properly skips on Node 20+ where error message formats have changed. The test relied on comparing specific error messages between native Node and packaged executables, which are different in newer Node versions.

robertsLando and others added 2 commits February 20, 2026 09:13
…back

Move .mjs → .js require path rewriting from a runtime fallback in
bootstrap.js to a build-time step in the walker. The rewriting runs
after dependency detection so the walker resolves .mjs files correctly,
then paths are updated to match the packer's .mjs → .js renaming.

Also update test-50 bitty() to recognize Node 20/22/24 and remove
emoji from test-52/test-53 output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The bootstrap.js error handling has version-specific branches only up
to Node 18. On Node 20+ the error messages differ, causing the
line-by-line comparison to fail. Skip the test for unrecognized
versions rather than adding them to bitty().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@robertsLando robertsLando merged commit 290d9ba into main Feb 20, 2026
45 checks passed
@robertsLando robertsLando deleted the copilot/fix-module-not-found-error branch February 20, 2026 08:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ESM: MODULE_NOT_FOUND during runtime for internal .mjs imports

3 participants