Skip to content

Commit fb2db5f

Browse files
joyeecheungaduh95
authored andcommitted
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 4f2f800 commit fb2db5f

File tree

10 files changed

+320
-76
lines changed

10 files changed

+320
-76
lines changed

lib/internal/main/embedding.js

Lines changed: 8 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 { compileSourceTextModule, SourceTextModuleTypes: { kEmbedder } } = 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
}
@@ -65,6 +60,7 @@ function embedderRunCjs(content, filename) {
6560
filename,
6661
isLoadingSea, // is_sea_main
6762
false, // should_detect_module, ESM should be supported differently for embedded code
63+
true, // is_embedder
6864
);
6965
// Cache the source map for the module if present.
7066
if (sourceMapURL) {
@@ -103,28 +99,8 @@ function embedderRunCjs(content, filename) {
10399
);
104100
}
105101

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-
120102
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);
103+
return loadBuiltinModuleForEmbedder(id).exports;
128104
}
129105

130106
function embedderRunESM(content, filename) {
@@ -134,31 +110,10 @@ function embedderRunESM(content, filename) {
134110
} else {
135111
resourceName = filename;
136112
}
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);
113+
// TODO(joyeecheung): allow configuration from node::ModuleData,
114+
// either via a more generic context object, or something like import.meta extensions.
115+
const context = { isMain: true, __proto__: null };
116+
const wrap = compileSourceTextModule(resourceName, content, kEmbedder, context);
162117

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

