File tree

11 files changed

+208
-128
lines changed

11 files changed

+208
-128
lines changed
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,5 @@ async function checkSyntax(source, filename) {
7575
return;
7676
}
7777

78-
wrapSafe(filename, source);
78+
wrapSafe(filename, source, undefined, 'commonjs');
7979
}
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,116 @@
11
'use strict';
2+
3+
// This main script is currently only run when LoadEnvironment()
4+
// is run with a non-null StartExecutionCallback or a UTF8
5+
// main script. Effectively there are two cases where this happens:
6+
// 1. It's a single-executable application *loading* a main script
7+
// bundled into the executable. This is currently done from
8+
// NodeMainInstance::Run().
9+
// 2. It's an embedder application and LoadEnvironment() is invoked
10+
// as described above.
11+
212
const {
313
prepareMainThreadExecution,
414
} = require('internal/process/pre_execution');
5-
const { isExperimentalSeaWarningNeeded } = internalBinding('sea');
15+
const { isExperimentalSeaWarningNeeded, isSea } = internalBinding('sea');
616
const { emitExperimentalWarning } = require('internal/util');
7-
const { embedderRequire, embedderRunCjs } = require('internal/util/embedding');
17+
const { emitWarningSync } = require('internal/process/warning');
18+
const { BuiltinModule: { normalizeRequirableId } } = require('internal/bootstrap/realm');
19+
const { Module } = require('internal/modules/cjs/loader');
20+
const { compileFunctionForCJSLoader } = internalBinding('contextify');
21+
const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
22+
23+
const { codes: {
24+
ERR_UNKNOWN_BUILTIN_MODULE,
25+
} } = require('internal/errors');
826

27+
// Don't expand process.argv[1] because in a single-executable application or an
28+
// embedder application, the user main script isn't necessarily provided via the
29+
// command line (e.g. it could be provided via an API or bundled into the executable).
930
prepareMainThreadExecution(false, true);
1031

32+
const isLoadingSea = isSea();
1133
if (isExperimentalSeaWarningNeeded()) {
1234
emitExperimentalWarning('Single executable application');
1335
}
1436

