Skip to content

Commit 73c18cd

Browse files
committed
sea: support VFS in embedderRequire
Enable direct require() of modules from VFS in Single Executable Applications. Previously, SEA's embedderRequire only supported built-in modules, requiring users to use module.createRequire() for VFS paths. Now, after calling sea.getVfs(), you can require VFS modules directly: ```js const sea = require('node:sea'); sea.getVfs(); // Initialize VFS const myModule = require('/sea/lib/mymodule.js'); ``` The implementation: - Lazily initializes SEA VFS on first non-builtin require - Checks if the required path exists in VFS - Uses Module._load to load from VFS via the registered hooks
1 parent 3b941d1 commit 73c18cd

File tree

4 files changed

+76
-33
lines changed

4 files changed

+76
-33
lines changed

doc/api/single-executable-applications.md

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -286,27 +286,23 @@ The VFS supports the following `fs` operations on bundled assets:
286286
287287
#### Loading modules from VFS in SEA
288288
289-
The default `require()` function in a SEA only supports loading Node.js
290-
built-in modules. To load JavaScript modules bundled as assets, you must use
291-
[`module.createRequire()`][]:
289+
Once the VFS is initialized with `sea.getVfs()`, you can use `require()` directly
290+
with absolute VFS paths:
292291
293292
```cjs
294-
const { createRequire } = require('node:module');
295293
const sea = require('node:sea');
296294
297-
// Initialize VFS
295+
// Initialize VFS - this must be called first
298296
sea.getVfs();
299297
300-
// Create a require function that works with VFS
301-
const seaRequire = createRequire('/sea/');
302-
303-
// Now you can require bundled modules
304-
const myModule = seaRequire('/sea/lib/mymodule.js');
305-
const utils = seaRequire('/sea/utils/helpers.js');
298+
// Now you can require bundled modules directly
299+
const myModule = require('/sea/lib/mymodule.js');
300+
const utils = require('/sea/utils/helpers.js');
306301
```
307302
308-
This is necessary because SEA uses a special embedder require that doesn't go
309-
through the standard module resolution hooks that VFS registers.
303+
The SEA's `require()` function automatically detects VFS paths (paths starting
304+
with the VFS mount point, e.g., `/sea/`) and loads modules from the virtual
305+
file system.
310306
311307
#### Custom mount prefix
312308
@@ -641,7 +637,6 @@ to help us document them.
641637
[Mach-O]: https://en.wikipedia.org/wiki/Mach-O
642638
[PE]: https://en.wikipedia.org/wiki/Portable_Executable
643639
[Windows SDK]: https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/
644-
[`module.createRequire()`]: module.md#modulecreaterequirefilename
645640
[`process.execPath`]: process.md#processexecpath
646641
[`require()`]: modules.md#requireid
647642
[`require.main`]: modules.md#accessing-the-main-module

