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 4c50826

Browse filesBrowse files
joyeecheungtargos
authored andcommitted
vm: support using the default loader to handle dynamic import()
This patch adds support for using `vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER` as `importModuleDynamically` in all APIs that take the option except `vm.SourceTextModule`. This allows users to have a shortcut to support dynamic import() in the compiled code without missing the compilation cache if they don't need customization of the loading process. We emit an experimental warning when the `import()` is actually handled by the default loader through this option instead of requiring `--experimental-vm-modules`. In addition this refactors the documentation for `importModuleDynamically` and adds a dedicated section for it with examples. `vm.SourceTextModule` is not supported in this patch because it needs additional refactoring to handle `initializeImportMeta`, which can be done in a follow-up. PR-URL: #51244 Fixes: #51154 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent af6b5b1 commit 4c50826
Copy full SHA for 4c50826

File tree

Expand file treeCollapse file tree

15 files changed

+596
-193
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

15 files changed

+596
-193
lines changed
Open diff view settings
Collapse file

‎doc/api/vm.md‎

Copy file name to clipboardExpand all lines: doc/api/vm.md
+310-106Lines changed: 310 additions & 106 deletions
  • Display the source diff
  • Display the rich diff
Large diffs are not rendered by default.
Collapse file

‎lib/internal/bootstrap/switches/is_main_thread.js‎

Copy file name to clipboardExpand all lines: lib/internal/bootstrap/switches/is_main_thread.js
-1Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,6 @@ require('url'); // eslint-disable-line no-restricted-modules
294294
internalBinding('module_wrap');
295295
require('internal/modules/cjs/loader');
296296
require('internal/modules/esm/utils');
297-
require('internal/vm/module');
298297

299298
// Needed to refresh the time origin.
300299
require('internal/perf/utils');
Collapse file

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

