diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 01652bd48d80e2..8fbcb56aa65287 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -319,7 +319,7 @@ class ModuleLoader { * @param {object} importAttributes import attributes from the import statement. * @returns {ModuleJobBase} */ - getModuleWrapForRequire(specifier, parentURL, importAttributes) { + getModuleJobForRequire(specifier, parentURL, importAttributes) { assert(getOptionValue('--experimental-require-module')); if (canParse(specifier)) { diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 853ad8c72bd4b4..d7c6927049c42d 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -1,8 +1,8 @@ 'use strict'; const { + Array, ArrayPrototypeJoin, - ArrayPrototypePush, ArrayPrototypeSome, FunctionPrototype, ObjectSetPrototypeOf, @@ -87,31 +87,8 @@ class ModuleJob extends ModuleJobBase { this.modulePromise = PromiseResolve(this.modulePromise); } - // Wait for the ModuleWrap instance being linked with all dependencies. - const link = async () => { - this.module = await this.modulePromise; - assert(this.module instanceof ModuleWrap); - - // Explicitly keeping track of dependency jobs is needed in order - // to flatten out the dependency graph below in `_instantiate()`, - // so that circular dependencies can't cause a deadlock by two of - // these `link` callbacks depending on each other. - const dependencyJobs = []; - const promises = this.module.link(async (specifier, attributes) => { - const job = await this.#loader.getModuleJob(specifier, url, attributes); - debug(`async link() ${this.url} -> ${specifier}`, job); - ArrayPrototypePush(dependencyJobs, job); - return job.modulePromise; - }); - - if (promises !== undefined) { - await SafePromiseAllReturnVoid(promises); - } - - return SafePromiseAllReturnArrayLike(dependencyJobs); - }; // Promise for the list of all dependencyJobs. - this.linked = link(); + this.linked = this._link(); // This promise is awaited later anyway, so silence // 'unhandled rejection' warnings. PromisePrototypeThen(this.linked, undefined, noop); @@ -121,6 +98,49 @@ class ModuleJob extends ModuleJobBase { this.instantiated = undefined; } + /** + * Iterates the module requests and links with the loader. + * @returns {Promise} Dependency module jobs. + */ + async _link() { + this.module = await this.modulePromise; + assert(this.module instanceof ModuleWrap); + + const moduleRequests = this.module.getModuleRequests(); + // Explicitly keeping track of dependency jobs is needed in order + // to flatten out the dependency graph below in `_instantiate()`, + // so that circular dependencies can't cause a deadlock by two of + // these `link` callbacks depending on each other. + // Create an ArrayLike to avoid calling into userspace with `.then` + // when returned from the async function. + const dependencyJobs = Array(moduleRequests.length); + ObjectSetPrototypeOf(dependencyJobs, null); + + // Specifiers should be aligned with the moduleRequests array in order. + const specifiers = Array(moduleRequests.length); + const modulePromises = Array(moduleRequests.length); + // Iterate with index to avoid calling into userspace with `Symbol.iterator`. + for (let idx = 0; idx < moduleRequests.length; idx++) { + const { specifier, attributes } = moduleRequests[idx]; + + const dependencyJobPromise = this.#loader.getModuleJob( + specifier, this.url, attributes, + ); + const modulePromise = PromisePrototypeThen(dependencyJobPromise, (job) => { + debug(`async link() ${this.url} -> ${specifier}`, job); + dependencyJobs[idx] = job; + return job.modulePromise; + }); + modulePromises[idx] = modulePromise; + specifiers[idx] = specifier; + } + + const modules = await SafePromiseAllReturnArrayLike(modulePromises); + this.module.link(specifiers, modules); + + return dependencyJobs; + } + instantiate() { if (this.instantiated === undefined) { this.instantiated = this._instantiate(); @@ -277,18 +297,20 @@ class ModuleJobSync extends ModuleJobBase { super(url, importAttributes, moduleWrap, isMain, inspectBrk, true); assert(this.module instanceof ModuleWrap); this.#loader = loader; - const moduleRequests = this.module.getModuleRequestsSync(); - const linked = []; + const moduleRequests = this.module.getModuleRequests(); + // Specifiers should be aligned with the moduleRequests array in order. + const specifiers = Array(moduleRequests.length); + const modules = Array(moduleRequests.length); + const jobs = Array(moduleRequests.length); for (let i = 0; i < moduleRequests.length; ++i) { - const { 0: specifier, 1: attributes } = moduleRequests[i]; - const job = this.#loader.getModuleWrapForRequire(specifier, url, attributes); - const isLast = (i === moduleRequests.length - 1); - // TODO(joyeecheung): make the resolution callback deal with both promisified - // an raw module wraps, then we don't need to wrap it with a promise here. - this.module.cacheResolvedWrapsSync(specifier, PromiseResolve(job.module), isLast); - ArrayPrototypePush(linked, job); + const { specifier, attributes } = moduleRequests[i]; + const job = this.#loader.getModuleJobForRequire(specifier, url, attributes); + specifiers[i] = specifier; + modules[i] = job.module; + jobs[i] = job; } - this.linked = linked; + this.module.link(specifiers, modules); + this.linked = jobs; } get modulePromise() { diff --git a/lib/internal/vm/module.js b/lib/internal/vm/module.js index 9ab73e4d3d1ed7..d14f3f6c4ebb44 100644 --- a/lib/internal/vm/module.js +++ b/lib/internal/vm/module.js @@ -1,17 +1,21 @@ 'use strict'; const { + Array, ArrayIsArray, ArrayPrototypeForEach, ArrayPrototypeIndexOf, + ArrayPrototypeMap, ArrayPrototypeSome, ObjectDefineProperty, ObjectFreeze, ObjectGetPrototypeOf, ObjectPrototypeHasOwnProperty, ObjectSetPrototypeOf, + PromiseResolve, + PromisePrototypeThen, ReflectApply, - SafePromiseAllReturnVoid, + SafePromiseAllReturnArrayLike, Symbol, SymbolToStringTag, TypeError, @@ -294,44 +298,62 @@ class SourceTextModule extends Module { importModuleDynamically, }); - this[kLink] = async (linker) => { - this.#statusOverride = 'linking'; + this[kDependencySpecifiers] = undefined; + } - const promises = this[kWrap].link(async (identifier, attributes) => { - const module = await linker(identifier, this, { attributes, assert: attributes }); - if (!isModule(module)) { - throw new ERR_VM_MODULE_NOT_MODULE(); - } - if (module.context !== this.context) { - throw new ERR_VM_MODULE_DIFFERENT_CONTEXT(); - } - if (module.status === 'errored') { - throw new ERR_VM_MODULE_LINK_FAILURE(`request for '${identifier}' resolved to an errored module`, module.error); - } - if (module.status === 'unlinked') { - await module[kLink](linker); - } - return module[kWrap]; + async [kLink](linker) { + this.#statusOverride = 'linking'; + + const moduleRequests = this[kWrap].getModuleRequests(); + // Iterates the module requests and links with the linker. + // Specifiers should be aligned with the moduleRequests array in order. + const specifiers = Array(moduleRequests.length); + const modulePromises = Array(moduleRequests.length); + // Iterates with index to avoid calling into userspace with `Symbol.iterator`. + for (let idx = 0; idx < moduleRequests.length; idx++) { + const { specifier, attributes } = moduleRequests[idx]; + + const linkerResult = linker(specifier, this, { + attributes, + assert: attributes, }); + const modulePromise = PromisePrototypeThen( + PromiseResolve(linkerResult), async (module) => { + if (!isModule(module)) { + throw new ERR_VM_MODULE_NOT_MODULE(); + } + if (module.context !== this.context) { + throw new ERR_VM_MODULE_DIFFERENT_CONTEXT(); + } + if (module.status === 'errored') { + throw new ERR_VM_MODULE_LINK_FAILURE(`request for '${specifier}' resolved to an errored module`, module.error); + } + if (module.status === 'unlinked') { + await module[kLink](linker); + } + return module[kWrap]; + }); + modulePromises[idx] = modulePromise; + specifiers[idx] = specifier; + } - try { - if (promises !== undefined) { - await SafePromiseAllReturnVoid(promises); - } - } catch (e) { - this.#error = e; - throw e; - } finally { - this.#statusOverride = undefined; - } - }; - - this[kDependencySpecifiers] = undefined; + try { + const modules = await SafePromiseAllReturnArrayLike(modulePromises); + this[kWrap].link(specifiers, modules); + } catch (e) { + this.#error = e; + throw e; + } finally { + this.#statusOverride = undefined; + } } get dependencySpecifiers() { validateInternalField(this, kDependencySpecifiers, 'SourceTextModule'); - this[kDependencySpecifiers] ??= ObjectFreeze(this[kWrap].getStaticDependencySpecifiers()); + // TODO(legendecas): add a new getter to expose the import attributes as the value type + // of [[RequestedModules]] is changed in https://tc39.es/proposal-import-attributes/#table-cyclic-module-fields. + this[kDependencySpecifiers] ??= ObjectFreeze( + ArrayPrototypeMap(this[kWrap].getModuleRequests(), (request) => request.specifier)); return this[kDependencySpecifiers]; } @@ -393,10 +415,10 @@ class SyntheticModule extends Module { context, identifier, }); + } - this[kLink] = () => this[kWrap].link(() => { - assert.fail('link callback should not be called'); - }); + [kLink]() { + /** nothing to do for synthetic modules */ } setExport(name, value) { diff --git a/src/env_properties.h b/src/env_properties.h index c28878486e4ac2..e10d472e730f34 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -74,6 +74,7 @@ V(args_string, "args") \ V(asn1curve_string, "asn1Curve") \ V(async_ids_stack_string, "async_ids_stack") \ + V(attributes_string, "attributes") \ V(base_string, "base") \ V(bits_string, "bits") \ V(block_list_string, "blockList") \ @@ -307,6 +308,7 @@ V(sni_context_string, "sni_context") \ V(source_string, "source") \ V(source_map_url_string, "sourceMapURL") \ + V(specifier_string, "specifier") \ V(stack_string, "stack") \ V(standard_name_string, "standardName") \ V(start_time_string, "startTime") \ @@ -384,6 +386,7 @@ V(js_transferable_constructor_template, v8::FunctionTemplate) \ V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \ V(message_port_constructor_template, v8::FunctionTemplate) \ + V(module_wrap_constructor_template, v8::FunctionTemplate) \ V(microtask_queue_ctor_template, v8::FunctionTemplate) \ V(pipe_constructor_template, v8::FunctionTemplate) \ V(promise_wrap_template, v8::ObjectTemplate) \ diff --git a/src/module_wrap.cc b/src/module_wrap.cc index fd3d9c0ceaf317..f6285182d92871 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -399,136 +399,105 @@ static Local createImportAttributesContainer( Local raw_attributes, const int elements_per_attribute) { CHECK_EQ(raw_attributes->Length() % elements_per_attribute, 0); - Local attributes = - Object::New(isolate, v8::Null(isolate), nullptr, nullptr, 0); + size_t num_attributes = raw_attributes->Length() / elements_per_attribute; + std::vector> names(num_attributes); + std::vector> values(num_attributes); + for (int i = 0; i < raw_attributes->Length(); i += elements_per_attribute) { - attributes - ->Set(realm->context(), - raw_attributes->Get(realm->context(), i).As(), - raw_attributes->Get(realm->context(), i + 1).As()) - .ToChecked(); + int idx = i / elements_per_attribute; + names[idx] = raw_attributes->Get(realm->context(), i).As(); + values[idx] = raw_attributes->Get(realm->context(), i + 1).As(); } - return attributes; + return Object::New( + isolate, v8::Null(isolate), names.data(), values.data(), num_attributes); } -void ModuleWrap::GetModuleRequestsSync( - const FunctionCallbackInfo& args) { - Realm* realm = Realm::GetCurrent(args); - Isolate* isolate = args.GetIsolate(); - - Local that = args.This(); - - ModuleWrap* obj; - ASSIGN_OR_RETURN_UNWRAP(&obj, that); - - CHECK(!obj->linked_); +static Local createModuleRequestsContainer( + Realm* realm, Isolate* isolate, Local raw_requests) { + std::vector> requests(raw_requests->Length()); - Local module = obj->module_.Get(isolate); - Local module_requests = module->GetModuleRequests(); - const int module_requests_length = module_requests->Length(); - - std::vector> requests; - requests.reserve(module_requests_length); - // call the dependency resolve callbacks - for (int i = 0; i < module_requests_length; i++) { + for (int i = 0; i < raw_requests->Length(); i++) { Local module_request = - module_requests->Get(realm->context(), i).As(); - Local raw_attributes = module_request->GetImportAssertions(); - std::vector> request = { - module_request->GetSpecifier(), - createImportAttributesContainer(realm, isolate, raw_attributes, 3), - }; - requests.push_back(Array::New(isolate, request.data(), request.size())); - } + raw_requests->Get(realm->context(), i).As(); - args.GetReturnValue().Set( - Array::New(isolate, requests.data(), requests.size())); -} - -void ModuleWrap::CacheResolvedWrapsSync( - const FunctionCallbackInfo& args) { - Isolate* isolate = args.GetIsolate(); + Local specifier = module_request->GetSpecifier(); - CHECK_EQ(args.Length(), 3); - CHECK(args[0]->IsString()); - CHECK(args[1]->IsPromise()); - CHECK(args[2]->IsBoolean()); + // Contains the import assertions for this request in the form: + // [key1, value1, source_offset1, key2, value2, source_offset2, ...]. + Local raw_attributes = module_request->GetImportAssertions(); + Local attributes = + createImportAttributesContainer(realm, isolate, raw_attributes, 3); - ModuleWrap* dependent; - ASSIGN_OR_RETURN_UNWRAP(&dependent, args.This()); + Local names[] = { + realm->isolate_data()->specifier_string(), + realm->isolate_data()->attributes_string(), + }; + Local values[] = { + specifier, + attributes, + }; + DCHECK_EQ(arraysize(names), arraysize(values)); - Utf8Value specifier(isolate, args[0]); - dependent->resolve_cache_[specifier.ToString()].Reset(isolate, - args[1].As()); + Local request = Object::New( + isolate, v8::Null(isolate), names, values, arraysize(names)); - if (args[2].As()->Value()) { - dependent->linked_ = true; + requests[i] = request; } + + return Array::New(isolate, requests.data(), requests.size()); } -void ModuleWrap::Link(const FunctionCallbackInfo& args) { +void ModuleWrap::GetModuleRequests(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); Isolate* isolate = args.GetIsolate(); - - CHECK_EQ(args.Length(), 1); - CHECK(args[0]->IsFunction()); - Local that = args.This(); ModuleWrap* obj; ASSIGN_OR_RETURN_UNWRAP(&obj, that); - if (obj->linked_) - return; - obj->linked_ = true; + Local module = obj->module_.Get(isolate); + args.GetReturnValue().Set(createModuleRequestsContainer( + realm, isolate, module->GetModuleRequests())); +} - Local resolver_arg = args[0].As(); +// moduleWrap.link(specifiers, moduleWraps) +void ModuleWrap::Link(const FunctionCallbackInfo& args) { + Realm* realm = Realm::GetCurrent(args); + Isolate* isolate = args.GetIsolate(); + Local context = realm->context(); - Local mod_context = obj->context(); - Local module = obj->module_.Get(isolate); + ModuleWrap* dependent; + ASSIGN_OR_RETURN_UNWRAP(&dependent, args.This()); - Local module_requests = module->GetModuleRequests(); - const int module_requests_length = module_requests->Length(); - MaybeStackBuffer, 16> promises(module_requests_length); + CHECK_EQ(args.Length(), 2); - // call the dependency resolve callbacks - for (int i = 0; i < module_requests_length; i++) { - Local module_request = - module_requests->Get(realm->context(), i).As(); - Local specifier = module_request->GetSpecifier(); - Utf8Value specifier_utf8(realm->isolate(), specifier); - std::string specifier_std(*specifier_utf8, specifier_utf8.length()); + Local specifiers = args[0].As(); + Local modules = args[1].As(); + CHECK_EQ(specifiers->Length(), modules->Length()); - Local raw_attributes = module_request->GetImportAssertions(); - Local attributes = - createImportAttributesContainer(realm, isolate, raw_attributes, 3); + std::vector> specifiers_buffer; + if (FromV8Array(context, specifiers, &specifiers_buffer).IsNothing()) { + return; + } + std::vector> modules_buffer; + if (FromV8Array(context, modules, &modules_buffer).IsNothing()) { + return; + } - Local argv[] = { - specifier, - attributes, - }; + for (uint32_t i = 0; i < specifiers->Length(); i++) { + Local specifier_str = + specifiers_buffer[i].Get(isolate).As(); + Local module_object = modules_buffer[i].Get(isolate).As(); - MaybeLocal maybe_resolve_return_value = - resolver_arg->Call(mod_context, that, arraysize(argv), argv); - if (maybe_resolve_return_value.IsEmpty()) { - return; - } - Local resolve_return_value = - maybe_resolve_return_value.ToLocalChecked(); - if (!resolve_return_value->IsPromise()) { - THROW_ERR_VM_MODULE_LINK_FAILURE( - realm, "request for '%s' did not return promise", specifier_std); - return; - } - Local resolve_promise = resolve_return_value.As(); - obj->resolve_cache_[specifier_std].Reset(isolate, resolve_promise); + CHECK( + realm->isolate_data()->module_wrap_constructor_template()->HasInstance( + module_object)); - promises[i] = resolve_promise; + Utf8Value specifier(isolate, specifier_str); + dependent->resolve_cache_[specifier.ToString()].Reset(isolate, + module_object); } - - args.GetReturnValue().Set( - Array::New(isolate, promises.out(), promises.length())); } void ModuleWrap::Instantiate(const FunctionCallbackInfo& args) { @@ -788,29 +757,6 @@ void ModuleWrap::GetStatus(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(module->GetStatus()); } -void ModuleWrap::GetStaticDependencySpecifiers( - const FunctionCallbackInfo& args) { - Realm* realm = Realm::GetCurrent(args); - ModuleWrap* obj; - ASSIGN_OR_RETURN_UNWRAP(&obj, args.This()); - - Local module = obj->module_.Get(realm->isolate()); - - Local module_requests = module->GetModuleRequests(); - int count = module_requests->Length(); - - MaybeStackBuffer, 16> specifiers(count); - - for (int i = 0; i < count; i++) { - Local module_request = - module_requests->Get(realm->context(), i).As(); - specifiers[i] = module_request->GetSpecifier(); - } - - args.GetReturnValue().Set( - Array::New(realm->isolate(), specifiers.out(), count)); -} - void ModuleWrap::GetError(const FunctionCallbackInfo& args) { Isolate* isolate = args.GetIsolate(); ModuleWrap* obj; @@ -848,16 +794,8 @@ MaybeLocal ModuleWrap::ResolveModuleCallback( return MaybeLocal(); } - Local resolve_promise = + Local module_object = dependent->resolve_cache_[specifier_std].Get(isolate); - - if (resolve_promise->State() != Promise::kFulfilled) { - THROW_ERR_VM_MODULE_LINK_FAILURE( - env, "request for '%s' is not yet fulfilled", specifier_std); - return MaybeLocal(); - } - - Local module_object = resolve_promise->Result().As(); if (module_object.IsEmpty() || !module_object->IsObject()) { THROW_ERR_VM_MODULE_LINK_FAILURE( env, "request for '%s' did not return an object", specifier_std); @@ -1084,9 +1022,7 @@ void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data, ModuleWrap::kInternalFieldCount); SetProtoMethod(isolate, tpl, "link", Link); - SetProtoMethod(isolate, tpl, "getModuleRequestsSync", GetModuleRequestsSync); - SetProtoMethod( - isolate, tpl, "cacheResolvedWrapsSync", CacheResolvedWrapsSync); + SetProtoMethod(isolate, tpl, "getModuleRequests", GetModuleRequests); SetProtoMethod(isolate, tpl, "instantiateSync", InstantiateSync); SetProtoMethod(isolate, tpl, "evaluateSync", EvaluateSync); SetProtoMethod(isolate, tpl, "getNamespaceSync", GetNamespaceSync); @@ -1098,12 +1034,8 @@ void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data, SetProtoMethodNoSideEffect(isolate, tpl, "getNamespace", GetNamespace); SetProtoMethodNoSideEffect(isolate, tpl, "getStatus", GetStatus); SetProtoMethodNoSideEffect(isolate, tpl, "getError", GetError); - SetProtoMethodNoSideEffect(isolate, - tpl, - "getStaticDependencySpecifiers", - GetStaticDependencySpecifiers); - SetConstructorFunction(isolate, target, "ModuleWrap", tpl); + isolate_data->set_module_wrap_constructor_template(tpl); SetMethod(isolate, target, @@ -1141,8 +1073,7 @@ void ModuleWrap::RegisterExternalReferences( registry->Register(New); registry->Register(Link); - registry->Register(GetModuleRequestsSync); - registry->Register(CacheResolvedWrapsSync); + registry->Register(GetModuleRequests); registry->Register(InstantiateSync); registry->Register(EvaluateSync); registry->Register(GetNamespaceSync); @@ -1153,7 +1084,6 @@ void ModuleWrap::RegisterExternalReferences( registry->Register(GetNamespace); registry->Register(GetStatus); registry->Register(GetError); - registry->Register(GetStaticDependencySpecifiers); registry->Register(SetImportModuleDynamicallyCallback); registry->Register(SetInitializeImportMetaObjectCallback); diff --git a/src/module_wrap.h b/src/module_wrap.h index bd50bce8ad2add..671f87f76eb3b3 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -98,9 +98,7 @@ class ModuleWrap : public BaseObject { ~ModuleWrap() override; static void New(const v8::FunctionCallbackInfo& args); - static void GetModuleRequestsSync( - const v8::FunctionCallbackInfo& args); - static void CacheResolvedWrapsSync( + static void GetModuleRequests( const v8::FunctionCallbackInfo& args); static void InstantiateSync(const v8::FunctionCallbackInfo& args); static void EvaluateSync(const v8::FunctionCallbackInfo& args); @@ -112,8 +110,6 @@ class ModuleWrap : public BaseObject { static void GetNamespace(const v8::FunctionCallbackInfo& args); static void GetStatus(const v8::FunctionCallbackInfo& args); static void GetError(const v8::FunctionCallbackInfo& args); - static void GetStaticDependencySpecifiers( - const v8::FunctionCallbackInfo& args); static void SetImportModuleDynamicallyCallback( const v8::FunctionCallbackInfo& args); @@ -133,10 +129,9 @@ class ModuleWrap : public BaseObject { static ModuleWrap* GetFromModule(node::Environment*, v8::Local); v8::Global module_; - std::unordered_map> resolve_cache_; + std::unordered_map> resolve_cache_; contextify::ContextifyContext* contextify_context_ = nullptr; bool synthetic_ = false; - bool linked_ = false; int module_hash_; }; diff --git a/test/parallel/test-internal-module-wrap.js b/test/parallel/test-internal-module-wrap.js index 520a83a3a47c0e..3839338bc2da98 100644 --- a/test/parallel/test-internal-module-wrap.js +++ b/test/parallel/test-internal-module-wrap.js @@ -5,23 +5,21 @@ const assert = require('assert'); const { internalBinding } = require('internal/test/binding'); const { ModuleWrap } = internalBinding('module_wrap'); -const { getPromiseDetails, isPromise } = internalBinding('util'); -const setTimeoutAsync = require('util').promisify(setTimeout); const foo = new ModuleWrap('foo', undefined, 'export * from "bar";', 0, 0); const bar = new ModuleWrap('bar', undefined, 'export const five = 5', 0, 0); (async () => { - const promises = foo.link(() => setTimeoutAsync(1000).then(() => bar)); - assert.strictEqual(promises.length, 1); - assert(isPromise(promises[0])); - - await Promise.all(promises); - - assert.strictEqual(getPromiseDetails(promises[0])[1], bar); + const moduleRequests = foo.getModuleRequests(); + assert.strictEqual(moduleRequests.length, 1); + assert.strictEqual(moduleRequests[0].specifier, 'bar'); + foo.link(['bar'], [bar]); foo.instantiate(); assert.strictEqual(await foo.evaluate(-1, false), undefined); assert.strictEqual(foo.getNamespace().five, 5); + + // Check that the module requests are the same after linking, instantiate, and evaluation. + assert.deepStrictEqual(moduleRequests, foo.getModuleRequests()); })().then(common.mustCall());