Skip to content

Commit 4dae68c

Browse files
joyeecheungmarco-ippolito
authored andcommitted
module: centralize SourceTextModule compilation for builtin loader
This refactors the code that compiles SourceTextModule for the built-in ESM loader to use a common routine so that it's easier to customize cache handling for the ESM loader. In addition this introduces a common symbol for import.meta and import() so that we don't need to create additional closures as handlers, since we can get all the information we need from the V8 callback already. This should reduce the memory footprint of ESM as well. PR-URL: #52291 Backport-PR-URL: #53500 Refs: #47472 Reviewed-By: Geoffrey Booth <[email protected]> Reviewed-By: Stephen Belanger <[email protected]>
1 parent cad46af commit 4dae68c

File tree

7 files changed

+109
-91
lines changed

7 files changed

+109
-91
lines changed

lib/internal/modules/esm/create_dynamic_module.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ ${ArrayPrototypeJoin(ArrayPrototypeMap(imports, createImport), '\n')}
5555
${ArrayPrototypeJoin(ArrayPrototypeMap(exports, createExport), '\n')}
5656
import.meta.done();
5757
`;
58-
const { ModuleWrap } = internalBinding('module_wrap');
59-
const m = new ModuleWrap(`${url}`, undefined, source, 0, 0);
58+
const { registerModule, compileSourceTextModule } = require('internal/modules/esm/utils');
59+
const m = compileSourceTextModule(`${url}`, source);
6060

6161
const readyfns = new SafeSet();
6262
/** @type {DynamicModuleReflect} */
@@ -68,7 +68,6 @@ import.meta.done();
6868
if (imports.length) {
6969
reflect.imports = { __proto__: null };
7070
}
71-
const { registerModule } = require('internal/modules/esm/utils');
7271
registerModule(m, {
7372
__proto__: null,
7473
initializeImportMeta: (meta, wrap) => {

lib/internal/modules/esm/loader.js

+3-31
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,10 @@ const { getOptionValue } = require('internal/options');
2525
const { isURL, pathToFileURL, URL } = require('internal/url');
2626
const { emitExperimentalWarning, kEmptyObject } = require('internal/util');
2727
const {
28-
registerModule,
28+
compileSourceTextModule,
2929
getDefaultConditions,
3030
} = require('internal/modules/esm/utils');
3131
const { kImplicitAssertType } = require('internal/modules/esm/assert');
32-
const {
33-
maybeCacheSourceMap,
34-
} = require('internal/source_map/source_map_cache');
3532
const { canParse } = internalBinding('url');
3633
const { ModuleWrap } = internalBinding('module_wrap');
3734
let defaultResolve, defaultLoad, defaultLoadSync, importMetaInitializer;
@@ -193,16 +190,7 @@ class ModuleLoader {
193190

194191
async eval(source, url) {
195192
const evalInstance = (url) => {
196-
const module = new ModuleWrap(url, undefined, source, 0, 0);
197-
registerModule(module, {
198-
__proto__: null,
199-
initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }),
200-
importModuleDynamically: (specifier, { url }, importAttributes) => {
201-
return this.import(specifier, url, importAttributes);
202-
},
203-
});
204-
205-
return module;
193+
return compileSourceTextModule(url, source, this);
206194
};
207195
const { ModuleJob } = require('internal/modules/esm/module_job');
208196
const job = new ModuleJob(
@@ -273,26 +261,10 @@ class ModuleLoader {
273261
if (job !== undefined) {
274262
return job.module.getNamespaceSync();
275263
}
276-
277264
// TODO(joyeecheung): refactor this so that we pre-parse in C++ and hit the
278265
// cache here, or use a carrier object to carry the compiled module script
279266
// into the constructor to ensure cache hit.
280-
const wrap = new ModuleWrap(url, undefined, source, 0, 0);
281-
// Cache the source map for the module if present.
282-
if (wrap.sourceMapURL) {
283-
maybeCacheSourceMap(url, source, null, false, undefined, wrap.sourceMapURL);
284-
}
285-
const { registerModule } = require('internal/modules/esm/utils');
286-
// TODO(joyeecheung): refactor so that the default options are shared across
287-
// the built-in loaders.
288-
registerModule(wrap, {
289-
__proto__: null,
290-
initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }),
291-
importModuleDynamically: (specifier, wrap, importAttributes) => {
292-
return this.import(specifier, url, importAttributes);
293-
},
294-
});
295-
267+
const wrap = compileSourceTextModule(url, source, this);
296268
const inspectBrk = (isMain && getOptionValue('--inspect-brk'));
297269

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

lib/internal/modules/esm/translators.js

+2-24
Original file line numberDiff line numberDiff line change
@@ -156,35 +156,13 @@ function errPath(url) {
156156
return url;
157157
}
158158

159-
/**
160-
* Dynamically imports a module using the ESM loader.
161-
* @param {string} specifier - The module specifier to import.
162-
* @param {object} options - An object containing options for the import.
163-
* @param {string} options.url - The URL of the module requesting the import.
164-
* @param {Record<string, string>} [attributes] - An object containing attributes for the import.
165-
* @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} The imported module.
166-
*/
167-
async function importModuleDynamically(specifier, { url }, attributes) {
168-
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
169-
return cascadedLoader.import(specifier, url, attributes);
170-
}
171-
172159
// Strategy for loading a standard JavaScript module.
173160
translators.set('module', function moduleStrategy(url, source, isMain) {
174161
assertBufferSource(source, true, 'load');
175162
source = stringify(source);
176163
debug(`Translating StandardModule ${url}`);
177-
const module = new ModuleWrap(url, undefined, source, 0, 0);
178-
// Cache the source map for the module if present.
179-
if (module.sourceMapURL) {
180-
maybeCacheSourceMap(url, source, null, false, undefined, module.sourceMapURL);
181-
}
182-
const { registerModule } = require('internal/modules/esm/utils');
183-
registerModule(module, {
184-
__proto__: null,
185-
initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }),
186-
importModuleDynamically,
187-
});
164+
const { compileSourceTextModule } = require('internal/modules/esm/utils');
165+
const module = compileSourceTextModule(url, source, this);
188166
return module;
189167
});
190168

lib/internal/modules/esm/utils.js

+71-10
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,18 @@ const {
1313
},
1414
} = internalBinding('util');
1515
const {
16+
source_text_module_default_hdo,
1617
vm_dynamic_import_default_internal,
1718
vm_dynamic_import_main_context_default,
1819
vm_dynamic_import_missing_flag,
1920
vm_dynamic_import_no_callback,
2021
} = internalBinding('symbols');
2122

23+
const { ModuleWrap } = internalBinding('module_wrap');
24+
const {
25+
maybeCacheSourceMap,
26+
} = require('internal/source_map/source_map_cache');
27+
2228
const {
2329
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG,
2430
ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING,
@@ -167,28 +173,55 @@ function registerModule(referrer, registry) {
167173
moduleRegistries.set(idSymbol, registry);
168174
}
169175

176+
/**
177+
* Proxy the import meta handling to the default loader for source text modules.
178+
* @param {Record<string, string | Function>} meta - The import.meta object to initialize.
179+
* @param {ModuleWrap} wrap - The ModuleWrap of the SourceTextModule where `import.meta` is referenced.
180+
*/
181+
function defaultInitializeImportMetaForModule(meta, wrap) {
182+
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
183+
return cascadedLoader.importMetaInitialize(meta, { url: wrap.url });
184+
}
185+
170186
/**
171187
* Defines the `import.meta` object for a given module.
172188
* @param {symbol} symbol - Reference to the module.
173189
* @param {Record<string, string | Function>} meta - The import.meta object to initialize.
190+
* @param {ModuleWrap} wrap - The ModuleWrap of the SourceTextModule where `import.meta` is referenced.
174191
*/
175-
function initializeImportMetaObject(symbol, meta) {
176-
if (moduleRegistries.has(symbol)) {
177-
const { initializeImportMeta, callbackReferrer } = moduleRegistries.get(symbol);
178-
if (initializeImportMeta !== undefined) {
179-
meta = initializeImportMeta(meta, callbackReferrer);
180-
}
192+
function initializeImportMetaObject(symbol, meta, wrap) {
193+
if (symbol === source_text_module_default_hdo) {
194+
defaultInitializeImportMetaForModule(meta, wrap);
195+
return;
196+
}
197+
const data = moduleRegistries.get(symbol);
198+
assert(data, `import.meta registry not found for ${wrap.url}`);
199+
const { initializeImportMeta, callbackReferrer } = data;
200+
if (initializeImportMeta !== undefined) {
201+
meta = initializeImportMeta(meta, callbackReferrer);
181202
}
182203
}
183204

184205
/**
185-
* Proxy the dynamic import to the default loader.
206+
* Proxy the dynamic import handling to the default loader for source text modules.
207+
* @param {string} specifier - The module specifier string.
208+
* @param {Record<string, string>} attributes - The import attributes object.
209+
* @param {string|null|undefined} referrerName - name of the referrer.
210+
* @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} - The imported module object.
211+
*/
212+
function defaultImportModuleDynamicallyForModule(specifier, attributes, referrerName) {
213+
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
214+
return cascadedLoader.import(specifier, referrerName, attributes);
215+
}
216+
217+
/**
218+
* Proxy the dynamic import to the default loader for classic scripts.
186219
* @param {string} specifier - The module specifier string.
187220
* @param {Record<string, string>} attributes - The import attributes object.
188221
* @param {string|null|undefined} referrerName - name of the referrer.
189222
* @returns {Promise<import('internal/modules/esm/loader.js').ModuleExports>} - The imported module object.
190223
*/
191-
function defaultImportModuleDynamically(specifier, attributes, referrerName) {
224+
function defaultImportModuleDynamicallyForScript(specifier, attributes, referrerName) {
192225
const parentURL = normalizeReferrerURL(referrerName);
193226
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
194227
return cascadedLoader.import(specifier, parentURL, attributes);
@@ -208,12 +241,16 @@ async function importModuleDynamicallyCallback(referrerSymbol, specifier, attrib
208241
// and fall back to the default loader.
209242
if (referrerSymbol === vm_dynamic_import_main_context_default) {
210243
emitExperimentalWarning('vm.USE_MAIN_CONTEXT_DEFAULT_LOADER');
211-
return defaultImportModuleDynamically(specifier, attributes, referrerName);
244+
return defaultImportModuleDynamicallyForScript(specifier, attributes, referrerName);
212245
}
213246
// For script compiled internally that should use the default loader to handle dynamic
214247
// import, proxy the request to the default loader without the warning.
215248
if (referrerSymbol === vm_dynamic_import_default_internal) {
216-
return defaultImportModuleDynamically(specifier, attributes, referrerName);
249+
return defaultImportModuleDynamicallyForScript(specifier, attributes, referrerName);
250+
}
251+
// For SourceTextModules compiled internally, proxy the request to the default loader.
252+
if (referrerSymbol === source_text_module_default_hdo) {
253+
return defaultImportModuleDynamicallyForModule(specifier, attributes, referrerName);
217254
}
218255

219256
if (moduleRegistries.has(referrerSymbol)) {
@@ -288,6 +325,29 @@ async function initializeHooks() {
288325
return { __proto__: null, hooks, preloadScripts };
289326
}
290327

328+
/**
329+
* Compile a SourceTextModule for the built-in ESM loader. Register it for default
330+
* source map and import.meta and dynamic import() handling if cascadedLoader is provided.
331+
* @param {string} url URL of the module.
332+
* @param {string} source Source code of the module.
333+
* @param {typeof import('./loader.js').ModuleLoader|undefined} cascadedLoader If provided,
334+
* register the module for default handling.
335+
* @returns {ModuleWrap}
336+
*/
337+
function compileSourceTextModule(url, source, cascadedLoader) {
338+
const hostDefinedOption = cascadedLoader ? source_text_module_default_hdo : undefined;
339+
const wrap = new ModuleWrap(url, undefined, source, 0, 0, hostDefinedOption);
340+
341+
if (!cascadedLoader) {
342+
return wrap;
343+
}
344+
// Cache the source map for the module if present.
345+
if (wrap.sourceMapURL) {
346+
maybeCacheSourceMap(url, source, null, false, undefined, wrap.sourceMapURL);
347+
}
348+
return wrap;
349+
}
350+
291351
module.exports = {
292352
registerModule,
293353
initializeESM,
@@ -296,4 +356,5 @@ module.exports = {
296356
getConditionsSet,
297357
loaderWorkerId: 'internal/modules/esm/worker',
298358
forceDefaultLoader,
359+
compileSourceTextModule,
299360
};

lib/internal/vm/module.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -131,19 +131,18 @@ class Module {
131131
importModuleDynamicallyWrap(options.importModuleDynamically) :
132132
undefined,
133133
};
134+
// This will take precedence over the referrer as the object being
135+
// passed into the callbacks.
136+
registry.callbackReferrer = this;
137+
const { registerModule } = require('internal/modules/esm/utils');
138+
registerModule(this[kWrap], registry);
134139
} else {
135140
assert(syntheticEvaluationSteps);
136141
this[kWrap] = new ModuleWrap(identifier, context,
137142
syntheticExportNames,
138143
syntheticEvaluationSteps);
139144
}
140145

141-
// This will take precedence over the referrer as the object being
142-
// passed into the callbacks.
143-
registry.callbackReferrer = this;
144-
const { registerModule } = require('internal/modules/esm/utils');
145-
registerModule(this[kWrap], registry);
146-
147146
this[kContext] = context;
148147
}
149148

src/env_properties.h

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
V(onpskexchange_symbol, "onpskexchange") \
4646
V(resource_symbol, "resource_symbol") \
4747
V(trigger_async_id_symbol, "trigger_async_id_symbol") \
48+
V(source_text_module_default_hdo, "source_text_module_default_hdo") \
4849
V(vm_dynamic_import_default_internal, "vm_dynamic_import_default_internal") \
4950
V(vm_dynamic_import_main_context_default, \
5051
"vm_dynamic_import_main_context_default") \

src/module_wrap.cc

+25-17
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,10 @@ ModuleWrap* ModuleWrap::GetFromModule(Environment* env,
102102
return nullptr;
103103
}
104104

105-
// new ModuleWrap(url, context, source, lineOffset, columnOffset)
106-
// new ModuleWrap(url, context, exportNames, syntheticExecutionFunction)
105+
// new ModuleWrap(url, context, source, lineOffset, columnOffset, cachedData)
106+
// new ModuleWrap(url, context, source, lineOffset, columOffset,
107+
// hostDefinedOption) new ModuleWrap(url, context, exportNames,
108+
// syntheticExecutionFunction)
107109
void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
108110
CHECK(args.IsConstructCall());
109111
CHECK_GE(args.Length(), 3);
@@ -132,22 +134,36 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
132134
int column_offset = 0;
133135

134136
bool synthetic = args[2]->IsArray();
137+
138+
Local<PrimitiveArray> host_defined_options =
139+
PrimitiveArray::New(isolate, HostDefinedOptions::kLength);
140+
Local<Symbol> id_symbol;
135141
if (synthetic) {
136142
// new ModuleWrap(url, context, exportNames, syntheticExecutionFunction)
137143
CHECK(args[3]->IsFunction());
138144
} else {
139145
// new ModuleWrap(url, context, source, lineOffset, columOffset, cachedData)
146+
// new ModuleWrap(url, context, source, lineOffset, columOffset,
147+
// hostDefinedOption)
140148
CHECK(args[2]->IsString());
141149
CHECK(args[3]->IsNumber());
142150
line_offset = args[3].As<Int32>()->Value();
143151
CHECK(args[4]->IsNumber());
144152
column_offset = args[4].As<Int32>()->Value();
145-
}
153+
if (args[5]->IsSymbol()) {
154+
id_symbol = args[5].As<Symbol>();
155+
} else {
156+
id_symbol = Symbol::New(isolate, url);
157+
}
158+
host_defined_options->Set(isolate, HostDefinedOptions::kID, id_symbol);
146159

147-
Local<PrimitiveArray> host_defined_options =
148-
PrimitiveArray::New(isolate, HostDefinedOptions::kLength);
149-
Local<Symbol> id_symbol = Symbol::New(isolate, url);
150-
host_defined_options->Set(isolate, HostDefinedOptions::kID, id_symbol);
160+
if (that->SetPrivate(context,
161+
realm->isolate_data()->host_defined_option_symbol(),
162+
id_symbol)
163+
.IsNothing()) {
164+
return;
165+
}
166+
}
151167

152168
ShouldNotAbortOnUncaughtScope no_abort_scope(realm->env());
153169
TryCatchScope try_catch(realm->env());
@@ -173,8 +189,7 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
173189
SyntheticModuleEvaluationStepsCallback);
174190
} else {
175191
ScriptCompiler::CachedData* cached_data = nullptr;
176-
if (!args[5]->IsUndefined()) {
177-
CHECK(args[5]->IsArrayBufferView());
192+
if (args[5]->IsArrayBufferView()) {
178193
Local<ArrayBufferView> cached_data_buf = args[5].As<ArrayBufferView>();
179194
uint8_t* data =
180195
static_cast<uint8_t*>(cached_data_buf->Buffer()->Data());
@@ -237,13 +252,6 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
237252
return;
238253
}
239254

240-
if (that->SetPrivate(context,
241-
realm->isolate_data()->host_defined_option_symbol(),
242-
id_symbol)
243-
.IsNothing()) {
244-
return;
245-
}
246-
247255
// Use the extras object as an object whose GetCreationContext() will be the
248256
// original `context`, since the `Context` itself strictly speaking cannot
249257
// be stored in an internal field.
@@ -837,7 +845,7 @@ void ModuleWrap::HostInitializeImportMetaObjectCallback(
837845
return;
838846
}
839847
DCHECK(id->IsSymbol());
840-
Local<Value> args[] = {id, meta};
848+
Local<Value> args[] = {id, meta, wrap};
841849
TryCatchScope try_catch(env);
842850
USE(callback->Call(
843851
context, Undefined(realm->isolate()), arraysize(args), args));

0 commit comments

Comments
 (0)