Skip to content

Commit 439b04c

Browse files
committed
src: support import() and import.meta in embedder-run modules
This adds a embedder_module_hdo for identifying embedder-run modules in the dynamic import handler and import.meta initializer, and a SourceTextModuleTypes for customizing source text module compilation in the JS land via compileSourceTextModule(). Also, refactors the existing embedder module compilation code to reuse the builtin resolution logic. PR-URL: #61654 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Aditi Singh <aditisingh1400@gmail.com>
1 parent 3d9c36a commit 439b04c

File tree

5 files changed

+91
-57
lines changed

5 files changed

+91
-57
lines changed

doc/api/single-executable-applications.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ a non-container Linux arm64 environment to work around this issue.
495495

496496
The process documented here is subject to change.
497497

498-
#### 1. Generating single executable preparation blobs
498+
#### Generating single executable preparation blobs
499499

500500
To build a single executable application, Node.js would first generate a blob
501501
that contains all the necessary information to run the bundled script.
@@ -522,7 +522,7 @@ the final executable.
522522
}
523523
```
524524

525-
#### 2. Injecting the preparation blob into the `node` binary
525+
#### Injecting the preparation blob into the `node` binary
526526

527527
To complete the creation of a single executable application, the generated blob
528528
needs to be injected into a copy of the `node` binary, as documented below.
@@ -641,7 +641,7 @@ to help us document them.
641641
[CommonJS]: modules.md#modules-commonjs-modules
642642
[ECMAScript Modules]: esm.md#modules-ecmascript-modules
643643
[ELF]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
644-
[Generating single executable preparation blobs]: #1-generating-single-executable-preparation-blobs
644+
[Generating single executable preparation blobs]: #generating-single-executable-preparation-blobs
645645
[Mach-O]: https://en.wikipedia.org/wiki/Mach-O
646646
[PE]: https://en.wikipedia.org/wiki/Portable_Executable
647647
[Windows SDK]: https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/

lib/internal/main/embedding.js

Lines changed: 7 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,12 @@ const {
1515
const { isExperimentalSeaWarningNeeded, isSea } = internalBinding('sea');
1616
const { emitExperimentalWarning } = require('internal/util');
1717
const { emitWarningSync } = require('internal/process/warning');
18-
const { BuiltinModule } = require('internal/bootstrap/realm');
19-
const { normalizeRequirableId } = BuiltinModule;
2018
const { Module } = require('internal/modules/cjs/loader');
2119
const { compileFunctionForCJSLoader } = internalBinding('contextify');
2220
const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
23-
const { codes: {
24-
ERR_UNKNOWN_BUILTIN_MODULE,
25-
} } = require('internal/errors');
2621
const { pathToFileURL } = require('internal/url');
27-
const { loadBuiltinModule } = require('internal/modules/helpers');
22+
const { loadBuiltinModuleForEmbedder } = require('internal/modules/helpers');
23+
const { compileSourceTextModuleForEmbedder } = require('internal/modules/esm/utils');
2824
const { moduleFormats } = internalBinding('modules');
2925
const assert = require('internal/assert');
3026
const path = require('path');
@@ -34,7 +30,6 @@ const path = require('path');
3430
prepareMainThreadExecution(false, true);
3531

3632
const isLoadingSea = isSea();
37-
const isBuiltinWarningNeeded = isLoadingSea && isExperimentalSeaWarningNeeded();
3833
if (isExperimentalSeaWarningNeeded()) {
3934
emitExperimentalWarning('Single executable application');
4035
}
@@ -103,28 +98,8 @@ function embedderRunCjs(content, filename) {
10398
);
10499
}
105100

106-
let warnedAboutBuiltins = false;
107-
function warnNonBuiltinInSEA() {
108-
if (isBuiltinWarningNeeded && !warnedAboutBuiltins) {
109-
emitWarningSync(
110-
'Currently the require() provided to the main script embedded into ' +
111-
'single-executable applications only supports loading built-in modules.\n' +
112-
'To load a module from disk after the single executable application is ' +
113-
'launched, use require("module").createRequire().\n' +
114-
'Support for bundled module loading or virtual file systems are under ' +
115-
'discussions in https://github.com/nodejs/single-executable');
116-
warnedAboutBuiltins = true;
117-
}
118-
}
119-
120101
function embedderRequire(id) {
121-
const normalizedId = normalizeRequirableId(id);
122-
123-
if (!normalizedId) {
124-
warnNonBuiltinInSEA();
125-
throw new ERR_UNKNOWN_BUILTIN_MODULE(id);
126-
}
127-
return require(normalizedId);
102+
return loadBuiltinModuleForEmbedder(id).exports;
128103
}
129104

130105
function embedderRunESM(content, filename) {
@@ -134,31 +109,10 @@ function embedderRunESM(content, filename) {
134109
} else {
135110
resourceName = filename;
136111
}
137-
const { compileSourceTextModule } = require('internal/modules/esm/utils');
138-
// TODO(joyeecheung): support code cache, dynamic import() and import.meta.
139-
const wrap = compileSourceTextModule(resourceName, content);
140-
// Cache the source map for the module if present.
141-
if (wrap.sourceMapURL) {
142-
maybeCacheSourceMap(resourceName, content, wrap, false, undefined, wrap.sourceMapURL);
143-
}
144-
const requests = wrap.getModuleRequests();
145-
const modules = [];
146-
for (let i = 0; i < requests.length; ++i) {
147-
const { specifier } = requests[i];
148-
const normalizedId = normalizeRequirableId(specifier);
149-
if (!normalizedId) {
150-
warnNonBuiltinInSEA();
151-
throw new ERR_UNKNOWN_BUILTIN_MODULE(specifier);
152-
}
153-
const mod = loadBuiltinModule(normalizedId);
154-
if (!mod) {
155-
throw new ERR_UNKNOWN_BUILTIN_MODULE(specifier);
156-
}
157-
modules.push(mod.getESMFacade());
158-
}
159-
wrap.link(modules);
160-
wrap.instantiate();
161-
wrap.evaluate(-1, false);
112+
// TODO(joyeecheung): allow configuration from node::ModuleData,
113+
// either via a more generic context object, or something like import.meta extensions.
114+
const context = { isMain: true, __proto__: null };
115+
const wrap = compileSourceTextModuleForEmbedder(resourceName, content, context);
162116

163117
// TODO(joyeecheung): we may want to return the v8::Module via a vm.SourceTextModule
164118
// when vm.SourceTextModule stablizes, or put it in an out parameter.

lib/internal/modules/esm/utils.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const {
1414
},
1515
} = internalBinding('util');
1616
const {
17+
embedder_module_hdo,
1718
source_text_module_default_hdo,
1819
vm_dynamic_import_default_internal,
1920
vm_dynamic_import_main_context_default,
@@ -43,6 +44,7 @@ const {
4344
const assert = require('internal/assert');
4445
const {
4546
normalizeReferrerURL,
47+
loadBuiltinModuleForEmbedder,
4648
} = require('internal/modules/helpers');
4749

4850
let defaultConditions;
@@ -200,7 +202,8 @@ function defaultInitializeImportMetaForModule(meta, wrap) {
200202
* @param {ModuleWrap} wrap - The ModuleWrap of the SourceTextModule where `import.meta` is referenced.
201203
*/
202204
function initializeImportMetaObject(symbol, meta, wrap) {
203-
if (symbol === source_text_module_default_hdo) {
205+
if (symbol === source_text_module_default_hdo ||
206+
symbol === embedder_module_hdo) {
204207
defaultInitializeImportMetaForModule(meta, wrap);
205208
return;
206209
}
@@ -266,6 +269,10 @@ async function importModuleDynamicallyCallback(referrerSymbol, specifier, phase,
266269
if (referrerSymbol === source_text_module_default_hdo) {
267270
return defaultImportModuleDynamicallyForModule(specifier, phase, attributes, referrerName);
268271
}
272+
// For embedder entry point ESM, only allow built-in modules.
273+
if (referrerSymbol === embedder_module_hdo) {
274+
return loadBuiltinModuleForEmbedder(specifier).getESMFacade().getNamespace();
275+
}
269276

270277
if (moduleRegistries.has(referrerSymbol)) {
271278
const { importModuleDynamically, callbackReferrer } = moduleRegistries.get(referrerSymbol);
@@ -334,6 +341,37 @@ function compileSourceTextModule(url, source, cascadedLoader, context = kEmptyOb
334341
}
335342

336343

344+
/**
345+
* Compile, link, instantiate and evaluate a SourceTextModule for embedder ESM entry point.
346+
* This resolves only built-in modules and uses the embedder_module_hdo for import.meta
347+
* and dynamic import() support.
348+
* @param {string} url URL of the module.
349+
* @param {string} source Source code of the module.
350+
* @param {{ isMain?: boolean }|undefined} context - context object containing module metadata.
351+
* @returns {ModuleWrap}
352+
*/
353+
function compileSourceTextModuleForEmbedder(url, source, context = kEmptyObject) {
354+
const wrap = new ModuleWrap(url, undefined, source, 0, 0, embedder_module_hdo);
355+
356+
const { isMain } = context;
357+
if (isMain) {
358+
wrap.isMain = true;
359+
}
360+
361+
// Cache the source map for the module if present.
362+
if (wrap.sourceMapURL) {
363+
maybeCacheSourceMap(url, source, wrap, false, wrap.sourceURL, wrap.sourceMapURL);
364+
}
365+
366+
// For embedder ESM, handle linking and evaluation.
367+
const requests = wrap.getModuleRequests();
368+
const modules = requests.map(({ specifier }) => loadBuiltinModuleForEmbedder(specifier).getESMFacade());
369+
wrap.link(modules);
370+
wrap.instantiate();
371+
wrap.evaluate(-1, false);
372+
return wrap;
373+
}
374+
337375
const kImportInImportedESM = Symbol('kImportInImportedESM');
338376
const kImportInRequiredESM = Symbol('kImportInRequiredESM');
339377
const kRequireInImportedCJS = Symbol('kRequireInImportedCJS');
@@ -344,11 +382,13 @@ const kRequireInImportedCJS = Symbol('kRequireInImportedCJS');
344382
const requestTypes = { kImportInImportedESM, kImportInRequiredESM, kRequireInImportedCJS };
345383

346384
module.exports = {
385+
embedder_module_hdo,
347386
registerModule,
348387
initializeESM,
349388
getDefaultConditions,
350389
getConditionsSet,
351390
shouldSpawnLoaderHookWorker,
352391
compileSourceTextModule,
392+
compileSourceTextModuleForEmbedder,
353393
requestTypes,
354394
};

lib/internal/modules/helpers.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const {
1515
const {
1616
ERR_INVALID_ARG_TYPE,
1717
ERR_INVALID_RETURN_PROPERTY_VALUE,
18+
ERR_UNKNOWN_BUILTIN_MODULE,
1819
} = require('internal/errors').codes;
1920
const { BuiltinModule } = require('internal/bootstrap/realm');
2021

@@ -126,6 +127,43 @@ function loadBuiltinModule(id) {
126127
return mod;
127128
}
128129

130+
let isSEABuiltinWarningNeeded_;
131+
function isSEABuiltinWarningNeeded() {
132+
if (isSEABuiltinWarningNeeded_ === undefined) {
133+
const { isExperimentalSeaWarningNeeded, isSea } = internalBinding('sea');
134+
isSEABuiltinWarningNeeded_ = isSea() && isExperimentalSeaWarningNeeded();
135+
}
136+
return isSEABuiltinWarningNeeded_;
137+
}
138+
139+
let warnedAboutBuiltins = false;
140+
/**
141+
* Load a built-in module for embedder/SEA modules.
142+
* @param {string} id
143+
* @returns {import('internal/bootstrap/realm.js').BuiltinModule}
144+
*/
145+
function loadBuiltinModuleForEmbedder(id) {
146+
const normalized = BuiltinModule.normalizeRequirableId(id);
147+
if (normalized) {
148+
const mod = loadBuiltinModule(normalized);
149+
if (mod) {
150+
return mod;
151+
}
152+
}
153+
if (isSEABuiltinWarningNeeded() && !warnedAboutBuiltins) {
154+
const { emitWarningSync } = require('internal/process/warning');
155+
emitWarningSync(
156+
'Currently the require() provided to the main script embedded into ' +
157+
'single-executable applications only supports loading built-in modules.\n' +
158+
'To load a module from disk after the single executable application is ' +
159+
'launched, use require("module").createRequire().\n' +
160+
'Support for bundled module loading or virtual file systems are under ' +
161+
'discussions in https://github.com/nodejs/single-executable');
162+
warnedAboutBuiltins = true;
163+
}
164+
throw new ERR_UNKNOWN_BUILTIN_MODULE(id);
165+
}
166+
129167
/** @type {Module} */
130168
let $Module = null;
131169
/**
@@ -470,6 +508,7 @@ module.exports = {
470508
getCompileCacheDir,
471509
initializeCjsConditions,
472510
loadBuiltinModule,
511+
loadBuiltinModuleForEmbedder,
473512
makeRequireFunction,
474513
normalizeReferrerURL,
475514
stringify,

src/env_properties.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
V(onpskexchange_symbol, "onpskexchange") \
5858
V(resource_symbol, "resource_symbol") \
5959
V(trigger_async_id_symbol, "trigger_async_id_symbol") \
60+
V(embedder_module_hdo, "embedder_module_hdo") \
6061
V(source_text_module_default_hdo, "source_text_module_default_hdo") \
6162
V(vm_context_no_contextify, "vm_context_no_contextify") \
6263
V(vm_dynamic_import_default_internal, "vm_dynamic_import_default_internal") \

0 commit comments

Comments
 (0)