lib/internal/modules/esm/create_dynamic_module.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ ${ArrayPrototypeJoin(ArrayPrototypeMap(imports, createImport), '\n')}
5858
${ArrayPrototypeJoin(ArrayPrototypeMap(exports, createExport), '\n')}
5959
import.meta.done();
6060
`;
61-
const { registerModule, compileSourceTextModule } = require('internal/modules/esm/utils');
62-
const m = compileSourceTextModule(`${url}`, source);
61+
const {
62+
registerModule, compileSourceTextModule, SourceTextModuleTypes: { kFacade },
63+
} = require('internal/modules/esm/utils');
64+
const m = compileSourceTextModule(`${url}`, source, kFacade);
6365

6466
const readyfns = new SafeSet();
6567
/** @type {DynamicModuleReflect} */

lib/internal/modules/esm/loader.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const { isURL, pathToFileURL } = require('internal/url');
3434
const { kEmptyObject } = require('internal/util');
3535
const {
3636
compileSourceTextModule,
37+
SourceTextModuleTypes: { kUser },
3738
getDefaultConditions,
3839
shouldSpawnLoaderHookWorker,
3940
requestTypes: { kImportInRequiredESM, kRequireInImportedCJS, kImportInImportedESM },
@@ -244,7 +245,7 @@ class ModuleLoader {
244245
* @returns {object} The module wrap object.
245246
*/
246247
createModuleWrap(source, url, context = kEmptyObject) {
247-
return compileSourceTextModule(url, source, this, context);
248+
return compileSourceTextModule(url, source, kUser, context);
248249
}
249250

250251
/**
@@ -371,7 +372,7 @@ class ModuleLoader {
371372
// TODO(joyeecheung): refactor this so that we pre-parse in C++ and hit the
372373
// cache here, or use a carrier object to carry the compiled module script
373374
// into the constructor to ensure cache hit.
374-
const wrap = compileSourceTextModule(url, source, this);
375+
const wrap = compileSourceTextModule(url, source, kUser);
375376
const inspectBrk = (isMain && getOptionValue('--inspect-brk'));
376377

377378
const { ModuleJobSync } = require('internal/modules/esm/module_job');

lib/internal/modules/esm/translators.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,11 @@ translators.set('module', function moduleStrategy(url, translateContext, parentU
9393
assertBufferSource(source, true, 'load');
9494
source = stringify(source);
9595
debug(`Translating StandardModule ${url}`, translateContext);
96-
const { compileSourceTextModule } = require('internal/modules/esm/utils');
96+
const {
97+
compileSourceTextModule, SourceTextModuleTypes: { kUser },
98+
} = require('internal/modules/esm/utils');
9799
const context = isMain ? { isMain } : undefined;
98-
const module = compileSourceTextModule(url, source, this, context);
100+
const module = compileSourceTextModule(url, source, kUser, context);
99101
return module;
100102
});
101103

lib/internal/modules/esm/utils.js

Lines changed: 66 additions & 7 deletions
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;
@@ -226,6 +228,28 @@ function defaultImportModuleDynamicallyForScript(specifier, phase, attributes, r
226228
return cascadedLoader.import(specifier, parentURL, attributes, phase);
227229
}
228230

231+
/**
232+
* Loads the built-in and wraps it in a ModuleWrap for embedder ESM.
233+
* @param {string} specifier
234+
* @returns {ModuleWrap}
235+
*/
236+
function getBuiltinModuleWrapForEmbedder(specifier) {
237+
return loadBuiltinModuleForEmbedder(specifier).getESMFacade();
238+
}
239+
240+
/**
241+
* Get the built-in module dynamically for embedder ESM.
242+
* @param {string} specifier - The module specifier string.
243+
* @param {number} phase - The module import phase. Ignored for now.
244+
* @param {Record<string, string>} attributes - The import attributes object. Ignored for now.
245+
* @param {string|null|undefined} referrerName - name of the referrer.
246+
* @returns {import('internal/modules/esm/loader.js').ModuleExports} - The imported module object.
247+
*/
248+
function importModuleDynamicallyForEmbedder(specifier, phase, attributes, referrerName) {
249+
// Ignore phase and attributes for embedder ESM for now, because this only supports loading builtins.
250+
return getBuiltinModuleWrapForEmbedder(specifier).getNamespace();
251+
}
252+
229253
/**
230254
* Asynchronously imports a module dynamically using a callback function. The native callback.
231255
* @param {symbol} referrerSymbol - Referrer symbol of the registered script, function, module, or contextified object.
@@ -253,6 +277,10 @@ async function importModuleDynamicallyCallback(referrerSymbol, specifier, phase,
253277
if (referrerSymbol === source_text_module_default_hdo) {
254278
return defaultImportModuleDynamicallyForModule(specifier, phase, attributes, referrerName);
255279
}
280+
// For embedder entry point ESM, only allow built-in modules.
281+
if (referrerSymbol === embedder_module_hdo) {
282+
return importModuleDynamicallyForEmbedder(specifier, phase, attributes, referrerName);
283+
}
256284

257285
if (moduleRegistries.has(referrerSymbol)) {
258286
const { importModuleDynamically, callbackReferrer } = moduleRegistries.get(referrerSymbol);
@@ -290,21 +318,42 @@ function shouldSpawnLoaderHookWorker() {
290318
return _shouldSpawnLoaderHookWorker;
291319
}
292320

321+
const SourceTextModuleTypes = {
322+
kInternal: 'internal', // TODO(joyeecheung): support internal ESM.
323+
kEmbedder: 'embedder', // Embedder ESM, also used by SEA
324+
kUser: 'user', // User-land ESM
325+
kFacade: 'facade', // Currently only used by the facade that proxies WASM module import/exports.
326+
};
327+
293328
/**
294329
* Compile a SourceTextModule for the built-in ESM loader. Register it for default
295330
* source map and import.meta and dynamic import() handling if cascadedLoader is provided.
296331
* @param {string} url URL of the module.
297332
* @param {string} source Source code of the module.
298-
* @param {typeof import('./loader.js').ModuleLoader|undefined} cascadedLoader If provided,
299-
* register the module for default handling.
333+
* @param {string} type Type of the source text module, one of SourceTextModuleTypes.
300334
* @param {{ isMain?: boolean }|undefined} context - context object containing module metadata.
301335
* @returns {ModuleWrap}
302336
*/
303-
function compileSourceTextModule(url, source, cascadedLoader, context = kEmptyObject) {
304-
const hostDefinedOption = cascadedLoader ? source_text_module_default_hdo : undefined;
305-
const wrap = new ModuleWrap(url, undefined, source, 0, 0, hostDefinedOption);
337+
function compileSourceTextModule(url, source, type, context = kEmptyObject) {
338+
let hostDefinedOptions;
339+
switch (type) {
340+
case SourceTextModuleTypes.kFacade:
341+
case SourceTextModuleTypes.kInternal:
342+
hostDefinedOptions = undefined;
343+
break;
344+
case SourceTextModuleTypes.kEmbedder:
345+
hostDefinedOptions = embedder_module_hdo;
346+
break;
347+
case SourceTextModuleTypes.kUser:
348+
hostDefinedOptions = source_text_module_default_hdo;
349+
break;
350+
default:
351+
assert.fail(`Unknown SourceTextModule type: ${type}`);
352+
}
353+
354+
const wrap = new ModuleWrap(url, undefined, source, 0, 0, hostDefinedOptions);
306355

307-
if (!cascadedLoader) {
356+
if (type === SourceTextModuleTypes.kFacade) {
308357
return wrap;
309358
}
310359

@@ -317,10 +366,18 @@ function compileSourceTextModule(url, source, cascadedLoader, context = kEmptyOb
317366
if (wrap.sourceMapURL) {
318367
maybeCacheSourceMap(url, source, wrap, false, wrap.sourceURL, wrap.sourceMapURL);
319368
}
369+
370+
if (type === SourceTextModuleTypes.kEmbedder) {
371+
// For embedder ESM, we also handle the linking and evaluation.
372+
const requests = wrap.getModuleRequests();
373+
const modules = requests.map(({ specifier }) => getBuiltinModuleWrapForEmbedder(specifier));
374+
wrap.link(modules);
375+
wrap.instantiate();
376+
wrap.evaluate(-1, false);
377+
}
320378
return wrap;
321379
}
322380

323-
324381
const kImportInImportedESM = Symbol('kImportInImportedESM');
325382
const kImportInRequiredESM = Symbol('kImportInRequiredESM');
326383
const kRequireInImportedCJS = Symbol('kRequireInImportedCJS');
@@ -331,11 +388,13 @@ const kRequireInImportedCJS = Symbol('kRequireInImportedCJS');
331388
const requestTypes = { kImportInImportedESM, kImportInRequiredESM, kRequireInImportedCJS };
332389

333390
module.exports = {
391+
embedder_module_hdo,
334392
registerModule,
335393
initializeESM,
336394
getDefaultConditions,
337395
getConditionsSet,
338396
shouldSpawnLoaderHookWorker,
339397
compileSourceTextModule,
398+
SourceTextModuleTypes,
340399
requestTypes,
341400
};

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

@@ -28,6 +29,7 @@ const assert = require('internal/assert');
2829
const { getOptionValue } = require('internal/options');
2930
const { setOwnProperty, getLazy } = require('internal/util');
3031
const { inspect } = require('internal/util/inspect');
32+
const { emitWarningSync } = require('internal/process/warning');
3133

3234
const lazyTmpdir = getLazy(() => require('os').tmpdir());
3335
const { join } = path;
@@ -126,6 +128,42 @@ function loadBuiltinModule(id) {
126128
return mod;
127129
}
128130

131+
let isSEABuiltinWarningNeeded_;
132+
function isSEABuiltinWarningNeeded() {
133+
if (isSEABuiltinWarningNeeded_ === undefined) {
134+
const { isExperimentalSeaWarningNeeded, isSea } = internalBinding('sea');
135+
isSEABuiltinWarningNeeded_ = isSea() && isExperimentalSeaWarningNeeded();
136+
}
137+
return isSEABuiltinWarningNeeded_;
138+
}
139+
140+
let warnedAboutBuiltins = false;
141+
/**
142+
* Helpers to load built-in modules for embedder modules.
143+
* @param {string} id
144+
* @returns {import('internal/bootstrap/realm.js').BuiltinModule}
145+
*/
146+
function loadBuiltinModuleForEmbedder(id) {
147+
const normalized = BuiltinModule.normalizeRequirableId(id);
148+
if (normalized) {
149+
const mod = loadBuiltinModule(normalized);
150+
if (mod) {
151+
return mod;
152+
}
153+
}
154+
if (isSEABuiltinWarningNeeded() && !warnedAboutBuiltins) {
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
/**
@@ -469,6 +507,7 @@ module.exports = {
469507
getCjsConditionsArray,
470508
getCompileCacheDir,
471509
initializeCjsConditions,
510+
loadBuiltinModuleForEmbedder,
472511
loadBuiltinModule,
473512
makeRequireFunction,
474513
normalizeReferrerURL,

src/env_properties.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
V(resource_symbol, "resource_symbol") \
5959
V(trigger_async_id_symbol, "trigger_async_id_symbol") \
6060
V(builtin_source_text_module_hdo, "builtin_source_text_module_hdo") \
61+
V(embedder_module_hdo, "embedder_module_hdo") \
6162
V(source_text_module_default_hdo, "source_text_module_default_hdo") \
6263
V(vm_context_no_contextify, "vm_context_no_contextify") \
6364
V(vm_dynamic_import_default_internal, "vm_dynamic_import_default_internal") \

src/module_wrap.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1397,7 +1397,8 @@ void ModuleWrap::HostInitializeImportMetaObjectCallback(
13971397

13981398
// Use the default initializer for source text modules without custom
13991399
// callbacks.
1400-
if (id == env->source_text_module_default_hdo()) {
1400+
if (id == env->source_text_module_default_hdo() ||
1401+
id == env->embedder_module_hdo()) {
14011402
USE(DefaultImportMetaObjectInitializer(realm, wrap, meta));
14021403
return;
14031404
}

0 commit comments

Comments
 (0)