Skip to content

Commit d6b57f6

Browse files
authored
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 Refs: #47472 Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
1 parent 4df34cf commit d6b57f6

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, isEntryPoint = false) {
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)) {
@@ -286,6 +323,29 @@ async function initializeHooks() {
286323
return hooks;
287324
}
288325

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

lib/internal/vm/module.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -141,19 +141,18 @@ class Module {
141141
importModuleDynamicallyWrap(options.importModuleDynamically) :
142142
undefined,
143143
};
144+
// This will take precedence over the referrer as the object being
145+
// passed into the callbacks.
146+
registry.callbackReferrer = this;
147+
const { registerModule } = require('internal/modules/esm/utils');
148+
registerModule(this[kWrap], registry);
144149
} else {
145150
assert(syntheticEvaluationSteps);
146151
this[kWrap] = new ModuleWrap(identifier, context,
147152
syntheticExportNames,
148153
syntheticEvaluationSteps);
149154
}
150155

151-
// This will take precedence over the referrer as the object being
152-
// passed into the callbacks.
153-
registry.callbackReferrer = this;
154-
const { registerModule } = require('internal/modules/esm/utils');
155-
registerModule(this[kWrap], registry);
156-
157156
this[kContext] = context;
158157
}
159158

src/env_properties.h

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
V(onpskexchange_symbol, "onpskexchange") \
5050
V(resource_symbol, "resource_symbol") \
5151
V(trigger_async_id_symbol, "trigger_async_id_symbol") \
52+
V(source_text_module_default_hdo, "source_text_module_default_hdo") \
5253
V(vm_dynamic_import_default_internal, "vm_dynamic_import_default_internal") \
5354
V(vm_dynamic_import_main_context_default, \
5455
"vm_dynamic_import_main_context_default") \

src/module_wrap.cc

+25-17
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,10 @@ v8::Maybe<bool> ModuleWrap::CheckUnsettledTopLevelAwait() {
142142
return v8::Just(false);
143143
}
144144

145-
// new ModuleWrap(url, context, source, lineOffset, columnOffset)
146-
// new ModuleWrap(url, context, exportNames, syntheticExecutionFunction)
145+
// new ModuleWrap(url, context, source, lineOffset, columnOffset, cachedData)
146+
// new ModuleWrap(url, context, source, lineOffset, columOffset,
147+
// hostDefinedOption) new ModuleWrap(url, context, exportNames,
148+
// syntheticExecutionFunction)
147149
void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
148150
CHECK(args.IsConstructCall());
149151
CHECK_GE(args.Length(), 3);
@@ -172,22 +174,36 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
172174
int column_offset = 0;
173175

174176
bool synthetic = args[2]->IsArray();
177+
178+
Local<PrimitiveArray> host_defined_options =
179+
PrimitiveArray::New(isolate, HostDefinedOptions::kLength);
180+
Local<Symbol> id_symbol;
175181
if (synthetic) {
176182
// new ModuleWrap(url, context, exportNames, syntheticExecutionFunction)
177183
CHECK(args[3]->IsFunction());
178184
} else {
179185
// new ModuleWrap(url, context, source, lineOffset, columOffset, cachedData)
186+
// new ModuleWrap(url, context, source, lineOffset, columOffset,
187+
// hostDefinedOption)
180188
CHECK(args[2]->IsString());
181189
CHECK(args[3]->IsNumber());
182190
line_offset = args[3].As<Int32>()->Value();
183191
CHECK(args[4]->IsNumber());
184192
column_offset = args[4].As<Int32>()->Value();
185-
}
193+
if (args[5]->IsSymbol()) {
194+
id_symbol = args[5].As<Symbol>();
195+
} else {
196+
id_symbol = Symbol::New(isolate, url);
197+
}
198+
host_defined_options->Set(isolate, HostDefinedOptions::kID, id_symbol);
186199

187-
Local<PrimitiveArray> host_defined_options =
188-
PrimitiveArray::New(isolate, HostDefinedOptions::kLength);
189-
Local<Symbol> id_symbol = Symbol::New(isolate, url);
190-
host_defined_options->Set(isolate, HostDefinedOptions::kID, id_symbol);
200+
if (that->SetPrivate(context,
201+
realm->isolate_data()->host_defined_option_symbol(),
202+
id_symbol)
203+
.IsNothing()) {
204+
return;
205+
}
206+
}
191207

192208
ShouldNotAbortOnUncaughtScope no_abort_scope(realm->env());
193209
TryCatchScope try_catch(realm->env());
@@ -215,8 +231,7 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
215231
isolate, url, span, SyntheticModuleEvaluationStepsCallback);
216232
} else {
217233
ScriptCompiler::CachedData* cached_data = nullptr;
218-
if (!args[5]->IsUndefined()) {
219-
CHECK(args[5]->IsArrayBufferView());
234+
if (args[5]->IsArrayBufferView()) {
220235
Local<ArrayBufferView> cached_data_buf = args[5].As<ArrayBufferView>();
221236
uint8_t* data =
222237
static_cast<uint8_t*>(cached_data_buf->Buffer()->Data());
@@ -279,13 +294,6 @@ void ModuleWrap::New(const FunctionCallbackInfo<Value>& args) {
279294
return;
280295
}
281296

282-
if (that->SetPrivate(context,
283-
realm->isolate_data()->host_defined_option_symbol(),
284-
id_symbol)
285-
.IsNothing()) {
286-
return;
287-
}
288-
289297
// Use the extras object as an object whose GetCreationContext() will be the
290298
// original `context`, since the `Context` itself strictly speaking cannot
291299
// be stored in an internal field.
@@ -878,7 +886,7 @@ void ModuleWrap::HostInitializeImportMetaObjectCallback(
878886
return;
879887
}
880888
DCHECK(id->IsSymbol());
881-
Local<Value> args[] = {id, meta};
889+
Local<Value> args[] = {id, meta, wrap};
882890
TryCatchScope try_catch(env);
883891
USE(callback->Call(
884892
context, Undefined(realm->isolate()), arraysize(args), args));

0 commit comments

Comments
 (0)