mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
A single executable application built with "vfsArchive" mounted its archive at the next free layer, like any file system a program mounts itself, and only in the main thread. A worker had no mount at all, so a path into the archive, such as the main script's __filename, could not be loaded there. Mount the archive at the layer reserved for --vfs-load instead, so it is at the same mount point in every thread, and mount it in each worker as well, with the main script placed at its root just as in the main thread. The worker reads the main script through a new getMainCode() binding, since no main script is handed to it. Unlike the --vfs-load source, the archive needs no provider that a preload could register, so its mount in a worker is not deferred past --import. Since the archive takes that mount point, reject --vfs-load alongside it: --build-sea refuses it in "execArgv", and the executable refuses it at startup when it comes from --node-options or from the execArgv of a worker. Also fix the example in vfs.md, which showed a file system a program mounts itself at layer 0, the layer reserved for these mounts. Signed-off-by: Philipp Dunkel <pip@pipobscure.com> PR-URL: https://github.com/nodejs/node/pull/66709 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
184 lines
6.6 KiB
JavaScript
184 lines
6.6 KiB
JavaScript
'use strict';
|
|
|
|
// This main script is currently only run when LoadEnvironment()
|
|
// is run with a non-null StartExecutionCallback or a UTF8
|
|
// main script. Effectively there are two cases where this happens:
|
|
// 1. It's a single-executable application *loading* a main script
|
|
// bundled into the executable. This is currently done from
|
|
// NodeMainInstance::Run().
|
|
// 2. It's an embedder application and LoadEnvironment() is invoked
|
|
// as described above.
|
|
|
|
const {
|
|
prepareMainThreadExecution,
|
|
} = require('internal/process/pre_execution');
|
|
const {
|
|
isExperimentalSeaWarningNeeded,
|
|
isSea,
|
|
isVfsEnabled,
|
|
} = internalBinding('sea');
|
|
const { emitExperimentalWarning } = require('internal/util');
|
|
const { emitWarningSync } = require('internal/process/warning');
|
|
const { Module, wrapModuleLoad } = require('internal/modules/cjs/loader');
|
|
const { compileFunctionForCJSLoader } = internalBinding('contextify');
|
|
const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
|
|
const { pathToFileURL } = require('internal/url');
|
|
const { loadBuiltinModuleForEmbedder } = require('internal/modules/helpers');
|
|
const { compileSourceTextModule, SourceTextModuleTypes: { kEmbedder } } = require('internal/modules/esm/utils');
|
|
const { moduleFormats } = internalBinding('modules');
|
|
const assert = require('internal/assert');
|
|
const path = require('path');
|
|
// Don't expand process.argv[1] because in a single-executable application or an
|
|
// embedder application, the user main script isn't necessarily provided via the
|
|
// command line (e.g. it could be provided via an API or bundled into the executable).
|
|
prepareMainThreadExecution(false, true);
|
|
|
|
const isLoadingSea = isSea();
|
|
if (isExperimentalSeaWarningNeeded()) {
|
|
emitExperimentalWarning('Single executable application');
|
|
}
|
|
|
|
// This is roughly the same as:
|
|
//
|
|
// const mod = new Module(filename);
|
|
// mod._compile(content, filename);
|
|
//
|
|
// but the code has been duplicated because currently there is no way to set the
|
|
// value of require.main to module.
|
|
//
|
|
// TODO(RaisinTen): Find a way to deduplicate this.
|
|
function embedderRunCjs(content, filename) {
|
|
// The filename of the module (used for CJS module lookup)
|
|
// is always the same as the location of the executable itself
|
|
// at the time of the loading (which means it changes depending
|
|
// on where the executable is in the file system).
|
|
const customModule = new Module(filename, null);
|
|
|
|
const {
|
|
function: compiledWrapper,
|
|
cachedDataRejected,
|
|
sourceMapURL,
|
|
sourceURL,
|
|
} = compileFunctionForCJSLoader(
|
|
content,
|
|
filename,
|
|
isLoadingSea, // is_sea_main
|
|
false, // should_detect_module, ESM should be supported differently for embedded code
|
|
true, // is_embedder
|
|
);
|
|
// Cache the source map for the module if present.
|
|
if (sourceMapURL) {
|
|
maybeCacheSourceMap(
|
|
filename,
|
|
content,
|
|
customModule,
|
|
false, // isGeneratedSource
|
|
sourceURL,
|
|
sourceMapURL,
|
|
);
|
|
}
|
|
|
|
// cachedDataRejected is only set if cache from SEA is used.
|
|
if (cachedDataRejected !== false && isLoadingSea) {
|
|
emitWarningSync('Code cache data rejected.');
|
|
}
|
|
|
|
// Patch the module to make it look almost like a regular CJS module
|
|
// instance.
|
|
customModule.filename = process.execPath;
|
|
customModule.paths = Module._nodeModulePaths(process.execPath);
|
|
embedderRequire.main = customModule;
|
|
|
|
// This currently returns what the wrapper returns i.e. if the code
|
|
// happens to have a return statement, it returns that; Otherwise it's
|
|
// undefined.
|
|
// TODO(joyeecheung): we may want to return the customModule or put it in an
|
|
// out parameter.
|
|
return compiledWrapper(
|
|
customModule.exports, // exports
|
|
embedderRequire, // require
|
|
customModule, // module
|
|
filename, // __filename
|
|
customModule.path, // __dirname
|
|
);
|
|
}
|
|
|
|
function embedderRequire(id) {
|
|
return loadBuiltinModuleForEmbedder(id).exports;
|
|
}
|
|
|
|
function embedderRunESM(content, filename) {
|
|
let resourceName;
|
|
if (path.isAbsolute(filename)) {
|
|
resourceName = pathToFileURL(filename).href;
|
|
} else {
|
|
resourceName = filename;
|
|
}
|
|
// TODO(joyeecheung): allow configuration from node::ModuleData,
|
|
// either via a more generic context object, or something like import.meta extensions.
|
|
const context = { isMain: true, __proto__: null };
|
|
const wrap = compileSourceTextModule(resourceName, content, kEmbedder, context);
|
|
|
|
// TODO(joyeecheung): we may want to return the v8::Module via a vm.SourceTextModule
|
|
// when vm.SourceTextModule stabilizes, or put it in an out parameter.
|
|
return wrap.getNamespace();
|
|
}
|
|
|
|
/* c8 ignore start -- only reachable in an actual SEA binary */
|
|
/**
|
|
* Mounts the SEA virtual file system with the main script placed at the
|
|
* mount point root, and returns the path of the main script inside the
|
|
* mount, or null when the VFS could not be set up.
|
|
* @param {string} content The source of the SEA main script
|
|
* @returns {string|null} The VFS path of the main script
|
|
*/
|
|
function setUpSeaVfs(content) {
|
|
const { initSeaVfs, getSeaMainName } = require('internal/vfs/sea');
|
|
const seaVfs = initSeaVfs(content);
|
|
if (seaVfs === null) {
|
|
return null;
|
|
}
|
|
return path.join(seaVfs.mountPoint, getSeaMainName());
|
|
}
|
|
/* c8 ignore stop */
|
|
|
|
function embedderRunEntryPoint(content, format, filename) {
|
|
format ||= moduleFormats.kCommonJS;
|
|
filename ||= process.execPath;
|
|
|
|
/* c8 ignore start -- only reachable in an actual SEA binary */
|
|
if (isLoadingSea && isVfsEnabled()) {
|
|
// Run the main script from inside the SEA VFS mount so that
|
|
// `__filename`, `__dirname`, `import.meta`, relative requires and
|
|
// imports, and `node_modules` lookups all resolve against the
|
|
// bundled assets.
|
|
const vfsMain = setUpSeaVfs(content);
|
|
if (vfsMain !== null) {
|
|
if (format === moduleFormats.kCommonJS) {
|
|
return wrapModuleLoad(vfsMain, null, true);
|
|
} else if (format === moduleFormats.kModule) {
|
|
const { runEntryPointWithESMLoader } =
|
|
require('internal/modules/run_main');
|
|
const mainURL = pathToFileURL(vfsMain);
|
|
return runEntryPointWithESMLoader((cascadedLoader) => {
|
|
// Note that if the graph contains unsettled TLA, this may never
|
|
// resolve even after the event loop stops running.
|
|
return cascadedLoader.import(
|
|
mainURL, undefined, { __proto__: null }, undefined, true);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
/* c8 ignore stop */
|
|
|
|
if (format === moduleFormats.kCommonJS) {
|
|
return embedderRunCjs(content, filename);
|
|
} else if (format === moduleFormats.kModule) {
|
|
return embedderRunESM(content, filename);
|
|
}
|
|
assert.fail(`Unknown format: ${format}`);
|
|
|
|
}
|
|
|
|
return [process, embedderRequire, embedderRunEntryPoint];
|