Skip to content

Commit b5a91fd

Browse files
esm: add import.meta.sync
1 parent 60b9aaa commit b5a91fd

24 files changed

+433
-4
lines changed

doc/api/esm.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,37 @@ a second argument:
502502
* `parent` {string|URL} An optional absolute parent module URL to resolve from.
503503
**Default:** `import.meta.url`
504504
505+
### `import.meta.sync(specifier)`
506+
507+
<!-- YAML
508+
added: REPLACEME
509+
-->
510+
511+
> Stability: 1.0 - Early development
512+
513+
* `specifier` {string} The module specifier to synchronously import.
514+
* Returns: {Object} The module namespace object.
515+
516+
`import.meta.sync()` provides a way to synchronously import ES modules,
517+
offering feature parity with `require()` for conditional synchronous imports
518+
in ES modules. This is particularly useful when migrating from CommonJS to ES modules
519+
while maintaining the ability to conditionally load dependencies in synchronous code paths.
520+
521+
The primary use case is conditional synchronous importing:
522+
523+
```js
524+
let mod;
525+
try {
526+
mod = import.meta.sync('./module-a.js');
527+
} catch {
528+
mod = import.meta.sync('./module-b.js');
529+
}
530+
```
531+
532+
If the imported module or any of its
533+
dependencies use top-level `await`, `import.meta.sync()` will throw an
534+
[`ERR_REQUIRE_ASYNC_MODULE`][] error.
535+
505536
## Interoperability with CommonJS
506537
507538
### `import` statements
@@ -1304,6 +1335,7 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][].
13041335
[`"exports"`]: packages.md#exports
13051336
[`"type"`]: packages.md#type
13061337
[`--input-type`]: cli.md#--input-typetype
1338+
[`ERR_REQUIRE_ASYNC_MODULE`]: #err_require_async_module
13071339
[`data:` URLs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
13081340
[`export`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
13091341
[`import()`]: #import-expressions

lib/internal/modules/esm/loader.js

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const {
3131
} = require('internal/errors').codes;
3232
const { getOptionValue } = require('internal/options');
3333
const { isURL, pathToFileURL } = require('internal/url');
34-
const { kEmptyObject } = require('internal/util');
34+
const { kEmptyObject, emitExperimentalWarning } = require('internal/util');
3535
const {
3636
compileSourceTextModule,
3737
getDefaultConditions,
@@ -52,6 +52,7 @@ const {
5252
kSourcePhase,
5353
throwIfPromiseRejected,
5454
setImportMetaResolveInitializer,
55+
setImportMetaSyncInitializer,
5556
} = internalBinding('module_wrap');
5657
const {
5758
urlToFilename,
@@ -908,6 +909,36 @@ function createImportMetaResolve(loader, moduleURL) {
908909
};
909910
}
910911

912+
/**
913+
* @param {ModuleLoader} loader The cascaded loader.
914+
* @param {string} moduleURL URL of the module accessing import.meta
915+
* @returns {function(string, ImportAttributes|undefined): ModuleNamespace} The import.meta.sync function
916+
*/
917+
function createImportMetaSync(loader, moduleURL) {
918+
/**
919+
* @param {string} specifier The module specifier to synchronously import.
920+
* @param {Record<string, any>} [importAttributes] Optional import attributes object. Accepts
921+
* the same shape as dynamic import(): { with: { type: 'json' } } etc.
922+
* @returns {ModuleNamespace} The module namespace object
923+
*/
924+
return function sync(specifier, importAttributes = kEmptyObject) {
925+
emitExperimentalWarning('import.meta.sync');
926+
let normalizedAttributes = importAttributes;
927+
if (importAttributes && typeof importAttributes === 'object' && importAttributes.with) {
928+
const withBag = importAttributes.with;
929+
if (withBag && typeof withBag === 'object') {
930+
normalizedAttributes = { ...withBag, __proto__: null };
931+
}
932+
}
933+
const request = { specifier, phase: kEvaluationPhase, attributes: normalizedAttributes, __proto__: null };
934+
const job = loader.getOrCreateModuleJob(moduleURL, request, kImportInImportedESM);
935+
936+
// This will throw if the module has TLA or is async
937+
const result = job.runSync();
938+
return result.namespace;
939+
};
940+
}
941+
911942
let cascadedLoader;
912943
/**
913944
* This is a singleton ESM loader that integrates the loader hooks, if any.
@@ -924,12 +955,13 @@ let cascadedLoader;
924955
function getOrInitializeCascadedLoader(asyncLoaderHooks) {
925956
if (!cascadedLoader) {
926957
cascadedLoader = createModuleLoader(asyncLoaderHooks);
927-
// import.meta.resolve is not allowed in the async loader hook worker thread.
928-
// So only set up the import.meta.resolve initializer when we are initializing
958+
// import.meta.resolve and import.meta.sync are not allowed in the async loader hook worker thread.
959+
// So only set up the initializers when we are initializing
929960
// the non-loader-hook-thread cascaded loader. When the native land doesn't see it,
930961
// it knows the loader is running on the loader hook thread.
931962
if (!(asyncLoaderHooks?.isForAsyncLoaderHookWorker)) {
932963
setImportMetaResolveInitializer(createImportMetaResolve.bind(null, cascadedLoader));
964+
setImportMetaSyncInitializer(createImportMetaSync.bind(null, cascadedLoader));
933965
}
934966
}
935967
return cascadedLoader;

src/env_properties.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@
471471
V(get_source_map_error_source, v8::Function) \
472472
V(host_import_module_dynamically_callback, v8::Function) \
473473
V(host_import_meta_resolve_initializer, v8::Function) \
474+
V(host_import_meta_sync_initializer, v8::Function) \
474475
V(host_initialize_import_meta_object_callback, v8::Function) \
475476
V(http2session_on_altsvc_function, v8::Function) \
476477
V(http2session_on_error_function, v8::Function) \

src/module_wrap.cc

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,18 @@ void ModuleWrap::SetImportMetaResolveInitializer(
12201220
realm->set_host_import_meta_resolve_initializer(initializer);
12211221
}
12221222

1223+
void ModuleWrap::SetImportMetaSyncInitializer(
1224+
const v8::FunctionCallbackInfo<v8::Value>& args) {
1225+
Isolate* isolate = args.GetIsolate();
1226+
Realm* realm = Realm::GetCurrent(args);
1227+
HandleScope handle_scope(isolate);
1228+
1229+
CHECK_EQ(args.Length(), 1);
1230+
CHECK(args[0]->IsFunction());
1231+
Local<Function> initializer = args[0].As<Function>();
1232+
realm->set_host_import_meta_sync_initializer(initializer);
1233+
}
1234+
12231235
static void ImportMetaResolveLazyGetter(
12241236
Local<v8::Name> name, const PropertyCallbackInfo<Value>& info) {
12251237
Isolate* isolate = info.GetIsolate();
@@ -1256,6 +1268,42 @@ static void ImportMetaResolveLazyGetter(
12561268
info.GetReturnValue().Set(ret);
12571269
}
12581270

1271+
static void ImportMetaSyncLazyGetter(Local<v8::Name> name,
1272+
const PropertyCallbackInfo<Value>& info) {
1273+
Isolate* isolate = info.GetIsolate();
1274+
Local<Value> receiver_val = info.This();
1275+
if (!receiver_val->IsObject()) {
1276+
THROW_ERR_INVALID_INVOCATION(isolate);
1277+
return;
1278+
}
1279+
Local<Object> receiver = receiver_val.As<Object>();
1280+
Local<Context> context;
1281+
if (!receiver->GetCreationContext().ToLocal(&context)) {
1282+
THROW_ERR_INVALID_INVOCATION(isolate);
1283+
return;
1284+
}
1285+
Realm* realm = Realm::GetCurrent(context);
1286+
if (realm == nullptr) {
1287+
THROW_ERR_INVALID_INVOCATION(isolate);
1288+
}
1289+
Local<Function> initializer = realm->host_import_meta_sync_initializer();
1290+
if (initializer.IsEmpty()) {
1291+
THROW_ERR_INVALID_INVOCATION(isolate);
1292+
return;
1293+
}
1294+
1295+
// This should be createImportMetaSync(). The loader argument is already
1296+
// bound at initialization time.
1297+
Local<Value> args[] = {info.Data()};
1298+
Local<Value> ret;
1299+
if (!initializer
1300+
->Call(context, Undefined(realm->isolate()), arraysize(args), args)
1301+
.ToLocal(&ret)) {
1302+
return;
1303+
}
1304+
info.GetReturnValue().Set(ret);
1305+
}
1306+
12591307
static void PathHelpersLazyGetter(Local<v8::Name> name,
12601308
const PropertyCallbackInfo<Value>& info) {
12611309
Isolate* isolate = info.GetIsolate();
@@ -1361,6 +1409,18 @@ static Maybe<void> DefaultImportMetaObjectInitializer(Realm* realm,
13611409
return Nothing<void>();
13621410
}
13631411

1412+
// Set a lazy getter of import.meta.sync
1413+
Local<Function> import_meta_sync_initializer =
1414+
realm->host_import_meta_sync_initializer();
1415+
if (!import_meta_sync_initializer.IsEmpty() &&
1416+
meta->SetLazyDataProperty(context,
1417+
FIXED_ONE_BYTE_STRING(isolate, "sync"),
1418+
ImportMetaSyncLazyGetter,
1419+
url)
1420+
.IsNothing()) {
1421+
return Nothing<void>();
1422+
}
1423+
13641424
// Set import.meta.url = moduleWrap.url
13651425
if (meta->Set(context, env->url_string(), url).IsEmpty()) {
13661426
return Nothing<void>();
@@ -1629,6 +1689,10 @@ void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data,
16291689
target,
16301690
"setImportMetaResolveInitializer",
16311691
SetImportMetaResolveInitializer);
1692+
SetMethod(isolate,
1693+
target,
1694+
"setImportMetaSyncInitializer",
1695+
SetImportMetaSyncInitializer);
16321696
SetMethod(isolate,
16331697
target,
16341698
"createRequiredModuleFacade",
@@ -1683,6 +1747,7 @@ void ModuleWrap::RegisterExternalReferences(
16831747
registry->Register(SetImportModuleDynamicallyCallback);
16841748
registry->Register(SetInitializeImportMetaObjectCallback);
16851749
registry->Register(SetImportMetaResolveInitializer);
1750+
registry->Register(SetImportMetaSyncInitializer);
16861751
registry->Register(ThrowIfPromiseRejected);
16871752
}
16881753
} // namespace loader

src/module_wrap.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ class ModuleWrap : public BaseObject {
184184
const v8::FunctionCallbackInfo<v8::Value>& args);
185185
static void SetImportMetaResolveInitializer(
186186
const v8::FunctionCallbackInfo<v8::Value>& args);
187+
static void SetImportMetaSyncInitializer(
188+
const v8::FunctionCallbackInfo<v8::Value>& args);
187189
static void SetInitializeImportMetaObjectCallback(
188190
const v8::FunctionCallbackInfo<v8::Value>& args);
189191
static v8::MaybeLocal<v8::Value> SyntheticModuleEvaluationStepsCallback(

0 commit comments

Comments
 (0)