Skip to content

Commit 7e5ab70

Browse files
committed
sea,vfs: address review feedback from Joyee
- Make SEA VFS opt-in via `"useVfs": true` config field with corresponding `kEnableVfs` flag in C++ and `isVfsEnabled()` binding - Replace manual VFS path resolution in embedderRequire with wrapModuleLoad() that flows through registered module hooks, supporting transitive requires - Set main module filename to VFS path when VFS is active so relative require('./foo.js') resolves correctly - Convert _-prefixed methods to # private in file_system.js - Fix inaccurate code caching docs per reviewer suggestion - Replace supported sync method list with unsupported method list - Add native addon limitation note for VFS in SEA docs
1 parent 164ba61 commit 7e5ab70

File tree

10 files changed

+253
-215
lines changed

10 files changed

+253
-215
lines changed

doc/api/single-executable-applications.md

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ The configuration currently reads the following top-level fields:
115115
"disableExperimentalSEAWarning": true, // Default: false
116116
"useSnapshot": false, // Default: false
117117
"useCodeCache": true, // Default: false
118+
"useVfs": true, // Default: false
118119
"execArgv": ["--no-warnings", "--max-old-space-size=4096"], // Optional
119120
"execArgvExtension": "env", // Default: "env", options: "none", "env", "cli"
120121
"assets": { // Optional
@@ -180,9 +181,10 @@ See documentation of the [`sea.getAsset()`][], [`sea.getAssetAsBlob()`][],
180181
181182
Instead of using the `node:sea` API to access individual assets, you can use
182183
the Virtual File System (VFS) to access bundled assets through standard `fs`
183-
APIs. When running as a Single Executable Application, the VFS is automatically
184-
initialized and mounted at `/sea`. All assets defined in the SEA configuration
185-
are accessible through this virtual path.
184+
APIs. To enable VFS, set `"useVfs": true` in the SEA configuration. When
185+
enabled, the VFS is automatically initialized and mounted at `/sea`. All
186+
assets defined in the SEA configuration are accessible through this virtual
187+
path.
186188

187189
```cjs
188190
const fs = require('node:fs');
@@ -224,16 +226,27 @@ const myModule = require('/sea/lib/mymodule.js');
224226
const utils = require('/sea/utils/helpers.js');
225227
```
226228

227-
The SEA's `require()` function automatically detects VFS paths (paths starting
228-
with `/sea/`) and loads modules from the virtual file system.
229+
When `useVfs` is enabled, `require()` in the injected main script uses the
230+
registered module hooks to load modules from the virtual file system. This
231+
supports both absolute VFS paths and relative requires (e.g.,
232+
`require('./helper.js')` from a VFS module).
233+
234+
When `useVfs` is enabled, `__filename` and `__dirname` in the injected main
235+
script reflect VFS paths (e.g., `/sea/main.js`) instead of
236+
[`process.execPath`][].
229237

230238
#### Code caching limitations
231239

232240
The `useCodeCache` option in the SEA configuration does not apply to modules
233-
loaded from the VFS. Code caching requires executing modules during the SEA
234-
build process to generate cached code, but VFS modules are only available at
235-
runtime after the SEA is built. If performance is critical, consider using
236-
`useCodeCache` for your main entry point and `useSnapshot` for initialization.
241+
loaded from the VFS. Consider bundling the application to enable code caching
242+
and do not rely on module loading in VFS.
243+
244+
#### Native addon limitations
245+
246+
Native addons (`.node` files) cannot be loaded directly from the VFS because
247+
`process.dlopen()` requires files on the real file system. To use native
248+
addons in a SEA with VFS, write the asset to a temporary file first. See
249+
[Using native addons in the injected main script][] for an example.
237250

238251
### Startup snapshot support
239252

@@ -665,6 +678,7 @@ to help us document them.
665678
[Mach-O]: https://en.wikipedia.org/wiki/Mach-O
666679
[PE]: https://en.wikipedia.org/wiki/Portable_Executable
667680
[Windows SDK]: https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/
681+
[Using native addons in the injected main script]: #using-native-addons-in-the-injected-main-script
668682
[`process.execPath`]: process.md#processexecpath
669683
[`require()`]: modules.md#requireid
670684
[`require.main`]: modules.md#accessing-the-main-module

