Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit fb2db5f

Browse filesBrowse 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
Copy full SHA for fb2db5f

10 files changed

+320-76Lines changed: 320 additions & 76 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎lib/internal/main/embedding.js‎

Copy file name to clipboardExpand all lines: lib/internal/main/embedding.js
+8-53Lines 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.
Collapse file

‎lib/internal/modules/esm/create_dynamic_module.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/esm/create_dynamic_module.js
+4-2Lines 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} */
Collapse file

‎lib/internal/modules/esm/loader.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/esm/loader.js
+3-2Lines 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');
Collapse file

‎lib/internal/modules/esm/translators.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/esm/translators.js
+4-2Lines 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

Collapse file

‎lib/internal/modules/esm/utils.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/esm/utils.js
+66-7Lines 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
};
Collapse file

‎lib/internal/modules/helpers.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/helpers.js
+39Lines 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,
Collapse file

‎src/env_properties.h‎

Copy file name to clipboardExpand all lines: src/env_properties.h
+1Lines 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") \
Collapse file

‎src/module_wrap.cc‎

Copy file name to clipboardExpand all lines: src/module_wrap.cc
+2-1Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.