37+
// This is roughly the same as:
38+
//
39+
// const mod = new Module(filename);
40+
// mod._compile(content, filename);
41+
//
42+
// but the code has been duplicated because currently there is no way to set the
43+
// value of require.main to module.
44+
//
45+
// TODO(RaisinTen): Find a way to deduplicate this.
46+
function embedderRunCjs(content) {
47+
// The filename of the module (used for CJS module lookup)
48+
// is always the same as the location of the executable itself
49+
// at the time of the loading (which means it changes depending
50+
// on where the executable is in the file system).
51+
const filename = process.execPath;
52+
const customModule = new Module(filename, null);
53+
54+
const {
55+
function: compiledWrapper,
56+
cachedDataRejected,
57+
sourceMapURL,
58+
} = compileFunctionForCJSLoader(
59+
content,
60+
filename,
61+
isLoadingSea, // is_sea_main
62+
false, // should_detect_module, ESM should be supported differently for embedded code
63+
);
64+
// Cache the source map for the module if present.
65+
if (sourceMapURL) {
66+
maybeCacheSourceMap(
67+
filename,
68+
content,
69+
customModule,
70+
false, // isGeneratedSource
71+
undefined, // sourceURL, TODO(joyeecheung): should be extracted by V8
72+
sourceMapURL,
73+
);
74+
}
75+
76+
// cachedDataRejected is only set if cache from SEA is used.
77+
if (cachedDataRejected !== false && isLoadingSea) {
78+
emitWarningSync('Code cache data rejected.');
79+
}
80+
81+
// the module to make it look almost like a regular CJS module
82+
// instance.
83+
customModule.filename = process.execPath;
84+
customModule.paths = Module._nodeModulePaths(process.execPath);
85+
embedderRequire.main = customModule;
86+
87+
return compiledWrapper(
88+
customModule.exports, // exports
89+
embedderRequire, // require
90+
customModule, // module
91+
process.execPath, // __filename
92+
customModule.path, // __dirname
93+
);
94+
}
95+
96+
let warnedAboutBuiltins = false;
97+
98+
function embedderRequire(id) {
99+
const normalizedId = normalizeRequirableId(id);
100+
if (!normalizedId) {
101+
if (isLoadingSea && !warnedAboutBuiltins) {
102+
emitWarningSync(
103+
'Currently the require() provided to the main script embedded into ' +
104+
'single-executable applications only supports loading built-in modules.\n' +
105+
'To load a module from disk after the single executable application is ' +
106+
'launched, use require("module").createRequire().\n' +
107+
'Support for bundled module loading or virtual file systems are under ' +
108+
'discussions in https://.com/nodejs/single-executable');
109+
warnedAboutBuiltins = true;
110+
}
111+
throw new ERR_UNKNOWN_BUILTIN_MODULE(id);
112+
}
113+
return require(normalizedId);
114+
}
115+
15116
return [process, embedderRequire, embedderRunCjs];
Original file line numberDiff line numberDiff line change
@@ -1350,11 +1350,10 @@ function loadESMFromCJS(mod, filename) {
13501350
* Wraps the given content in a script and runs it in a new context.
13511351
* @param {string} filename The name of the file being loaded
13521352
* @param {string} content The content of the file being loaded
1353-
* @param {Module} cjsModuleInstance The CommonJS loader instance
1354-
* @param {object} codeCache The SEA code cache
1353+
* @param {Module|undefined} cjsModuleInstance The CommonJS loader instance
13551354
* @param {'commonjs'|undefined} format Intended format of the module.
13561355
*/
1357-
function wrapSafe(filename, content, cjsModuleInstance, codeCache, format) {
1356+
function wrapSafe(filename, content, cjsModuleInstance, format) {
13581357
assert(format !== 'module'); // ESM should be handled in loadESMFromCJS().
13591358
const hostDefinedOptionId = vm_dynamic_import_default_internal;
13601359
const importModuleDynamically = vm_dynamic_import_default_internal;
@@ -1385,16 +1384,8 @@ function wrapSafe(filename, content, cjsModuleInstance, codeCache, format) {
13851384
};
13861385
}
13871386

1388-
const isMain = !!(cjsModuleInstance && cjsModuleInstance[kIsMainSymbol]);
13891387
const shouldDetectModule = (format !== 'commonjs' && getOptionValue('--experimental-detect-module'));
1390-
const result = compileFunctionForCJSLoader(content, filename, isMain, shouldDetectModule);
1391-
1392-
// cachedDataRejected is only set for cache coming from SEA.
1393-
if (codeCache &&
1394-
result.cachedDataRejected !== false &&
1395-
internalBinding('sea').isSea()) {
1396-
process.emitWarning('Code cache data rejected.');
1397-
}
1388+
const result = compileFunctionForCJSLoader(content, filename, false /* is_sea_main */, shouldDetectModule);
13981389

13991390
// Cache the source map for the module if present.
14001391
if (result.sourceMapURL) {
@@ -1423,7 +1414,7 @@ Module.._compile = function(content, filename, format) {
14231414

14241415
let compiledWrapper;
14251416
if (format !== 'module') {
1426-
const result = wrapSafe(filename, content, this, undefined, format);
1417+
const result = wrapSafe(filename, content, this, format);
14271418
compiledWrapper = result.function;
14281419
if (result.canParseAsESM) {
14291420
format = 'module';
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ translators.set('module', function moduleStrategy(url, source, isMain) {
176176
* @param {boolean} isMain - Whether the module is the entrypoint
177177
*/
178178
function loadCJSModule(module, source, url, filename, isMain) {
179-
const compileResult = compileFunctionForCJSLoader(source, filename, isMain, false);
179+
const compileResult = compileFunctionForCJSLoader(source, filename, false /* is_sea_main */, false);
180180

181181
const { function: compiledWrapper, sourceMapURL } = compileResult;
182182
// Cache the source map for the cjs module if present.
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,14 @@ std::optional<StartExecutionCallbackInfo> CallbackInfoFromArray(
308308
CHECK(process_obj->IsObject());
309309
CHECK(require_fn->IsFunction());
310310
CHECK(runcjs_fn->IsFunction());
311+
// TODO(joyeecheung): some support for running ESM as an entrypoint
312+
// is needed. The simplest API would be to add a run_esm to
313+
// StartExecutionCallbackInfo which compiles, links (to builtins)
314+
// and evaluates a SourceTextModule.
315+
// TODO(joyeecheung): the env pointer should be part of
316+
// StartExecutionCallbackInfo, otherwise embedders are forced to use
317+
// lambdas to pass it into the callback, which can make the code
318+
// difficult to read.
311319
node::StartExecutionCallbackInfo info{process_obj.As<Object>(),
312320
require_fn.As<Function>(),
313321
runcjs_fn.As<Function>()};
Original file line numberDiff line numberDiff line change
@@ -1441,12 +1441,17 @@ static std::vector<std::string_view> throws_only_in_cjs_error_messages = {
14411441
"await is only valid in async functions and "
14421442
"the top level bodies of modules"};
14431443

1444-
static MaybeLocal<Function> CompileFunctionForCJSLoader(Environment* env,
1445-
Local<Context> context,
1446-
Local<String> code,
1447-
Local<String> filename,
1448-
bool* cache_rejected,
1449-
bool is_cjs_scope) {
1444+
// If cached_data is provided, it would be used for the compilation and
1445+
// the on-disk compilation cache from NODE_COMPILE_CACHE (if configured)
1446+
// would be ignored.
1447+
static MaybeLocal<Function> CompileFunctionForCJSLoader(
1448+
Environment* env,
1449+
Local<Context> context,
1450+
Local<String> code,
1451+
Local<String> filename,
1452+
bool* cache_rejected,
1453+
bool is_cjs_scope,
1454+
ScriptCompiler::CachedData* cached_data) {
14501455
Isolate* isolate = context->GetIsolate();
14511456
EscapableHandleScope scope(isolate);
14521457

@@ -1464,20 +1469,7 @@ static MaybeLocal<Function> CompileFunctionForCJSLoader(Environment* env,
14641469
false, // is WASM
14651470
false, // is ES Module
14661471
hdo);
1467-
ScriptCompiler::CachedData* cached_data = nullptr;
14681472

1469-
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
1470-
if (sea::IsSingleExecutable()) {
1471-
sea::SeaResource sea = sea::FindSingleExecutableResource();
1472-
if (sea.use_code_cache()) {
1473-
std::string_view data = sea.code_cache.value();
1474-
cached_data = new ScriptCompiler::CachedData(
1475-
reinterpret_cast<const uint8_t*>(data.data()),
1476-
static_cast<int>(data.size()),
1477-
v8::ScriptCompiler::CachedData::BufferNotOwned);
1478-
}
1479-
}
1480-
#endif
14811473
ScriptCompiler::Source source(code, origin, cached_data);
14821474
ScriptCompiler::CompileOptions options;
14831475
if (cached_data == nullptr) {
@@ -1532,6 +1524,7 @@ static void CompileFunctionForCJSLoader(
15321524
CHECK(args[3]->IsBoolean());
15331525
Local<String> code = args[0].As<String>();
15341526
Local<String> filename = args[1].As<String>();
1527+
bool is_sea_main = args[2].As<Boolean>()->Value();
15351528
bool should_detect_module = args[3].As<Boolean>()->Value();
15361529

15371530
Isolate* isolate = args.GetIsolate();
@@ -1544,11 +1537,31 @@ static void CompileFunctionForCJSLoader(
15441537
Local<Value> cjs_exception;
15451538
Local<Message> cjs_message;
15461539

1540+
ScriptCompiler::CachedData* cached_data = nullptr;
1541+
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
1542+
if (is_sea_main) {
1543+
sea::SeaResource sea = sea::FindSingleExecutableResource();
1544+
// Use the "main" field in SEA config for the filename.
1545+
Local<Value> filename_from_sea;
1546+
if (!ToV8Value(context, sea.code_path).ToLocal(&filename_from_sea)) {
1547+
return;
1548+
}
1549+
filename = filename_from_sea.As<String>();
1550+
if (sea.use_code_cache()) {
1551+
std::string_view data = sea.code_cache.value();
1552+
cached_data = new ScriptCompiler::CachedData(
1553+
reinterpret_cast<const uint8_t*>(data.data()),
1554+
static_cast<int>(data.size()),
1555+
v8::ScriptCompiler::CachedData::BufferNotOwned);
1556+
}
1557+
}
1558+
#endif
1559+
15471560
{
15481561
ShouldNotAbortOnUncaughtScope no_abort_scope(realm->env());
15491562
TryCatchScope try_catch(env);
15501563
if (!CompileFunctionForCJSLoader(
1551-
env, context, code, filename, &cache_rejected, true)
1564+
env, context, code, filename, &cache_rejected, true, cached_data)
15521565
.ToLocal(&fn)) {
15531566
CHECK(try_catch.HasCaught());
15541567
CHECK(!try_catch.HasTerminated());
@@ -1703,7 +1716,7 @@ static void ContainsModuleSyntax(const FunctionCallbackInfo<Value>& args) {
17031716
TryCatchScope try_catch(env);
17041717
ShouldNotAbortOnUncaughtScope no_abort_scope(env);
17051718
if (CompileFunctionForCJSLoader(
1706-
env, context, code, filename, &cache_rejected, cjs_var)
1719+
env, context, code, filename, &cache_rejected, cjs_var, nullptr)
17071720
.ToLocal(&fn)) {
17081721
args.GetReturnValue().Set(false);
17091722
return;
Original file line numberDiff line numberDiff line change
@@ -103,20 +103,7 @@ ExitCode NodeMainInstance::Run() {
103103

104104
void NodeMainInstance::Run(ExitCode* exit_code, Environment* env) {
105105
if (*exit_code == ExitCode::kNoFailure) {
106-
bool runs_sea_code = false;
107-
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
108-
if (sea::IsSingleExecutable()) {
109-
sea::SeaResource sea = sea::FindSingleExecutableResource();
110-
if (!sea.use_snapshot()) {
111-
runs_sea_code = true;
112-
std::string_view code = sea.main_code_or_snapshot;
113-
LoadEnvironment(env, code);
114-
}
115-
}
116-
#endif
117-
// Either there is already a snapshot main function from SEA, or it's not
118-
// a SEA at all.
119-
if (!runs_sea_code) {
106+
if (!sea::MaybeLoadSingleExecutableApplication(env)) {
120107
LoadEnvironment(env, StartExecutionCallback{});
121108
}
122109

0 commit comments

Comments
 (0)