Skip to content

Commit d78f4f9

Browse files
committed
vfs: use wrapModuleLoad for SEA VFS entry point
Use wrapModuleLoad instead of custom embedderRunCjs code when VFS is enabled. This gives proper CJS cycle handling and standard module loading behavior through the registered VFS hooks. - Restore embedderRunCjs to match upstream (no VFS-specific code) - Restore embedderRequire to use loadBuiltinModuleForEmbedder - Add VFS entry path in embedderRunEntryPoint using wrapModuleLoad with path.posix.join for cross-platform path construction - Auto-include main script as VFS asset during blob generation so wrapModuleLoad can find it at the VFS mount point - Expose mainCodePath from SEA binding to construct correct VFS path
1 parent 95c4972 commit d78f4f9

File tree

2 files changed

+38
-53
lines changed

2 files changed

+38
-53
lines changed

lib/internal/main/embedding.js

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,16 @@ const {
1515
const {
1616
isSea,
1717
isVfsEnabled: isVfsEnabledFlag,
18+
mainCodePath: seaMainCodePath,
1819
isExperimentalSeaWarningNeeded,
1920
} = internalBinding('sea');
20-
const isLoadingSea = isSea();
2121
const { emitExperimentalWarning } = require('internal/util');
2222
const { emitWarningSync } = require('internal/process/warning');
23-
const { Module } = require('internal/modules/cjs/loader');
23+
const { Module, wrapModuleLoad } = require('internal/modules/cjs/loader');
2424
const { compileFunctionForCJSLoader } = internalBinding('contextify');
2525
const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
26-
const { BuiltinModule } = require('internal/bootstrap/realm');
27-
const { normalizeRequirableId } = BuiltinModule;
28-
const { codes: {
29-
ERR_UNKNOWN_BUILTIN_MODULE,
30-
} } = require('internal/errors');
3126
const { pathToFileURL } = require('internal/url');
27+
const { loadBuiltinModuleForEmbedder } = require('internal/modules/helpers');
3228
const { compileSourceTextModule, SourceTextModuleTypes: { kEmbedder } } = require('internal/modules/esm/utils');
3329
const { moduleFormats } = internalBinding('modules');
3430
const assert = require('internal/assert');
@@ -38,7 +34,7 @@ const path = require('path');
3834
// command line (e.g. it could be provided via an API or bundled into the executable).
3935
prepareMainThreadExecution(false, true);
4036

41-
const isBuiltinWarningNeeded = isLoadingSea && isExperimentalSeaWarningNeeded();
37+
const isLoadingSea = isSea();
4238
if (isExperimentalSeaWarningNeeded()) {
4339
emitExperimentalWarning('Single executable application');
4440
}
@@ -89,24 +85,9 @@ function embedderRunCjs(content, filename) {
8985
}
9086

9187
// Patch the module to make it look almost like a regular CJS module
92-
// instance. When VFS is active, set the filename to a VFS path so that
93-
// relative require('./foo.js') resolves under the VFS mount point,
94-
// and use createRequire for a fully-featured require function.
95-
let requireFn;
96-
if (seaVfsActive && seaVfsMountPoint) {
97-
// VFS paths always use POSIX separators regardless of platform.
98-
customModule.filename = seaVfsMountPoint + '/' + path.basename(filename);
99-
customModule.paths = Module._nodeModulePaths(
100-
path.dirname(customModule.filename));
101-
// Use createRequire so that require has all standard properties
102-
// (resolve, cache, etc.) and builtin loading flows through hooks.
103-
requireFn = Module.createRequire(customModule.filename);
104-
requireFn.main = customModule;
105-
} else {
106-
customModule.filename = process.execPath;
107-
customModule.paths = Module._nodeModulePaths(process.execPath);
108-
requireFn = embedderRequire;
109-
}
88+
// instance.
89+
customModule.filename = process.execPath;
90+
customModule.paths = Module._nodeModulePaths(process.execPath);
11091
embedderRequire.main = customModule;
11192

11293
// This currently returns what the wrapper returns i.e. if the code
@@ -116,27 +97,13 @@ function embedderRunCjs(content, filename) {
11697
// out parameter.
11798
return compiledWrapper(
11899
customModule.exports, // exports
119-
requireFn, // require
100+
embedderRequire, // require
120101
customModule, // module
121-
customModule.filename, // __filename
122-
path.dirname(customModule.filename), // __dirname
102+
filename, // __filename
103+
customModule.path, // __dirname
123104
);
124105
}
125106

126-
let warnedAboutBuiltins = false;
127-
function warnNonBuiltinInSEA() {
128-
if (isBuiltinWarningNeeded && !warnedAboutBuiltins) {
129-
emitWarningSync(
130-
'Currently the require() provided to the main script embedded into ' +
131-
'single-executable applications only supports loading built-in modules.\n' +
132-
'To load a module from disk after the single executable application is ' +
133-
'launched, use require("module").createRequire().\n' +
134-
'Support for bundled module loading or virtual file systems are under ' +
135-
'discussions in https://github.com/nodejs/single-executable');
136-
warnedAboutBuiltins = true;
137-
}
138-
}
139-
140107
// Lazy-loaded SEA VFS support
141108
let seaVfsInitialized = false;
142109
let seaVfsActive = false;
@@ -148,7 +115,6 @@ function initSeaVfs() {
148115

149116
if (!isLoadingSea || !isVfsEnabledFlag) return;
150117

151-
// Check if SEA has VFS support
152118
const { getSeaVfs } = require('internal/vfs/sea');
153119
const seaVfs = getSeaVfs();
154120
if (seaVfs) {
@@ -158,15 +124,7 @@ function initSeaVfs() {
158124
}
159125

160126
function embedderRequire(id) {
161-
const normalizedId = normalizeRequirableId(id);
162-
if (normalizedId) {
163-
return require(normalizedId);
164-
}
165-
166-
// When VFS is not active, only built-in modules are supported.
167-
// VFS-enabled SEAs use createRequire instead of embedderRequire.
168-
warnNonBuiltinInSEA();
169-
throw new ERR_UNKNOWN_BUILTIN_MODULE(id);
127+
return loadBuiltinModuleForEmbedder(id).exports;
170128
}
171129

172130
function embedderRunESM(content, filename) {
@@ -195,6 +153,12 @@ function embedderRunEntryPoint(content, format, filename) {
195153
initSeaVfs();
196154
}
197155

156+
if (seaVfsActive && format === moduleFormats.kCommonJS) {
157+
const mainName = seaMainCodePath ? path.basename(seaMainCodePath) : path.basename(filename);
158+
const vfsMain = path.posix.join(seaVfsMountPoint, mainName);
159+
return wrapModuleLoad(vfsMain, null, true);
160+
}
161+
198162
if (format === moduleFormats.kCommonJS) {
199163
return embedderRunCjs(content, filename);
200164
} else if (format === moduleFormats.kModule) {

src/node_sea.cc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,12 @@ ExitCode GenerateSingleExecutableBlob(
752752
if (!config.assets.empty() && BuildAssets(config.assets, &assets) != 0) {
753753
return ExitCode::kGenericUserError;
754754
}
755+
// When VFS is enabled, include the main script as an asset so it can be
756+
// loaded via wrapModuleLoad from the VFS mount point.
757+
if (static_cast<bool>(config.flags & SeaFlags::kEnableVfs) &&
758+
!builds_snapshot_from_main) {
759+
assets.emplace(config.main_path, main_script);
760+
}
755761
std::unordered_map<std::string_view, std::string_view> assets_view;
756762
for (auto const& [key, content] : assets) {
757763
assets_view.emplace(key, content);
@@ -909,6 +915,21 @@ void Initialize(Local<Object> target,
909915
SeaResource sea_resource = FindSingleExecutableResource();
910916
is_vfs_enabled =
911917
static_cast<bool>(sea_resource.flags & SeaFlags::kEnableVfs);
918+
// Expose the main code path so VFS can construct the correct entry point.
919+
if (is_vfs_enabled) {
920+
Local<String> code_path_str;
921+
if (String::NewFromUtf8(isolate,
922+
sea_resource.code_path.data(),
923+
NewStringType::kNormal,
924+
sea_resource.code_path.length())
925+
.ToLocal(&code_path_str)) {
926+
target
927+
->Set(context,
928+
FIXED_ONE_BYTE_STRING(isolate, "mainCodePath"),
929+
code_path_str)
930+
.Check();
931+
}
932+
}
912933
}
913934
target
914935
->Set(context,

0 commit comments

Comments
 (0)