doc/api/vfs.md

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,9 @@ files exist only in memory.
164164
### Code caching in SEA
165165

166166
When using VFS with Single Executable Applications, the `useCodeCache` option
167-
in the SEA configuration does not apply to modules loaded from the VFS. Code
168-
caching requires executing modules during the SEA build process, but VFS
169-
modules are only available at runtime.
167+
in the SEA configuration does not apply to modules loaded from the VFS.
168+
Consider bundling the application to enable code caching and do not rely on
169+
module loading in VFS.
170170

171171
## `vfs.create([provider][, options])`
172172

@@ -464,26 +464,19 @@ before VFS operations.
464464

465465
#### Synchronous Methods
466466

467-
* `vfs.accessSync(path[, mode])` - Check file accessibility
468-
* `vfs.appendFileSync(path, data[, options])` - Append to a file
469-
* `vfs.closeSync(fd)` - Close a file descriptor
470-
* `vfs.copyFileSync(src, dest[, mode])` - Copy a file
471-
* `vfs.existsSync(path)` - Check if path exists
472-
* `vfs.lstatSync(path[, options])` - Get file stats (no symlink follow)
473-
* `vfs.mkdirSync(path[, options])` - Create a directory
474-
* `vfs.openSync(path, flags[, mode])` - Open a file
475-
* `vfs.readdirSync(path[, options])` - Read directory contents
476-
* `vfs.readFileSync(path[, options])` - Read a file
477-
* `vfs.readlinkSync(path[, options])` - Read symlink target
478-
* `vfs.readSync(fd, buffer, offset, length, position)` - Read from fd
479-
* `vfs.realpathSync(path[, options])` - Resolve symlinks
480-
* `vfs.renameSync(oldPath, newPath)` - Rename a file or directory
481-
* `vfs.rmdirSync(path)` - Remove a directory
482-
* `vfs.statSync(path[, options])` - Get file stats
483-
* `vfs.symlinkSync(target, path[, type])` - Create a symlink
484-
* `vfs.unlinkSync(path)` - Remove a file
485-
* `vfs.writeFileSync(path, data[, options])` - Write a file
486-
* `vfs.writeSync(fd, buffer, offset, length, position)` - Write to fd
467+
The `VirtualFileSystem` class supports all common synchronous `fs` methods
468+
for reading, writing, and managing files and directories. Methods mirror the
469+
`fs` module API.
470+
471+
The following `fs` sync methods have **no** VFS equivalent:
472+
473+
* `chmodSync()` / `fchmodSync()` - VFS does not support permission changes
474+
* `chownSync()` / `fchownSync()` - VFS does not support ownership changes
475+
* `truncateSync()` / `ftruncateSync()` - Use `writeFileSync()` instead
476+
* `utimesSync()` / `futimesSync()` / `lutimesSync()` - VFS does not support
477+
changing timestamps
478+
* `linkSync()` - VFS does not support hard links (use `symlinkSync()`)
479+
* `fdatasyncSync()` / `fsyncSync()` - Not applicable to in-memory storage
487480

488481
#### Promise Methods
489482

@@ -790,8 +783,9 @@ operations without file descriptor collisions.
790783

791784
## Use with Single Executable Applications
792785

793-
When running as a Single Executable Application (SEA), bundled assets are
794-
automatically mounted at `/sea`. No additional setup is required:
786+
When running as a Single Executable Application (SEA) with `"useVfs": true` in
787+
the SEA configuration, bundled assets are automatically mounted at `/sea`. No
788+
additional setup is required:
795789

