Skip to content

Commit 0211a3d

Browse files
joyeecheungrichardlau
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: #51244Fixes: #51154 Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]>
1 parent 9a68b47 commit 0211a3d

File tree

15 files changed

+595
-193
lines changed

15 files changed

+595
-193
lines changed

‎doc/api/vm.md‎

Lines changed: 309 additions & 106 deletions
Large diffs are not rendered by default.

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

Lines 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');

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

Lines 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
constshouldReportRequiredModules=getLazy(()=>process.env.WATCH_REPORT_DEPENDENCIES);
127125

128-
constgetCascadedLoader=getLazy(
129-
()=>require('internal/process/esm_loader').esmLoader,
130-
);
131-
132126
constpermission=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
lethasLoadedAnyUserCJSModule=false;
@@ -1258,12 +1254,8 @@ let hasPausedEntry = false;
12581254
* @param{object} codeCache The SEA code cache
12591255
*/
12601256
functionwrapSafe(filename,content,cjsModuleInstance,codeCache){
1261-
consthostDefinedOptionId=Symbol(`cjs:${filename}`);
1262-
asyncfunctionimportModuleDynamically(specifier,_,importAttributes){
1263-
constcascadedLoader=getCascadedLoader();
1264-
returncascadedLoader.import(specifier,normalizeReferrerURL(filename),
1265-
importAttributes);
1266-
}
1257+
consthostDefinedOptionId=vm_dynamic_import_default_internal;
1258+
constimportModuleDynamically=vm_dynamic_import_default_internal;
12671259
if(patched){
12681260
constwrapped=Module.wrap(content);
12691261
constscript=makeContextifyScript(

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

Lines 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
constasyncESM=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
letgetSourceSync;
6566
/**
@@ -206,9 +207,8 @@ function enrichCJSError(err, content, filename){
206207
*/
207208
functionloadCJSModule(module,source,url,filename){
208209
letcompiledWrapper;
209-
asyncfunctionimportModuleDynamically(specifier,_,importAttributes){
210-
returnasyncESM.esmLoader.import(specifier,url,importAttributes);
211-
}
210+
consthostDefinedOptionId=vm_dynamic_import_default_internal;
211+
constimportModuleDynamically=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);

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

Lines 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
constassert=require('internal/assert');
42+
const{
43+
normalizeReferrerURL,
44+
}=require('internal/modules/helpers');
3745

3846
letdefaultConditions;
3947
/**
@@ -145,8 +153,10 @@ const moduleRegistries = new SafeWeakMap();
145153
*/
146154
functionregisterModule(referrer,registry){
147155
constidSymbol=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-
functionregisterRealm(globalThis,registry){
170-
letidSymbol=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+
constgetCascadedLoader=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+
functiondefaultImportModuleDynamically(specifier,attributes,referrerName){
196+
constparentURL=normalizeReferrerURL(referrerName);
197+
returngetCascadedLoader().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-
asyncfunctionimportModuleDynamicallyCallback(referrerSymbol,specifier,attributes){
209+
asyncfunctionimportModuleDynamicallyCallback(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+
returndefaultImportModuleDynamically(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+
returndefaultImportModuleDynamically(specifier,attributes,referrerName);
220+
}
221+
204222
if(moduleRegistries.has(referrerSymbol)){
205223
const{ importModuleDynamically, callbackReferrer }=moduleRegistries.get(referrerSymbol);
206224
if(importModuleDynamically!==undefined){
@@ -275,7 +293,6 @@ async function initializeHooks(){
275293

276294
module.exports={
277295
registerModule,
278-
registerRealm,
279296
initializeESM,
280297
initializeHooks,
281298
getDefaultConditions,

‎lib/internal/modules/helpers.js‎

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,19 @@ const{validateString } = require('internal/validators');
2323
constfs=require('fs');// Import all of `fs` so that it can be monkey-patched.
2424
constinternalFS=require('internal/fs/utils');
2525
constpath=require('path');
26-
const{ pathToFileURL, fileURLToPath,URL}=require('internal/url');
26+
const{ pathToFileURL, fileURLToPath }=require('internal/url');
27+
constassert=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
letdebug=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-
functionnormalizeReferrerURL(referrer){
295-
if(typeofreferrer==='string'&&path.isAbsolute(referrer)){
296-
returnpathToFileURL(referrer).href;
301+
functionnormalizeReferrerURL(referrerName){
302+
if(referrerName===null||referrerName===undefined){
303+
returnundefined;
297304
}
298-
returnnewURL(referrer).href;
305+
306+
if(typeofreferrerName==='string'){
307+
if(path.isAbsolute(referrerName)){
308+
returnpathToFileURL(referrerName).href;
309+
}
310+
311+
if(StringPrototypeStartsWith(referrerName,'file://')||
312+
URLCanParse(referrerName)){
313+
returnreferrerName;
314+
}
315+
316+
returnundefined;
317+
}
318+
319+
assert.fail('Unreachable code reached by '+inspect(referrerName));
299320
}
300321

301322
module.exports={

‎lib/internal/process/pre_execution.js‎

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,23 +67,31 @@ function prepareWorkerThreadExecution(){
6767
}
6868

6969
functionprepareShadowRealmExecution(){
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-
returnesmLoader.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
functionprepareExecution(options){

‎lib/internal/source_map/source_map_cache.js‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,10 @@ function extractSourceMapURLMagicComment(content){
107107
functionmaybeCacheSourceMap(filename,content,cjsModuleInstance,isGeneratedSource,sourceURL,sourceMapURL){
108108
constsourceMapsEnabled=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)