nodejs / nodejs/node

SEA: embedder main cannot dynamically import non-builtin modules on Node 25.5+

Đang mở
#62,726 6 bình luận 2 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Ngôn ngữ chính
JavaScript
Star
122k
Fork
37.3k
Merge trung bình
4 ngày 2 giờ
Pull request đã merge (30 ngày)
283

Mô tả

Version

v25.9.0 (also reproduces on v25.7.0, v25.8.x). Does not reproduce on v24.x.

Platform

Linux 6.17.0-19-generic x86_64

Subsystem

sea, esm, embedding

What steps will reproduce the bug?

Minimal repro, no external tools beyond postject:

mkdir -p /tmp/sea-repro && cd /tmp/sea-repro

cat > user.mjs <<'JS'
console.log('hello from user module');
JS

cat > bootstrap.js <<'JS'
import('file:///tmp/sea-repro/user.mjs').catch(err => {
  console.error(err);
  process.exit(1);
});
JS

cat > sea-config.json <<'JSON'
{
  "main": "bootstrap.js",
  "output": "sea-prep.blob",
  "disableExperimentalSEAWarning": true
}
JSON

node --experimental-sea-config sea-config.json
cp "$(command -v node)" ./app
npx postject ./app NODE_SEA_BLOB sea-prep.blob \
  --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
./app

Same failure occurs when:

  • mainFormat is set to "module" and the bootstrap uses await import('file:///...')
  • a CJS bootstrap calls require('/abs/path.js') on a non-builtin path
How often does it reproduce? Is there a required condition?

100% on Node 25.5+. The identical repro prints hello from user module on Node 24.14.0, so this is a regression introduced somewhere in the 25.5+ window (around the --build-sea landing in #61167 and the mainFormat: "module" support in #61813).

What is the expected behavior?

Dynamic import() (and require() of absolute paths) from the SEA main should resolve through the normal module loader. This is how SEA worked from v20 through v24 and is what enables the common pattern of a small bootstrap baked into the SEA that stages setup (VFS overlays, monkey-patches, diagnostics, ...) and then hands control off to a real user entrypoint on disk.

What do you see instead?
Error [ERR_UNKNOWN_BUILTIN_MODULE]: No such built-in module: file:///tmp/sea-repro/user.mjs
    at loadBuiltinModuleForEmbedder (node:internal/modules/helpers:165:9)
    at getBuiltinModuleWrapForEmbedder (node:internal/modules/esm/utils:237:10)
    at importModuleDynamicallyForEmbedder (node:internal/modules/esm/utils:250:10)
    at importModuleDynamicallyCallback (node:internal/modules/esm/utils:282:12)
    at bootstrap.js:1:1
    at embedderRunCjs (node:internal/main/embedding:93:10)
    at embedderRunEntryPoint (node:internal/main/embedding:128:12) {
  code: 'ERR_UNKNOWN_BUILTIN_MODULE'
}
Root cause

From lib/internal/modules/esm/utils.js in v25.9.0:

// For embedder entry point ESM, only allow built-in modules.
if (referrerSymbol === embedder_module_hdo) {
  return importModuleDynamicallyForEmbedder(specifier, phase, attributes, referrerName);
}
function importModuleDynamicallyForEmbedder(specifier, phase, attributes, referrerName) {
  // Ignore phase and attributes for embedder ESM for now, because this only supports loading builtins.
  return getBuiltinModuleWrapForEmbedder(specifier).getNamespace();
}

The CJS path has the same limitation: embedderRequire in lib/internal/main/embedding.js routes through loadBuiltinModuleForEmbedder, so require('/abs/path') from a CJS SEA main on Node 25.5+ also throws ERR_UNKNOWN_BUILTIN_MODULE.

This means the SEA main — whether mainFormat: "commonjs" or mainFormat: "module" — can only load builtin modules via its own require/import(), and cannot hand off to a user script.

Additional information

Workaround (for anyone hitting this in the wild): obtain Module via require('module') (which succeeds because module is a builtin), set process.argv[1] to the real entrypoint, then call Module.runMain(). Module.runMain uses the real CJS loader and, on Node 22.12+, transparently handles ESM entries via require(esm). Caveat: user entrypoints that use top-level await cannot go through this path — require(esm) rejects them — so there is currently no way to load a TLA-using ESM user entrypoint from an SEA main on Node 25.5+.

Request: route importModuleDynamicallyForEmbedder and embedderRequire through the default loaders (the same path source_text_module_default_hdo / vm_dynamic_import_default_internal use), so embedders — including SEA — can keep using dynamic import() / require() to hand off to a user entrypoint after setup.

Context: I'm the maintainer of yao-pkg/pkg (the maintained fork of vercel/pkg); pkg's enhanced SEA mode builds a small bootstrap that mounts a VFS and then hands off to the user entrypoint, which is exactly the pattern this regression breaks.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Tái hiện lỗi SEA bằng bootstrap.js, sea-config.json được cung cấp và lệnh postject, sau đó kiểm tra lib/internal/modules/esm/utils.js và lib/internal/main/embedding.js, đặc biệt là importModuleDynamicallyForEmbedder và embedderRequire. So sánh hành vi của chúng với các đường dẫn của loader mặc định và xác minh rằng các SEA main có thể chuyển tiếp đến các entrypoint ESM và CommonJS bằng đường dẫn tuyệt đối, đồng thời giữ nguyên việc tải các module tích hợp sẵn.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
javascript, nodejs
Lĩnh vực
backend
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Ít trao đổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
52/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.