lib/internal/main/embedding.js

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,22 +97,70 @@ function embedderRunCjs(content) {
9797

9898
let warnedAboutBuiltins = false;
9999

100+
// Lazy-loaded SEA VFS support
101+
let seaVfsInitialized = false;
102+
let seaVfs = null;
103+
let seaVfsMountPoint = null;
104+
105+
function initSeaVfs() {
106+
if (seaVfsInitialized) return;
107+
seaVfsInitialized = true;
108+
109+
if (!isLoadingSea) return;
110+
111+
// Check if SEA has assets (VFS support)
112+
const { hasSeaAssets, getSeaVfs } = require('internal/vfs/sea');
113+
if (hasSeaAssets()) {
114+
seaVfs = getSeaVfs();
115+
if (seaVfs) {
116+
seaVfsMountPoint = seaVfs.mountPoint;
117+
}
118+
}
119+
}
120+
100121
function embedderRequire(id) {
101122
const normalizedId = normalizeRequirableId(id);
102-
if (!normalizedId) {
103-
if (isBuiltinWarningNeeded && !warnedAboutBuiltins) {
104-
emitWarningSync(
105-
'Currently the require() provided to the main script embedded into ' +
106-
'single-executable applications only supports loading built-in modules.\n' +
107-
'To load a module from disk after the single executable application is ' +
108-
'launched, use require("module").createRequire().\n' +
109-
'Support for bundled module loading or virtual file systems are under ' +
110-
'discussions in https://github.com/nodejs/single-executable');
111-
warnedAboutBuiltins = true;
123+
if (normalizedId) {
124+
// Built-in module
125+
return require(normalizedId);
126+
}
127+
128+
// Not a built-in module - check if it's a VFS path in SEA
129+
if (isLoadingSea) {
130+
initSeaVfs();
131+
132+
if (seaVfs && seaVfsMountPoint) {
133+
// Check if the path is within the VFS mount point
134+
// Support both absolute paths (/sea/...) and relative to mount point
135+
let modulePath = id;
136+
if (id.startsWith(seaVfsMountPoint) || id.startsWith('/')) {
137+
// Absolute path - resolve within VFS
138+
if (!id.startsWith(seaVfsMountPoint) && id.startsWith('/')) {
139+
// Path like '/modules/foo.js' - prepend mount point
140+
modulePath = seaVfsMountPoint + id;
141+
}
142+
143+
// Check if the file exists in VFS
144+
if (seaVfs.existsSync(modulePath)) {
145+
// Use Module._load to load the module, which will use VFS hooks
146+
return Module._load(modulePath, embedderRequire.main, false);
147+
}
148+
}
112149
}
113-
throw new ERR_UNKNOWN_BUILTIN_MODULE(id);
114150
}
115-
return require(normalizedId);
151+
152+
// No VFS or file not in VFS - show warning and throw
153+
if (isBuiltinWarningNeeded && !warnedAboutBuiltins) {
154+
emitWarningSync(
155+
'Currently the require() provided to the main script embedded into ' +
156+
'single-executable applications only supports loading built-in modules.\n' +
157+
'To load a module from disk after the single executable application is ' +
158+
'launched, use require("module").createRequire().\n' +
159+
'Support for bundled module loading or virtual file systems are under ' +
160+
'discussions in https://github.com/nodejs/single-executable');
161+
warnedAboutBuiltins = true;
162+
}
163+
throw new ERR_UNKNOWN_BUILTIN_MODULE(id);
116164
}
117165

118166
return [process, embedderRequire, embedderRunCjs];

test/parallel/test-vfs-chdir.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,10 @@ const fs = require('fs');
165165
const originalCwd = process.cwd();
166166

167167
// Change to a real directory (not under /virtual)
168+
// Use realpathSync because /tmp may be a symlink (e.g., /tmp -> /private/tmp on macOS)
169+
const tmpDir = fs.realpathSync('/tmp');
168170
process.chdir('/tmp');
169-
assert.strictEqual(process.cwd(), '/tmp');
171+
assert.strictEqual(process.cwd(), tmpDir);
170172
// vfs.cwd() should still be null (not set)
171173
assert.strictEqual(vfs.cwd(), null);
172174

test/sea/test-single-executable-application-vfs.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'se
2323
const seaMain = `
2424
'use strict';
2525
const fs = require('fs');
26-
const { createRequire } = require('module');
2726
const sea = require('node:sea');
2827
const assert = require('assert');
2928
@@ -70,13 +69,12 @@ assert.ok(entries.includes('config.json'), 'Should include config.json');
7069
assert.ok(entries.includes('data'), 'Should include data directory');
7170
console.log('readdirSync tests passed, entries:', entries);
7271
73-
// Test requiring a module from SEA VFS using module.createRequire()
74-
// (SEA's built-in require only supports built-in modules)
75-
const seaRequire = createRequire('/sea/');
76-
const mathModule = seaRequire('/sea/modules/math.js');
72+
// Test requiring a module from SEA VFS using direct require()
73+
// (SEA's require now supports VFS paths automatically)
74+
const mathModule = require('/sea/modules/math.js');
7775
assert.strictEqual(mathModule.add(2, 3), 5, 'math.add should work');
7876
assert.strictEqual(mathModule.multiply(4, 5), 20, 'math.multiply should work');
79-
console.log('require from VFS tests passed');
77+
console.log('direct require from VFS tests passed');
8078
8179
// Test getSeaVfs with custom prefix
8280
const customVfs = sea.getVfs({ prefix: '/custom' });

0 commit comments

Comments
 (0)