Copy file name to clipboardExpand all lines: lib/internal/modules/cjs/loader.js
+5-13Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ const {
5252
SafeMap,
5353
SafeWeakMap,
5454
String,
55-
Symbol,
5655
StringPrototypeCharAt,
5756
StringPrototypeCharCodeAt,
5857
StringPrototypeEndsWith,
@@ -114,7 +113,6 @@ const {
114113
initializeCjsConditions,
115114
loadBuiltinModule,
116115
makeRequireFunction,
117-
normalizeReferrerURL,
118116
stripBOM,
119117
toRealPath,
120118
} = require('internal/modules/helpers');
@@ -125,12 +123,10 @@ const policy = getLazy(
125123
);
126124
const shouldReportRequiredModules = getLazy(() => process.env.WATCH_REPORT_DEPENDENCIES);
127125

128-
const getCascadedLoader = getLazy(
129-
() => require('internal/process/esm_loader').esmLoader,
130-
);
131-
132126
const permission = require('internal/process/permission');
133-
127+
const {
128+
vm_dynamic_import_default_internal,
129+
} = internalBinding('symbols');
134130
// Whether any user-provided CJS modules had been loaded (executed).
135131
// Used for internal assertions.
136132
let hasLoadedAnyUserCJSModule = false;
@@ -1257,12 +1253,8 @@ let hasPausedEntry = false;
12571253
* @param {object} codeCache The SEA code cache
12581254
*/
12591255
function wrapSafe(filename, content, cjsModuleInstance, codeCache) {
1260-
const hostDefinedOptionId = Symbol(`cjs:${filename}`);
1261-
async function importModuleDynamically(specifier, _, importAttributes) {
1262-
const cascadedLoader = getCascadedLoader();
1263-
return cascadedLoader.import(specifier, normalizeReferrerURL(filename),
1264-
importAttributes);
1265-
}
1256+
const hostDefinedOptionId = vm_dynamic_import_default_internal;
1257+
const importModuleDynamically = vm_dynamic_import_default_internal;
12661258
if (patched) {
12671259
const wrapped = Module.wrap(content);
12681260
const script = makeContextifyScript(
Collapse file

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

Copy file name to clipboardExpand all lines: lib/internal/modules/esm/translators.js
+7-7Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ const {
1515
StringPrototypeReplaceAll,
1616
StringPrototypeSlice,
1717
StringPrototypeStartsWith,
18-
Symbol,
1918
SyntaxErrorPrototype,
2019
globalThis: { WebAssembly },
2120
} = primordials;
@@ -59,7 +58,9 @@ const { ModuleWrap } = moduleWrap;
5958
const asyncESM = require('internal/process/esm_loader');
6059
const { emitWarningSync } = require('internal/process/warning');
6160
const { internalCompileFunction } = require('internal/vm');
62-
61+
const {
62+
vm_dynamic_import_default_internal,
63+
} = internalBinding('symbols');
6364
// Lazy-loading to avoid circular dependencies.
6465
let getSourceSync;
6566
/**
@@ -206,9 +207,8 @@ function enrichCJSError(err, content, filename) {
206207
*/
207208
function loadCJSModule(module, source, url, filename) {
208209
let compiledWrapper;
209-
async function importModuleDynamically(specifier, _, importAttributes) {
210-
return asyncESM.esmLoader.import(specifier, url, importAttributes);
211-
}
210+
const hostDefinedOptionId = vm_dynamic_import_default_internal;
211+
const importModuleDynamically = vm_dynamic_import_default_internal;
212212
try {
213213
compiledWrapper = internalCompileFunction(
214214
source, // code,
@@ -226,8 +226,8 @@ function loadCJSModule(module, source, url, filename) {
226226
'__filename',
227227
'__dirname',
228228
],
229-
Symbol(`cjs:${filename}`), // hostDefinedOptionsId
230-
importModuleDynamically, // importModuleDynamically
229+
hostDefinedOptionId, // hostDefinedOptionsId
230+
importModuleDynamically, // importModuleDynamically
231231
).function;
232232
} catch (err) {
233233
enrichCJSError(err, source, filename);
Collapse file

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

Copy file name to clipboardExpand all lines: lib/internal/modules/esm/utils.js
+44-27Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ const {
44
ArrayIsArray,
55
SafeSet,
66
SafeWeakMap,
7-
Symbol,
87
ObjectFreeze,
98
} = primordials;
109

@@ -14,8 +13,10 @@ const {
1413
},
1514
} = internalBinding('util');
1615
const {
17-
default_host_defined_options,
16+
vm_dynamic_import_default_internal,
17+
vm_dynamic_import_main_context_default,
1818
vm_dynamic_import_missing_flag,
19+
vm_dynamic_import_no_callback,
1920
} = internalBinding('symbols');
2021

2122
const {
@@ -28,12 +29,19 @@ const {
2829
loadPreloadModules,
2930
initializeFrozenIntrinsics,
3031
} = require('internal/process/pre_execution');
31-
const { getCWDURL } = require('internal/util');
32+
const {
33+
emitExperimentalWarning,
34+
getCWDURL,
35+
getLazy,
36+
} = require('internal/util');
3237
const {
3338
setImportModuleDynamicallyCallback,
3439
setInitializeImportMetaObjectCallback,
3540
} = internalBinding('module_wrap');
3641
const assert = require('internal/assert');
42+
const {
43+
normalizeReferrerURL,
44+
} = require('internal/modules/helpers');
3745

3846
let defaultConditions;
3947
/**
@@ -145,8 +153,10 @@ const moduleRegistries = new SafeWeakMap();
145153
*/
146154
function registerModule(referrer, registry) {
147155
const idSymbol = referrer[host_defined_option_symbol];
148-
if (idSymbol === default_host_defined_options ||
149-
idSymbol === vm_dynamic_import_missing_flag) {
156+
if (idSymbol === vm_dynamic_import_no_callback ||
157+
idSymbol === vm_dynamic_import_missing_flag ||
158+
idSymbol === vm_dynamic_import_main_context_default ||
159+
idSymbol === vm_dynamic_import_default_internal) {
150160
// The referrer is compiled without custom callbacks, so there is
151161
// no registry to hold on to. We'll throw
152162
// ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING when a callback is
@@ -158,26 +168,6 @@ function registerModule(referrer, registry) {
158168
moduleRegistries.set(idSymbol, registry);
159169
}
160170

161-
/**
162-
* Registers the ModuleRegistry for dynamic import() calls with a realm
163-
* as the referrer. Similar to {@link registerModule}, but this function
164-
* generates a new id symbol instead of using the one from the referrer
165-
* object.
166-
* @param {globalThis} globalThis The globalThis object of the realm.
167-
* @param {ModuleRegistry} registry
168-
*/
169-
function registerRealm(globalThis, registry) {
170-
let idSymbol = globalThis[host_defined_option_symbol];
171-
// If the per-realm host-defined options is already registered, do nothing.
172-
if (idSymbol) {
173-
return;
174-
}
175-
// Otherwise, register the per-realm host-defined options.
176-
idSymbol = Symbol('Realm globalThis');
177-
globalThis[host_defined_option_symbol] = idSymbol;
178-
moduleRegistries.set(idSymbol, registry);
179-
}
180-
181171
/**
182172
* Defines the `import.meta` object for a given module.
183173
* @param {symbol} symbol - Reference to the module.
@@ -191,16 +181,44 @@ function initializeImportMetaObject(symbol, meta) {
191181
}
192182
}
193183
}
184+
const getCascadedLoader = getLazy(
185+
() => require('internal/process/esm_loader').esmLoader,
186+
);
187+
188+
/**
189+
* Proxy the dynamic import to the default loader.
190+
* @param {string} specifier - The module specifier string.
191+
* @param {Record<string, string>} attributes - The import attributes object.
192+
* @param {string|null|undefined} referrerName - name of the referrer.
193+
* @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} - The imported module object.
194+
*/
195+
function defaultImportModuleDynamically(specifier, attributes, referrerName) {
196+
const parentURL = normalizeReferrerURL(referrerName);
197+
return getCascadedLoader().import(specifier, parentURL, attributes);
198+
}
194199

195200
/**
196201
* Asynchronously imports a module dynamically using a callback function. The native callback.
197202
* @param {symbol} referrerSymbol - Referrer symbol of the registered script, function, module, or contextified object.
198203
* @param {string} specifier - The module specifier string.
199204
* @param {Record<string, string>} attributes - The import attributes object.
205+
* @param {string|null|undefined} referrerName - name of the referrer.
200206
* @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} - The imported module object.
201207
* @throws {ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING} - If the callback function is missing.
202208
*/
203-
async function importModuleDynamicallyCallback(referrerSymbol, specifier, attributes) {
209+
async function importModuleDynamicallyCallback(referrerSymbol, specifier, attributes, referrerName) {
210+
// For user-provided vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER, emit the warning
211+
// and fall back to the default loader.
212+
if (referrerSymbol === vm_dynamic_import_main_context_default) {
213+
emitExperimentalWarning('vm.USE_MAIN_CONTEXT_DEFAULT_LOADER');
214+
return defaultImportModuleDynamically(specifier, attributes, referrerName);
215+
}
216+
// For script compiled internally that should use the default loader to handle dynamic
217+
// import, proxy the request to the default loader without the warning.
218+
if (referrerSymbol === vm_dynamic_import_default_internal) {
219+
return defaultImportModuleDynamically(specifier, attributes, referrerName);
220+
}
221+
204222
if (moduleRegistries.has(referrerSymbol)) {
205223
const { importModuleDynamically, callbackReferrer } = moduleRegistries.get(referrerSymbol);
206224
if (importModuleDynamically !== undefined) {
@@ -273,7 +291,6 @@ async function initializeHooks() {
273291

274292
module.exports = {
275293
registerModule,
276-
registerRealm,
277294
initializeESM,
278295
initializeHooks,
279296
getDefaultConditions,
Collapse file

‎lib/internal/modules/helpers.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/helpers.js
+28-7Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,19 @@ const { validateString } = require('internal/validators');
2323
const fs = require('fs'); // Import all of `fs` so that it can be monkey-patched.
2424
const internalFS = require('internal/fs/utils');
2525
const path = require('path');
26-
const { pathToFileURL, fileURLToPath, URL } = require('internal/url');
26+
const { pathToFileURL, fileURLToPath } = require('internal/url');
27+
const assert = require('internal/assert');
2728

2829
const { getOptionValue } = require('internal/options');
2930
const { setOwnProperty } = require('internal/util');
31+
const { inspect } = require('internal/util/inspect');
3032

3133
const {
3234
privateSymbols: {
3335
require_private_symbol,
3436
},
3537
} = internalBinding('util');
38+
const { canParse: URLCanParse } = internalBinding('url');
3639

3740
let debug = require('internal/util/debuglog').debuglog('module', (fn) => {
3841
debug = fn;
@@ -288,14 +291,32 @@ function addBuiltinLibsToObject(object, dummyModuleName) {
288291
}
289292

290293
/**
291-
* If a referrer is an URL instance or absolute path, convert it into an URL string.
292-
* @param {string | URL} referrer
294+
* Normalize the referrer name as a URL.
295+
* If it's a string containing an absolute path or a URL it's normalized as
296+
* a URL string.
297+
* Otherwise it's returned as undefined.
298+
* @param {string | null | undefined} referrerName
299+
* @returns {string | undefined}
293300
*/
294-
function normalizeReferrerURL(referrer) {
295-
if (typeof referrer === 'string' && path.isAbsolute(referrer)) {
296-
return pathToFileURL(referrer).href;
301+
function normalizeReferrerURL(referrerName) {
302+
if (referrerName === null || referrerName === undefined) {
303+
return undefined;
297304
}
298-
return new URL(referrer).href;
305+
306+
if (typeof referrerName === 'string') {
307+
if (path.isAbsolute(referrerName)) {
308+
return pathToFileURL(referrerName).href;
309+
}
310+
311+
if (StringPrototypeStartsWith(referrerName, 'file://') ||
312+
URLCanParse(referrerName)) {
313+
return referrerName;
314+
}
315+
316+
return undefined;
317+
}
318+
319+
assert.fail('Unreachable code reached by ' + inspect(referrerName));
299320
}
300321

301322
module.exports = {
Collapse file

‎lib/internal/process/pre_execution.js‎

Copy file name to clipboardExpand all lines: lib/internal/process/pre_execution.js
+17-9Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,23 +67,31 @@ function prepareWorkerThreadExecution() {
6767
}
6868

6969
function prepareShadowRealmExecution() {
70-
const { registerRealm } = require('internal/modules/esm/utils');
7170
// Patch the process object with legacy properties and normalizations.
7271
// Do not expand argv1 as it is not available in ShadowRealm.
7372
patchProcessObject(false);
7473
setupDebugEnv();
7574

7675
// Disable custom loaders in ShadowRealm.
7776
setupUserModules(true);
78-
registerRealm(globalThis, {
79-
__proto__: null,
80-
importModuleDynamically: (specifier, _referrer, attributes) => {
81-
// The handler for `ShadowRealm.prototype.importValue`.
82-
const { esmLoader } = require('internal/process/esm_loader');
83-
// `parentURL` is not set in the case of a ShadowRealm top-level import.
84-
return esmLoader.import(specifier, undefined, attributes);
77+
const {
78+
privateSymbols: {
79+
host_defined_option_symbol,
8580
},
86-
});
81+
} = internalBinding('util');
82+
const {
83+
vm_dynamic_import_default_internal,
84+
} = internalBinding('symbols');
85+
86+
// For ShadowRealm.prototype.importValue(), the referrer name is
87+
// always null, so the native ImportModuleDynamically() callback would
88+
// always fallback to look up the host-defined option from the
89+
// global object using host_defined_option_symbol. Using
90+
// vm_dynamic_import_default_internal as the host-defined option
91+
// instructs the JS-land importModuleDynamicallyCallback() to
92+
// proxy the request to defaultImportModuleDynamically().
93+
globalThis[host_defined_option_symbol] =
94+
vm_dynamic_import_default_internal;
8795
}
8896

8997
function prepareExecution(options) {
Collapse file

‎lib/internal/source_map/source_map_cache.js‎

Copy file name to clipboardExpand all lines: lib/internal/source_map/source_map_cache.js
+3-5Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,10 @@ function extractSourceMapURLMagicComment(content) {
107107
function maybeCacheSourceMap(filename, content, cjsModuleInstance, isGeneratedSource, sourceURL, sourceMapURL) {
108108
const sourceMapsEnabled = getSourceMapsEnabled();
109109
if (!(process.env.NODE_V8_COVERAGE || sourceMapsEnabled)) return;
110-
try {
111-
const { normalizeReferrerURL } = require('internal/modules/helpers');
112-
filename = normalizeReferrerURL(filename);
113-
} catch (err) {
110+
const { normalizeReferrerURL } = require('internal/modules/helpers');
111+
filename = normalizeReferrerURL(filename);
112+
if (filename === undefined) {
114113
// This is most likely an invalid filename in sourceURL of [eval]-wrapper.
115-
debug(err);
116114
return;
117115
}
118116

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.