796790
```cjs
797791
// In your SEA entry script

lib/internal/main/embedding.js

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
const {
1313
prepareMainThreadExecution,
1414
} = require('internal/process/pre_execution');
15-
const { isExperimentalSeaWarningNeeded, isSea } = internalBinding('sea');
15+
const { isExperimentalSeaWarningNeeded, isSea, isVfsEnabled } = internalBinding('sea');
1616
const { emitExperimentalWarning } = require('internal/util');
1717
const { emitWarningSync } = require('internal/process/warning');
1818
const { Module, wrapModuleLoad } = require('internal/modules/cjs/loader');
@@ -85,9 +85,16 @@ function embedderRunCjs(content, filename) {
8585
}
8686

8787
// Patch the module to make it look almost like a regular CJS module
88-
// instance.
89-
customModule.filename = process.execPath;
90-
customModule.paths = Module._nodeModulePaths(process.execPath);
88+
// instance. When VFS is active, set the filename to a VFS path so that
89+
// relative require('./foo.js') resolves under the VFS mount point.
90+
if (seaVfsActive && seaVfsMountPoint) {
91+
customModule.filename = seaVfsMountPoint + '/' + path.basename(filename);
92+
customModule.paths = Module._nodeModulePaths(
93+
path.dirname(customModule.filename));
94+
} else {
95+
customModule.filename = process.execPath;
96+
customModule.paths = Module._nodeModulePaths(process.execPath);
97+
}
9198
embedderRequire.main = customModule;
9299

93100
// This currently returns what the wrapper returns i.e. if the code
@@ -99,8 +106,8 @@ function embedderRunCjs(content, filename) {
99106
customModule.exports, // exports
100107
embedderRequire, // require
101108
customModule, // module
102-
filename, // __filename
103-
customModule.path, // __dirname
109+
customModule.filename, // __filename
110+
path.dirname(customModule.filename), // __dirname
104111
);
105112
}
106113

@@ -120,19 +127,20 @@ function warnNonBuiltinInSEA() {
120127

121128
// Lazy-loaded SEA VFS support
122129
let seaVfsInitialized = false;
123-
let seaVfs = null;
130+
let seaVfsActive = false;
124131
let seaVfsMountPoint = null;
125132

126133
function initSeaVfs() {
127134
if (seaVfsInitialized) return;
128135
seaVfsInitialized = true;
129136

130-
if (!isLoadingSea) return;
137+
if (!isLoadingSea || !isVfsEnabled()) return;
131138

132139
// Check if SEA has VFS support
133140
const { getSeaVfs } = require('internal/vfs/sea');
134-
seaVfs = getSeaVfs();
141+
const seaVfs = getSeaVfs();
135142
if (seaVfs) {
143+
seaVfsActive = true;
136144
seaVfsMountPoint = seaVfs.mountPoint;
137145
}
138146
}
@@ -144,31 +152,14 @@ function embedderRequire(id) {
144152
return require(normalizedId);
145153
}
146154

147-
// Not a built-in module - check if it's a VFS path in SEA
148-
if (isLoadingSea) {
149-
initSeaVfs();
150-
151-
if (seaVfs && seaVfsMountPoint) {
152-
// Check if the path is within the VFS mount point
153-
// Support both absolute paths (/sea/...) and relative to mount point
154-
let modulePath = id;
155-
if (id.startsWith(seaVfsMountPoint) || id.startsWith('/')) {
156-
// Absolute path - resolve within VFS
157-
if (!id.startsWith(seaVfsMountPoint) && id.startsWith('/')) {
158-
// Path like '/modules/foo.js' - prepend mount point
159-
modulePath = seaVfsMountPoint + id;
160-
}
161-
162-
// Check if the file exists in VFS
163-
if (seaVfs.existsSync(modulePath)) {
164-
// Use wrapModuleLoad to load the module with proper tracing/diagnostics
165-
return wrapModuleLoad(modulePath, embedderRequire.main, false);
166-
}
167-
}
168-
}
155+
// When VFS is active, use wrapModuleLoad which flows through the
156+
// registered Module.registerHooks({ resolve, load }) hooks in
157+
// module_hooks.js, handling both absolute VFS paths and relative requires.
158+
if (isLoadingSea && seaVfsActive) {
159+
return wrapModuleLoad(id, embedderRequire.main, false);
169160
}
170161

171-
// No VFS or file not in VFS - show warning and throw
162+
// No VFS - show warning and throw
172163
warnNonBuiltinInSEA();
173164
throw new ERR_UNKNOWN_BUILTIN_MODULE(id);
174165
}

0 commit comments

Comments
 (0)