Repository navigation
feat: nodejs SEA improvements #204
Description
RFC: Modernizing pkg with Official Node.js SEA Support and a Transparent VFS
Purpose
The goal is to transition pkg from a legacy "binary-patching" model to a "Native SEA Container + Virtual Bootstrap" model. This will fix long-standing issues with ESM support, native addons, and brittle Node.js version compatibility by leveraging official runtime features introduced in Node.js v20 through v25.5.0.
The Problem with Current SEA
The official Node.js SEA implementation is currently "limited" for the average developer because:
- Manual Bundling Required: It expects a single entry-point file. Large applications with complex
node_modulesmust be manually rolled up (e.g., viaesbuild). - Opaque Asset Access: Files aren't visible to
fs. Developers must rewrite code to userequire('node:sea').getAsset(). - Native Addon Friction:
.nodefiles cannot be loaded from the blob without manual extraction to a temporary directory.
Proposed Solution
We can use the existing pkg Walker to automate the creation of a sophisticated SEA blob that "tricks" the application into thinking it is running in a normal filesystem.
1. Build-Time: Automated SEA Orchestration
Instead of pkg creating its own custom bundle, it will generate an official SEA Preparation Blob:
- Enhanced Walker: Re-use the existing
pkgwalker to traverse the dependency graph and identify all necessary scripts, assets, and native addons. - Auto-Generated Config:
pkgwill generate asea-config.jsonwhere every file found by the walker is mapped to theassetsfield. - Native Injection: Use the official Node.js
--build-seaflag (Node v25.5.0+) to inject the blob, ensuring the binary is properly signed and utilizes native "fuses" for stability.
2. Runtime: The Bootstrap "Illusion"
pkg will inject a hidden Bootstrap Loader as the main script in the SEA. This script executes before the user's code to set up the virtual environment:
- Transparent
fsPatching: The bootstrap will monkey-patchnode:fs(andnode:fs/promises). Whenfs.readFileSync('./data.json')is called, the patch intercepts the request and retrieves the buffer from the SEA asset store usingnode:sea. - ESM/Module Support: By registering a custom loader via
module.register(), we can handleimportstatements natively, pulling code directly from the SEA blob without requiring a single-file CJS rollup. - Automatic Native Addon Extraction: When a
.nodefile is required, the bootstrap will:
- Extract the addon from the SEA blob to a temporary directory (e.g.,
os.tmpdir()/pkg-sea/...). - Automatically load it via
process.dlopen(). - The user's code remains unchanged.
Technical Roadmap
| Phase | Task | Details |
|---|---|---|
| Build | sea-config Generation |
Map walker results to assets dictionary. |
| Build | Native Compilation | Support V8 Bytecode via useCodeCache: true in SEA config. |
| Runtime | Bootstrap Script | Create a CJS entry point that initializes the VFS. |
| Runtime | node:fs Polyfill |
Redirect standard file calls to node:sea.getAsset(). |
| Runtime | Addon Manager | Automate the "Extract-and-Load" lifecycle for .node files. |
Proposed Developer Experience
The user experience remains identical to the classic pkg workflow:
# pkg detects Node version and uses SEA mode automatically
pkg --sea. -t node24-win-x64
Benefits
- Future-Proof: Moves away from brittle offsets and toward official Node.js internal APIs (
FindSingleExecutableResource). - ESM & TS Ready: Native support for modern module graphs without complex CJS transpilation hacks.
- Security: Better integration with OS-level code signing (especially macOS/Windows) since the binary structure follows official Node.js standards.
Activity
robertsLando commentedon Feb 23, 2026
Seems there is some work in progress for supporting VFS on Nodejs that will make this easier: nodejs/node#61478
robertsLando commentedon Feb 23, 2026
Do some investigations on: nodejs/node#61813 as well
feat: enhanced SEA mode with walker integration and VFS (#204)
feat: enhanced SEA mode with walker integration and VFS (yao-pkg#229)
viceice commentedon Apr 22, 2026
it seems sea builds are not yet reproducible?
https://yao-pkg.github.io/pkg/guide/bytecode#reproducible-builds
robertsLando commentedon Apr 22, 2026
@viceice they don't ship bytecode so them are reproducible
viceice commentedon Apr 22, 2026
it seems docker doesn't like the new time stamp, it's always doing that copy step. 😕
i think the old non sea mode didn't had that issue.
maybe don't overwrite files if checksum is same? 🤔
robertsLando commentedon Apr 22, 2026
@viceice I'm not sure I understand what you mean here, could you be more specific pleae?
viceice commentedon Apr 22, 2026
you can run
pnpm test:docker -t build-test -l debug jb -btwice and the docker layer isn't using the cached value.i can verify the binaries are same when using
diff old new.so i wonder if it's because the created / changed timestamp of the binaries 🤔
viceice commentedon Apr 22, 2026
pnpm test:docker -t build-test -l debug jb(without-bbuild` it uses the cache for bulding the docker imageviceice commentedon Apr 22, 2026
yes, it seems to be the modified timestamp wich invalidates the context copy, so it always rebuilds.
btw: i build the binaries on host, not in a docker layer.
viceice commentedon Apr 22, 2026
interesting, when compiling as cjs then caching worksno, also doesn't workviceice commentedon Apr 22, 2026
some more findings when compiling as cjs:
/snapshot/app/main.js(entrypoint) callsnew Worker(path)/snapshot/app/worker.js` (woker)path='./worker.js'->Error: Cannot find module '~/dist/thread-stream-worker.js'(searches on host)path=new URL('./worker.js', require("url").pathToFileURL(__filename).href)->Error: Cannot find module '/snapshot/dist/app/worker.js'path=new URL('./worker.js', require("url").pathToFileURL(__filename).href).pathname-> workspath='/snapshot/dist/app/worker.js'-> worksmy idea was to not hardcode the
/snapshot/dist/apppath inside the js code.☝️ My fault. Worker takes a filename relative to⚠️
process.cwd, so i need to manually need to ensure it's using full path to vfs!viceice commentedon Apr 22, 2026
another strange warning when using esm target:
robertsLando commentedon Apr 22, 2026
could you collect all legit findings on an issue plesase? Consider also opening a PR in case you find the coolprit in the code