From 28055ff235ad0ce765f5242433e1751bad493cbd Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Mon, 12 Jun 2023 23:42:06 +0200 Subject: [PATCH 01/13] esm: remove CLI flag limitation to programmatic registration --- lib/internal/errors.js | 5 -- lib/internal/modules/esm/loader.js | 70 ++++++++++--------- lib/internal/process/esm_loader.js | 10 ++- .../test-esm-loader-programmatically.mjs | 31 ++++---- 4 files changed, 63 insertions(+), 53 deletions(-) diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 651a2fa99cb715..df3fcb13873015 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1036,11 +1036,6 @@ E('ERR_ENCODING_INVALID_ENCODED_DATA', function(encoding, ret) { }, TypeError); E('ERR_ENCODING_NOT_SUPPORTED', 'The "%s" encoding is not supported', RangeError); -E('ERR_ESM_LOADER_REGISTRATION_UNAVAILABLE', 'Programmatically registering custom ESM loaders ' + - 'currently requires at least one custom loader to have been registered via the --experimental-loader ' + - 'flag. A no-op loader registered via CLI is sufficient (for example: `--experimental-loader ' + - '"data:text/javascript,"` with the necessary trailing comma). A future version of Node.js ' + - 'will remove this requirement.', Error); E('ERR_EVAL_ESM_CANNOT_PRINT', '--print cannot be used with ESM input', Error); E('ERR_EVENT_RECURSION', 'The event "%s" is already being dispatched', Error); E('ERR_FALSY_VALUE_REJECTION', function(reason) { diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index b73ba2eb3c8154..5919faa56afa9c 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -11,16 +11,18 @@ const { } = primordials; const { - ERR_ESM_LOADER_REGISTRATION_UNAVAILABLE, ERR_UNKNOWN_MODULE_FORMAT, } = require('internal/errors').codes; const { getOptionValue } = require('internal/options'); const { pathToFileURL } = require('internal/url'); -const { emitExperimentalWarning } = require('internal/util'); +const { emitExperimentalWarning, kEmptyObject } = require('internal/util'); const { getDefaultConditions, } = require('internal/modules/esm/utils'); let defaultResolve, defaultLoad, importMetaInitializer; +let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { + debug = fn; +}); function newModuleMap() { const ModuleMap = require('internal/modules/esm/module_map'); @@ -106,6 +108,7 @@ class DefaultModuleLoader { setCallbackForWrap(module, { initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }), importModuleDynamically: (specifier, { url }, importAssertions) => { + debug('importModuleDynamically: %o', { specifier, url, Loader: this.constructor.name }); return this.import(specifier, url, importAssertions); }, }); @@ -113,8 +116,7 @@ class DefaultModuleLoader { return module; }; const ModuleJob = require('internal/modules/esm/module_job'); - const job = new ModuleJob( - this, url, undefined, evalInstance, false, false); + const job = new ModuleJob(this, url, undefined, evalInstance, false, false); this.moduleMap.set(url, undefined, job); const { module } = await job.run(); @@ -285,10 +287,21 @@ class CustomizedModuleLoader extends DefaultModuleLoader { /** * Instantiate a module loader that uses user-provided custom loader hooks. */ - constructor() { + constructor({ + cjsCache, + evalIndex, + moduleMap, + } = kEmptyObject) { super(); - getHooksProxy(); + if (cjsCache != null) { this.cjsCache = cjsCache; } + if (evalIndex != null) { this.evalIndex = evalIndex; } + if (moduleMap != null) { this.moduleMap = moduleMap; } + + if (!hooksProxy) { + const { HooksProxy } = require('internal/modules/esm/hooks'); + hooksProxy = new HooksProxy(); + } } /** @@ -357,40 +370,29 @@ let emittedExperimentalWarning = false; * A loader instance is used as the main entry point for loading ES modules. Currently, this is a singleton; there is * only one used for loading the main module and everything in its dependency graph, though separate instances of this * class might be instantiated as part of bootstrap for other purposes. - * @param {boolean} useCustomLoadersIfPresent If the user has provided loaders via the --loader flag, use them. + * @param {boolean} forceCustomizedLoaderInMain Ignore whether custom loader(s) have been provided + * via CLI and instantiate a CustomizedModuleLoader instance regardless. * @returns {DefaultModuleLoader | CustomizedModuleLoader} */ -function createModuleLoader(useCustomLoadersIfPresent = true) { - if (useCustomLoadersIfPresent && - // Don't spawn a new worker if we're already in a worker thread created by instantiating CustomizedModuleLoader; - // doing so would cause an infinite loop. - !require('internal/modules/esm/utils').isLoaderWorker()) { - const userLoaderPaths = getOptionValue('--experimental-loader'); - if (userLoaderPaths.length > 0) { +function createModuleLoader(customizationSetup) { + // Don't spawn a new worker if we're already in a worker thread (doing so would cause an infinite loop). + if (!require('internal/modules/esm/utils').isLoaderWorker()) { + if ( + customizationSetup || + getOptionValue('--experimental-loader').length > 0 + ) { if (!emittedExperimentalWarning) { emitExperimentalWarning('Custom ESM Loaders'); emittedExperimentalWarning = true; } - return new CustomizedModuleLoader(); + debug('instantiating CustomizedModuleLoader'); + return new CustomizedModuleLoader(customizationSetup); } } return new DefaultModuleLoader(); } -/** - * Get the HooksProxy instance. If it is not defined, then create a new one. - * @returns {HooksProxy} - */ -function getHooksProxy() { - if (!hooksProxy) { - const { HooksProxy } = require('internal/modules/esm/hooks'); - hooksProxy = new HooksProxy(); - } - - return hooksProxy; -} - /** * Register a single loader programmatically. * @param {string} specifier @@ -405,12 +407,13 @@ function getHooksProxy() { * ``` */ function register(specifier, parentURL = 'data:') { - // TODO: Remove this limitation in a follow-up before `register` is released publicly - if (getOptionValue('--experimental-loader').length < 1) { - throw new ERR_ESM_LOADER_REGISTRATION_UNAVAILABLE(); - } + let moduleLoader = require('internal/process/esm_loader').esmLoader; - const moduleLoader = require('internal/process/esm_loader').esmLoader; + if (!(moduleLoader instanceof CustomizedModuleLoader)) { + debug('register called on DefaultModuleLoader; switching to CustomizedModuleLoader'); + moduleLoader = createModuleLoader(moduleLoader); + require('internal/process/esm_loader').esmLoader = moduleLoader; + } moduleLoader.register(`${specifier}`, parentURL); } @@ -418,6 +421,5 @@ function register(specifier, parentURL = 'data:') { module.exports = { DefaultModuleLoader, createModuleLoader, - getHooksProxy, register, }; diff --git a/lib/internal/process/esm_loader.js b/lib/internal/process/esm_loader.js index 9a84ed944e87c4..8d01b1870e6dd1 100644 --- a/lib/internal/process/esm_loader.js +++ b/lib/internal/process/esm_loader.js @@ -11,15 +11,21 @@ const { } = require('internal/process/execution'); const { pathToFileURL } = require('internal/url'); const { kEmptyObject } = require('internal/util'); +let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { + debug = fn; +}); let esmLoader; module.exports = { get esmLoader() { - return esmLoader ??= createModuleLoader(true); + return esmLoader ??= createModuleLoader(); + }, + set esmLoader(supplantingLoader) { + if (supplantingLoader) esmLoader = supplantingLoader; }, async loadESM(callback) { - esmLoader ??= createModuleLoader(true); + esmLoader ??= createModuleLoader(); try { const userImports = getOptionValue('--import'); if (userImports.length > 0) { diff --git a/test/es-module/test-esm-loader-programmatically.mjs b/test/es-module/test-esm-loader-programmatically.mjs index 0c20bbcb7519f8..26d18c6b53f93c 100644 --- a/test/es-module/test-esm-loader-programmatically.mjs +++ b/test/es-module/test-esm-loader-programmatically.mjs @@ -189,19 +189,26 @@ describe('ESM: programmatically register loaders', { concurrency: true }, () => assert.strictEqual(lines[3], ''); }); - it('does not work without dummy CLI loader', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--input-type=module', - '--eval', - "import { register } from 'node:module';" + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); + it('works without a CLI flag', async () => { + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ + '--no-warnings', + '--input-type=module', + '--eval', + "import { register } from 'node:module';" + + commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs')) + + commonEvals.dynamicImport('console.log("Hello from dynamic import");'), + ]); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - assert.match(stderr, /ERR_ESM_LOADER_REGISTRATION_UNAVAILABLE/); + assert.strictEqual(stderr, ''); + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + + const lines = stdout.split('\n'); + + assert.match(lines[0], /load passthru/); + assert.match(lines[1], /Hello from dynamic import/); + + assert.strictEqual(lines[2], ''); }); it('does not work with a loader specifier that does not exist', async () => { From d4a5dd8d777ef2aabf8d8703087498f7bd5160f0 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Tue, 13 Jun 2023 00:35:57 +0200 Subject: [PATCH 02/13] WIP: disentangle `loader` from `ModuleJob` --- lib/internal/modules/esm/loader.js | 63 +++++++++---------------- lib/internal/modules/esm/module_job.js | 62 +++++++++++++++--------- lib/internal/modules/esm/translators.js | 9 ---- 3 files changed, 64 insertions(+), 70 deletions(-) diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 5919faa56afa9c..4ed9371a09ee2d 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -101,22 +101,10 @@ class DefaultModuleLoader { source, url = pathToFileURL(`${process.cwd()}/[eval${++this.evalIndex}]`).href, ) { - const evalInstance = (url) => { - const { ModuleWrap } = internalBinding('module_wrap'); - const { setCallbackForWrap } = require('internal/modules/esm/utils'); - const module = new ModuleWrap(url, undefined, source, 0, 0); - setCallbackForWrap(module, { - initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }), - importModuleDynamically: (specifier, { url }, importAssertions) => { - debug('importModuleDynamically: %o', { specifier, url, Loader: this.constructor.name }); - return this.import(specifier, url, importAssertions); - }, - }); - - return module; - }; + const { ModuleWrap } = internalBinding('module_wrap'); + const moduleWrapper = new ModuleWrap(url, undefined, source, 0, 0); const ModuleJob = require('internal/modules/esm/module_job'); - const job = new ModuleJob(this, url, undefined, evalInstance, false, false); + const job = new ModuleJob(url, moduleWrapper, undefined, false, false); this.moduleMap.set(url, undefined, job); const { module } = await job.run(); @@ -155,9 +143,7 @@ class DefaultModuleLoader { this.moduleMap.set(url, undefined, job = job()); } - if (job === undefined) { - job = this.#createModuleJob(url, resolvedImportAssertions, parentURL, format); - } + job ??= this.#createModuleJob(url, resolvedImportAssertions, parentURL, format); return job; } @@ -173,26 +159,7 @@ class DefaultModuleLoader { * `resolve` hook * @returns {Promise} The (possibly pending) module job */ - #createModuleJob(url, importAssertions, parentURL, format) { - const moduleProvider = async (url, isMain) => { - const { - format: finalFormat, - responseURL, - source, - } = await this.load(url, { - format, - importAssertions, - }); - - const translator = getTranslators().get(finalFormat); - - if (!translator) { - throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat, responseURL); - } - - return FunctionPrototypeCall(translator, this, responseURL, source, isMain); - }; - + async #createModuleJob(url, importAssertions, parentURL, format) { const inspectBrk = ( parentURL === undefined && getOptionValue('--inspect-brk') @@ -202,12 +169,28 @@ class DefaultModuleLoader { process.send({ 'watch:import': [url] }); } + const { + format: finalFormat, + responseURL, + source, + } = await this.load(url, { + format, + importAssertions, + }); + + const translator = getTranslators().get(finalFormat); + + if (!translator) { + throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat, responseURL); + } + + const moduleWrapper = FunctionPrototypeCall(translator, this, responseURL, source, isMain); + const ModuleJob = require('internal/modules/esm/module_job'); const job = new ModuleJob( - this, url, + moduleWrapper, importAssertions, - moduleProvider, parentURL === undefined, inspectBrk, ); diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 2cf2813a6dcf7f..309fc95dc2eed8 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -8,7 +8,6 @@ const { ObjectSetPrototypeOf, PromiseResolve, PromisePrototypeThen, - ReflectApply, RegExpPrototypeExec, RegExpPrototypeSymbolReplace, SafePromiseAllReturnArrayLike, @@ -20,6 +19,7 @@ const { } = primordials; const { ModuleWrap } = internalBinding('module_wrap'); +const { setCallbackForWrap } = require('internal/modules/esm/utils'); const { decorateErrorStack } = require('internal/util'); const { @@ -27,6 +27,9 @@ const { } = require('internal/source_map/source_map_cache'); const assert = require('internal/assert'); const resolvedPromise = PromiseResolve(); +let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { + debug = fn; +}); const noop = FunctionPrototype; @@ -45,22 +48,43 @@ const isCommonJSGlobalLikeNotDefinedError = (errorMessage) => (globalLike) => errorMessage === `${globalLike} is not defined`, ); -/* A ModuleJob tracks the loading of a single Module, and the ModuleJobs of - * its dependencies, over time. */ +/** + * A ModuleJob tracks the loading of a single Module, and the ModuleJobs of its dependencies, over + * time. + */ class ModuleJob { - // `loader` is the Loader instance used for loading dependencies. - // `moduleProvider` is a function - constructor(loader, url, importAssertions = { __proto__: null }, - moduleProvider, isMain, inspectBrk) { - this.loader = loader; + /** + * Deep dependency jobs wrappers are instantiated, and module wrapper is instantiated. + */ + instantiated; + + module; + + constructor( + url, + moduleWrapper, + importAssertions = { __proto__: null }, + isMain, + inspectBrk, + ) { this.importAssertions = importAssertions; this.isMain = isMain; this.inspectBrk = inspectBrk; + this.modulePromise = moduleWrapper; + + debug('new ModuleJob(%o)', { url, importAssertions, isMain, inspectBrk }); - this.module = undefined; // Expose the promise to the ModuleWrap directly for linking below. // `this.module` is also filled in below. - this.modulePromise = ReflectApply(moduleProvider, loader, [url, isMain]); + + setCallbackForWrap(this.modulePromise, { + initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }), + importModuleDynamically: (specifier, { url }, importAssertions) => { + const moduleLoader = require('internal/process/esm_loader').esmLoader; + debug('importModuleDynamically: %o', { specifier, url, moduleLoader }); + return moduleLoader.import(specifier, url, importAssertions); + }, + }); // Wait for the ModuleWrap instance being linked with all dependencies. const link = async () => { @@ -73,7 +97,8 @@ class ModuleJob { // these `link` callbacks depending on each other. const dependencyJobs = []; const promises = this.module.link((specifier, assertions) => { - const job = this.loader.getModuleJob(specifier, url, assertions); + const moduleLoader = require('internal/process/esm_loader').esmLoader; + const job = moduleLoader.getModuleJob(specifier, url, assertions); ArrayPrototypePush(dependencyJobs, job); return job.modulePromise; }); @@ -88,16 +113,11 @@ class ModuleJob { // This promise is awaited later anyway, so silence // 'unhandled rejection' warnings. PromisePrototypeThen(this.linked, undefined, noop); - - // instantiated == deep dependency jobs wrappers are instantiated, - // and module wrapper is instantiated. - this.instantiated = undefined; } instantiate() { - if (this.instantiated === undefined) { - this.instantiated = this._instantiate(); - } + this.instantiated ??= this._instantiate(); + return this.instantiated; } @@ -139,7 +159,8 @@ class ModuleJob { const { 1: childSpecifier, 2: name } = RegExpPrototypeExec( /module '(.*)' does not provide an export named '(.+)'/, e.message); - const { url: childFileURL } = await this.loader.resolve( + const moduleLoader = require('internal/process/esm_loader').esmLoader; + const { url: childFileURL } = await moduleLoader.resolve( childSpecifier, parentFileUrl, ); let format; @@ -147,8 +168,7 @@ class ModuleJob { // This might throw for non-CommonJS modules because we aren't passing // in the import assertions and some formats require them; but we only // care about CommonJS for the purposes of this error message. - ({ format } = - await this.loader.load(childFileURL)); + ({ format } = await moduleLoader.load(childFileURL)); } catch { // Continue regardless of error. } diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 267d89f1d44730..6b87d5c5d12867 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -103,10 +103,6 @@ function errPath(url) { return url; } -async function importModuleDynamically(specifier, { url }, assertions) { - return asyncESM.esmLoader.import(specifier, url, assertions); -} - // Strategy for loading a standard JavaScript module. translators.set('module', async function moduleStrategy(url, source, isMain) { assertBufferSource(source, true, 'load'); @@ -114,11 +110,6 @@ translators.set('module', async function moduleStrategy(url, source, isMain) { maybeCacheSourceMap(url, source); debug(`Translating StandardModule ${url}`); const module = new ModuleWrap(url, undefined, source, 0, 0); - const { setCallbackForWrap } = require('internal/modules/esm/utils'); - setCallbackForWrap(module, { - initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }), - importModuleDynamically, - }); return module; }); From 378c5a598a5592ca215658789cf782278fc16fe4 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Thu, 15 Jun 2023 22:21:11 +0200 Subject: [PATCH 03/13] WIP: continue to detangle ModuleJob & ESMLoader --- lib/internal/modules/esm/loader.js | 32 ++++++++++++++++-------------- lib/internal/modules/run_main.js | 5 +++-- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 4ed9371a09ee2d..a9144cb264434b 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -132,18 +132,21 @@ class DefaultModuleLoader { return this.getJobFromResolveResult(resolveResult, parentURL, importAssertions); } - getJobFromResolveResult(resolveResult, parentURL, importAssertions) { + getJobFromResolveResult( + resolveResult, + parentURL, + importAssertions = resolveResult.importAssertions, + ) { const { url, format } = resolveResult; - const resolvedImportAssertions = resolveResult.importAssertions ?? importAssertions; - - let job = this.moduleMap.get(url, resolvedImportAssertions.type); +debug('getModuleJob', { url, format, parentURL, importAssertions, moduleMap: this.moduleMap }); + let job = this.moduleMap.get(url, importAssertions.type); // CommonJS will set functions for lazy job evaluation. if (typeof job === 'function') { this.moduleMap.set(url, undefined, job = job()); } - job ??= this.#createModuleJob(url, resolvedImportAssertions, parentURL, format); + job ??= this.#createModuleJob(url, parentURL, importAssertions, format); return job; } @@ -151,15 +154,13 @@ class DefaultModuleLoader { /** * Create and cache an object representing a loaded module. * @param {string} url The absolute URL that was resolved for this module - * @param {Record} importAssertions Validations for the - * module import. - * @param {string} [parentURL] The absolute URL of the module importing this - * one, unless this is the Node.js entry point - * @param {string} [format] The format hint possibly returned by the - * `resolve` hook + * @param {string} [parentURL] The absolute URL of the module importing this one, unless this is + * the Node.js entry point + * @param {Record} importAssertions Validations for the module import. + * @param {string} [format] The format hint possibly returned by the `resolve` hook * @returns {Promise} The (possibly pending) module job */ - async #createModuleJob(url, importAssertions, parentURL, format) { + async #createModuleJob(url, parentURL, importAssertions, format) { const inspectBrk = ( parentURL === undefined && getOptionValue('--inspect-brk') @@ -184,6 +185,7 @@ class DefaultModuleLoader { throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat, responseURL); } + const isMain = parentURL === undefined; const moduleWrapper = FunctionPrototypeCall(translator, this, responseURL, source, isMain); const ModuleJob = require('internal/modules/esm/module_job'); @@ -191,7 +193,7 @@ class DefaultModuleLoader { url, moduleWrapper, importAssertions, - parentURL === undefined, + isMain, inspectBrk, ); @@ -209,8 +211,8 @@ class DefaultModuleLoader { * module import. * @returns {Promise} */ - async import(specifier, parentURL, importAssertions) { - const moduleJob = this.getModuleJob(specifier, parentURL, importAssertions); + async import(specifier, parentURL = undefined, importAssertions = { __proto__: null }) { + const moduleJob = await this.getModuleJob(specifier, parentURL, importAssertions); const { module } = await moduleJob.run(); return module.getNamespace(); } diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index 0bfe7b11241416..4c522fd579d05b 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -52,8 +52,9 @@ function runMainESM(mainPath) { handleMainPromise(loadESM((esmLoader) => { const main = path.isAbsolute(mainPath) ? - pathToFileURL(mainPath).href : mainPath; - return esmLoader.import(main, undefined, { __proto__: null }); + pathToFileURL(mainPath).href : + mainPath; + return esmLoader.import(main); })); } From d2ad85b6fb090fab853fe038b7cde8939304ef9c Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sun, 18 Jun 2023 13:32:46 +0200 Subject: [PATCH 04/13] WIP: add debugs --- lib/internal/modules/esm/module_job.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 309fc95dc2eed8..743f8dc5153809 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -72,7 +72,7 @@ class ModuleJob { this.inspectBrk = inspectBrk; this.modulePromise = moduleWrapper; - debug('new ModuleJob(%o)', { url, importAssertions, isMain, inspectBrk }); + debug('ModuleJob::constructor(%o)', { url, importAssertions, isMain, inspectBrk }); // Expose the promise to the ModuleWrap directly for linking below. // `this.module` is also filled in below. @@ -81,7 +81,7 @@ class ModuleJob { initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }), importModuleDynamically: (specifier, { url }, importAssertions) => { const moduleLoader = require('internal/process/esm_loader').esmLoader; - debug('importModuleDynamically: %o', { specifier, url, moduleLoader }); + debug('ModuleJob::importModuleDynamically(%o)', { specifier, url, moduleLoader }); return moduleLoader.import(specifier, url, importAssertions); }, }); @@ -90,21 +90,25 @@ class ModuleJob { const link = async () => { this.module = await this.modulePromise; assert(this.module instanceof ModuleWrap); + debug('ModuleJob…link(): %o', { url, module: this.module }); // 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 = []; + debug('this.module.link: %o', { [url]: this.module.link.toString() }); const promises = this.module.link((specifier, assertions) => { + debug('this.module.link(%o)', { specifier, assertions }); const moduleLoader = require('internal/process/esm_loader').esmLoader; + debug('this.module.link() moduleLoader: %s', moduleLoader.constructor.name); const job = moduleLoader.getModuleJob(specifier, url, assertions); + debug('this.module.link() job: %o', job); ArrayPrototypePush(dependencyJobs, job); return job.modulePromise; }); - if (promises !== undefined) - await SafePromiseAllReturnVoid(promises); + if (promises !== undefined) await SafePromiseAllReturnVoid(promises); return SafePromiseAllReturnArrayLike(dependencyJobs); }; From 02c6090f6528f40fc2fe4ab26192d2aac57f365d Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sun, 18 Jun 2023 19:48:56 +0200 Subject: [PATCH 05/13] WIP: await getting module job (now always async) --- lib/internal/modules/esm/module_job.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 743f8dc5153809..a1671989a4afe2 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -98,11 +98,11 @@ class ModuleJob { // these `link` callbacks depending on each other. const dependencyJobs = []; debug('this.module.link: %o', { [url]: this.module.link.toString() }); - const promises = this.module.link((specifier, assertions) => { + const promises = this.module.link(async (specifier, assertions) => { debug('this.module.link(%o)', { specifier, assertions }); const moduleLoader = require('internal/process/esm_loader').esmLoader; debug('this.module.link() moduleLoader: %s', moduleLoader.constructor.name); - const job = moduleLoader.getModuleJob(specifier, url, assertions); + const job = await moduleLoader.getModuleJob(specifier, url, assertions); debug('this.module.link() job: %o', job); ArrayPrototypePush(dependencyJobs, job); return job.modulePromise; From cb72d94db42a7168f4ced107a74c8624e8068465 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sun, 18 Jun 2023 19:49:17 +0200 Subject: [PATCH 06/13] WIP: move initialize import.meta out of module loader --- .../modules/esm/initialize_import_meta.js | 22 ++++++++++++------- lib/internal/modules/esm/loader.js | 16 +++++--------- lib/internal/modules/esm/module_job.js | 16 ++++++++++++-- lib/internal/modules/esm/translators.js | 3 +-- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/lib/internal/modules/esm/initialize_import_meta.js b/lib/internal/modules/esm/initialize_import_meta.js index c548f71bef837a..b1ced8c3d9909d 100644 --- a/lib/internal/modules/esm/initialize_import_meta.js +++ b/lib/internal/modules/esm/initialize_import_meta.js @@ -2,19 +2,26 @@ const { getOptionValue } = require('internal/options'); const experimentalImportMetaResolve = getOptionValue('--experimental-import-meta-resolve'); +let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { + debug = fn; +}); /** * Generate a function to be used as import.meta.resolve for a particular module. * @param {string} defaultParentUrl The default base to use for resolution - * @param {typeof import('./loader.js').ModuleLoader} loader Reference to the current module loader * @returns {(specifier: string, parentUrl?: string) => string} Function to assign to import.meta.resolve */ -function createImportMetaResolve(defaultParentUrl, loader) { +function createImportMetaResolve(defaultParentUrl) { + debug('createImportMetaResolve(): %o', { defaultParentUrl }); + return function resolve(specifier, parentUrl = defaultParentUrl) { + const moduleLoader = require('internal/process/esm_loader').esmLoader; let url; + debug('import.meta.resolve(%o)', { [specifier]: moduleLoader.constructor.name }); + try { - ({ url } = loader.resolve(specifier, parentUrl)); + ({ url } = moduleLoader.resolve(specifier, parentUrl)); } catch (error) { if (error?.code === 'ERR_UNSUPPORTED_DIR_IMPORT') { ({ url } = error); @@ -31,15 +38,14 @@ function createImportMetaResolve(defaultParentUrl, loader) { * Create the `import.meta` object for a module. * @param {object} meta * @param {{url: string}} context - * @param {typeof import('./loader.js').ModuleLoader} loader Reference to the current module loader * @returns {{url: string, resolve?: Function}} */ -function initializeImportMeta(meta, context, loader) { - const { url } = context; +function initializeImportMeta(meta, { url }) { + debug('initializeImportMeta(): %o', { url, moduleLoader: moduleLoader.constructor.name }) // Alphabetical - if (experimentalImportMetaResolve && loader.loaderType !== 'internal') { - meta.resolve = createImportMetaResolve(url, loader); + if (experimentalImportMetaResolve) { + meta.resolve = createImportMetaResolve(url); } meta.url = url; diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index a9144cb264434b..6922d0b1e599f5 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -19,7 +19,8 @@ const { emitExperimentalWarning, kEmptyObject } = require('internal/util'); const { getDefaultConditions, } = require('internal/modules/esm/utils'); -let defaultResolve, defaultLoad, importMetaInitializer; +let defaultResolve; +let defaultLoad; let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { debug = fn; }); @@ -138,7 +139,7 @@ class DefaultModuleLoader { importAssertions = resolveResult.importAssertions, ) { const { url, format } = resolveResult; -debug('getModuleJob', { url, format, parentURL, importAssertions, moduleMap: this.moduleMap }); +debug('getJobFromResolveResult:', { [url]: { format, moduleMap: this.moduleMap } }); let job = this.moduleMap.get(url, importAssertions.type); // CommonJS will set functions for lazy job evaluation. @@ -186,6 +187,7 @@ debug('getModuleJob', { url, format, parentURL, importAssertions, moduleMap: thi } const isMain = parentURL === undefined; + debug('#createModuleJob', { url, finalFormat, responseURL, source }); const moduleWrapper = FunctionPrototypeCall(translator, this, responseURL, source, isMain); const ModuleJob = require('internal/modules/esm/module_job'); @@ -258,12 +260,6 @@ debug('getModuleJob', { url, format, parentURL, importAssertions, moduleMap: thi require('internal/modules/esm/load').throwUnknownModuleFormat(url, format); } } - - importMetaInitialize(meta, context) { - importMetaInitializer ??= require('internal/modules/esm/initialize_import_meta').initializeImportMeta; - meta = importMetaInitializer(meta, context, this); - return meta; - } } ObjectSetPrototypeOf(DefaultModuleLoader.prototype, null); @@ -316,12 +312,12 @@ class CustomizedModuleLoader extends DefaultModuleLoader { async #getModuleJob(specifier, parentURL, importAssertions) { const resolveResult = await hooksProxy.makeAsyncRequest('resolve', specifier, parentURL, importAssertions); - +debug('#getModuleJob', { [specifier]: resolveResult }) return this.getJobFromResolveResult(resolveResult, parentURL, importAssertions); } getModuleJob(specifier, parentURL, importAssertions) { const jobPromise = this.#getModuleJob(specifier, parentURL, importAssertions); - +debug('getModuleJob', { [specifier]: jobPromise }) return { run() { return PromisePrototypeThen(jobPromise, (job) => job.run()); diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index a1671989a4afe2..b24cb0006bb2b1 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -34,6 +34,7 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { const noop = FunctionPrototype; let hasPausedEntry = false; +let importMetaInitializer; const CJSGlobalLike = [ 'require', @@ -60,6 +61,14 @@ class ModuleJob { module; + /** + * + * @param {URL['href']} url The resolved url of the module. + * @param {ModuleWrap} moduleWrapper + * @param {*} importAssertions + * @param {boolean} isMain Whether this is the main module. + * @param {boolean} inspectBrk Whether to invoke the inspector and break on start. + */ constructor( url, moduleWrapper, @@ -78,8 +87,11 @@ class ModuleJob { // `this.module` is also filled in below. setCallbackForWrap(this.modulePromise, { - initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }), - importModuleDynamically: (specifier, { url }, importAssertions) => { + initializeImportMeta(meta, wrap) { + importMetaInitializer ??= require('internal/modules/esm/initialize_import_meta').initializeImportMeta; + importMetaInitializer(meta, { url }); + }, + importModuleDynamically(specifier, { url }, importAssertions) { const moduleLoader = require('internal/process/esm_loader').esmLoader; debug('ModuleJob::importModuleDynamically(%o)', { specifier, url, moduleLoader }); return moduleLoader.import(specifier, url, importAssertions); diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 6b87d5c5d12867..95933aa6ecd478 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -133,8 +133,7 @@ function enrichCJSError(err, content, filename) { // Strategy for loading a node-style CommonJS module const isWindows = process.platform === 'win32'; -translators.set('commonjs', async function commonjsStrategy(url, source, - isMain) { +translators.set('commonjs', async function commonjsStrategy(url, source, isMain) { debug(`Translating CJSModule ${url}`); const filename = fileURLToPath(new URL(url)); From 77e91dc5543dc6c85b21b9f131d48eaf68edeaae Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sun, 18 Jun 2023 20:23:08 +0200 Subject: [PATCH 07/13] WIP: tweak debugs --- lib/internal/modules/esm/initialize_import_meta.js | 4 ++-- lib/internal/modules/esm/module_job.js | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/internal/modules/esm/initialize_import_meta.js b/lib/internal/modules/esm/initialize_import_meta.js index b1ced8c3d9909d..c36ef3514ea2c6 100644 --- a/lib/internal/modules/esm/initialize_import_meta.js +++ b/lib/internal/modules/esm/initialize_import_meta.js @@ -18,7 +18,7 @@ function createImportMetaResolve(defaultParentUrl) { const moduleLoader = require('internal/process/esm_loader').esmLoader; let url; - debug('import.meta.resolve(%o)', { [specifier]: moduleLoader.constructor.name }); + debug('import.meta.resolve(%o) "%s"', { specifier, parentUrl }, moduleLoader.constructor.name); try { ({ url } = moduleLoader.resolve(specifier, parentUrl)); @@ -41,7 +41,7 @@ function createImportMetaResolve(defaultParentUrl) { * @returns {{url: string, resolve?: Function}} */ function initializeImportMeta(meta, { url }) { - debug('initializeImportMeta(): %o', { url, moduleLoader: moduleLoader.constructor.name }) + debug('initializeImportMeta(): %o', { [url]: moduleLoader.constructor.name }) // Alphabetical if (experimentalImportMetaResolve) { diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index b24cb0006bb2b1..c5a1c2e23193e2 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -164,8 +164,7 @@ class ModuleJob { // stack trace originates in module_job, not the file itself. A hidden // symbol with filename could be set in node_errors.cc to facilitate this. if (!getSourceMapsEnabled() && - StringPrototypeIncludes(e.message, - ' does not provide an export named')) { + StringPrototypeIncludes(e.message, ' does not provide an export named')) { const splitStack = StringPrototypeSplit(e.stack, '\n'); const parentFileUrl = RegExpPrototypeSymbolReplace( /:\d+$/, @@ -174,11 +173,10 @@ class ModuleJob { ); const { 1: childSpecifier, 2: name } = RegExpPrototypeExec( /module '(.*)' does not provide an export named '(.+)'/, - e.message); - const moduleLoader = require('internal/process/esm_loader').esmLoader; - const { url: childFileURL } = await moduleLoader.resolve( - childSpecifier, parentFileUrl, + e.message, ); + const moduleLoader = require('internal/process/esm_loader').esmLoader; + const { url: childFileURL } = await moduleLoader.resolve(childSpecifier, parentFileUrl); let format; try { // This might throw for non-CommonJS modules because we aren't passing From 582f50a5b2a9d302ebc099cdc3103410e6aa7415 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sun, 18 Jun 2023 21:26:04 +0200 Subject: [PATCH 08/13] =?UTF-8?q?correct=20ModuleJob(`url`=20=E2=86=92=20`?= =?UTF-8?q?responseURL`)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/internal/modules/esm/loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 6922d0b1e599f5..f60a53371e30e4 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -192,7 +192,7 @@ debug('getJobFromResolveResult:', { [url]: { format, moduleMap: this.moduleMap } const ModuleJob = require('internal/modules/esm/module_job'); const job = new ModuleJob( - url, + responseURL, moduleWrapper, importAssertions, isMain, From ec5d529d899c9252525f2874bc636e5a89ef0403 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Sun, 18 Jun 2023 21:28:05 +0200 Subject: [PATCH 09/13] move `setCallbackForWrap` back into `translator('module')` --- .../modules/esm/initialize_import_meta.js | 4 ++-- lib/internal/modules/esm/module_job.js | 17 ----------------- lib/internal/modules/esm/translators.js | 16 +++++++++++++++- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/lib/internal/modules/esm/initialize_import_meta.js b/lib/internal/modules/esm/initialize_import_meta.js index c36ef3514ea2c6..0bc6259234ebc2 100644 --- a/lib/internal/modules/esm/initialize_import_meta.js +++ b/lib/internal/modules/esm/initialize_import_meta.js @@ -18,7 +18,7 @@ function createImportMetaResolve(defaultParentUrl) { const moduleLoader = require('internal/process/esm_loader').esmLoader; let url; - debug('import.meta.resolve(%o) "%s"', { specifier, parentUrl }, moduleLoader.constructor.name); + debug('import.meta.resolve(%o) %s', { specifier, parentUrl }, moduleLoader.constructor.name); try { ({ url } = moduleLoader.resolve(specifier, parentUrl)); @@ -41,7 +41,7 @@ function createImportMetaResolve(defaultParentUrl) { * @returns {{url: string, resolve?: Function}} */ function initializeImportMeta(meta, { url }) { - debug('initializeImportMeta(): %o', { [url]: moduleLoader.constructor.name }) + debug('initializeImportMeta for %s', url); // Alphabetical if (experimentalImportMetaResolve) { diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index c5a1c2e23193e2..816069643ed154 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -19,7 +19,6 @@ const { } = primordials; const { ModuleWrap } = internalBinding('module_wrap'); -const { setCallbackForWrap } = require('internal/modules/esm/utils'); const { decorateErrorStack } = require('internal/util'); const { @@ -34,7 +33,6 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { const noop = FunctionPrototype; let hasPausedEntry = false; -let importMetaInitializer; const CJSGlobalLike = [ 'require', @@ -83,21 +81,6 @@ class ModuleJob { debug('ModuleJob::constructor(%o)', { url, importAssertions, isMain, inspectBrk }); - // Expose the promise to the ModuleWrap directly for linking below. - // `this.module` is also filled in below. - - setCallbackForWrap(this.modulePromise, { - initializeImportMeta(meta, wrap) { - importMetaInitializer ??= require('internal/modules/esm/initialize_import_meta').initializeImportMeta; - importMetaInitializer(meta, { url }); - }, - importModuleDynamically(specifier, { url }, importAssertions) { - const moduleLoader = require('internal/process/esm_loader').esmLoader; - debug('ModuleJob::importModuleDynamically(%o)', { specifier, url, moduleLoader }); - return moduleLoader.import(specifier, url, importAssertions); - }, - }); - // Wait for the ModuleWrap instance being linked with all dependencies. const link = async () => { this.module = await this.modulePromise; diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 95933aa6ecd478..39659284220e49 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -103,13 +103,27 @@ function errPath(url) { return url; } +let importMetaInitializer; + // Strategy for loading a standard JavaScript module. translators.set('module', async function moduleStrategy(url, source, isMain) { assertBufferSource(source, true, 'load'); source = stringify(source); maybeCacheSourceMap(url, source); - debug(`Translating StandardModule ${url}`); + debug(`Translating ESModule ${url}`); const module = new ModuleWrap(url, undefined, source, 0, 0); + const { setCallbackForWrap } = require('internal/modules/esm/utils'); + setCallbackForWrap(module, { + initializeImportMeta(meta, wrap) { + importMetaInitializer ??= require('internal/modules/esm/initialize_import_meta').initializeImportMeta; + importMetaInitializer(meta, { url }); + }, + importModuleDynamically(specifier, { url }, importAssertions) { + const moduleLoader = require('internal/process/esm_loader').esmLoader; + debug('Translator("module")::importModuleDynamically(%o)', { specifier, url, moduleLoader }); + return moduleLoader.import(specifier, url, importAssertions); + }, + }); return module; }); From e6e8cacb0990fee2513222bb4638282b2cf91a19 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Mon, 19 Jun 2023 10:10:11 +0200 Subject: [PATCH 10/13] fix: restore `setCallbackForWrap` in `ModuleLoader::eval()` --- lib/internal/modules/esm/loader.js | 20 +++++++++++++++++++- lib/internal/modules/esm/utils.js | 5 ++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index f60a53371e30e4..c3da331fb118ea 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -104,8 +104,26 @@ class DefaultModuleLoader { ) { const { ModuleWrap } = internalBinding('module_wrap'); const moduleWrapper = new ModuleWrap(url, undefined, source, 0, 0); + const { setCallbackForWrap } = require('internal/modules/esm/utils'); + setCallbackForWrap(moduleWrapper, { + initializeImportMeta(meta, wrap) { + importMetaInitializer ??= require('internal/modules/esm/initialize_import_meta').initializeImportMeta; + importMetaInitializer(meta, { url }); + }, + importModuleDynamically(specifier, { url }, importAssertions) { + const moduleLoader = require('internal/process/esm_loader').esmLoader; + debug('ModuleLoader::eval::importModuleDynamically(%o)', { specifier, url, moduleLoader }); + return moduleLoader.import(specifier, url, importAssertions); + }, + }); const ModuleJob = require('internal/modules/esm/module_job'); - const job = new ModuleJob(url, moduleWrapper, undefined, false, false); + const job = new ModuleJob( + url, + moduleWrapper, + undefined, + false, // TODO: why isMain false in eval? it is the root module + false, + ); this.moduleMap.set(url, undefined, job); const { module } = await job.run(); diff --git a/lib/internal/modules/esm/utils.js b/lib/internal/modules/esm/utils.js index 4e919cd833011c..5e74958403007c 100644 --- a/lib/internal/modules/esm/utils.js +++ b/lib/internal/modules/esm/utils.js @@ -75,7 +75,7 @@ function initializeImportMetaObject(wrap, meta) { if (callbackMap.has(wrap)) { const { initializeImportMeta } = callbackMap.get(wrap); if (initializeImportMeta !== undefined) { - meta = initializeImportMeta(meta, getModuleFromWrap(wrap) || wrap); + meta = initializeImportMeta(meta, (getModuleFromWrap(wrap) || wrap)); } } } @@ -84,8 +84,7 @@ async function importModuleDynamicallyCallback(wrap, specifier, assertions) { if (callbackMap.has(wrap)) { const { importModuleDynamically } = callbackMap.get(wrap); if (importModuleDynamically !== undefined) { - return importModuleDynamically( - specifier, getModuleFromWrap(wrap) || wrap, assertions); + return importModuleDynamically(specifier, (getModuleFromWrap(wrap) || wrap), assertions); } } throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING(); From 3819d08f5886518d54e147f30012799fce53b843 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Mon, 19 Jun 2023 21:28:33 +0200 Subject: [PATCH 11/13] fix: restore gate of `import.meta.resolve` within loader worker --- lib/internal/modules/esm/initialize_import_meta.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/internal/modules/esm/initialize_import_meta.js b/lib/internal/modules/esm/initialize_import_meta.js index 0bc6259234ebc2..2188fea74d1a47 100644 --- a/lib/internal/modules/esm/initialize_import_meta.js +++ b/lib/internal/modules/esm/initialize_import_meta.js @@ -43,8 +43,10 @@ function createImportMetaResolve(defaultParentUrl) { function initializeImportMeta(meta, { url }) { debug('initializeImportMeta for %s', url); + const { isLoaderWorker } = require('internal/modules/esm/utils'); + // Alphabetical - if (experimentalImportMetaResolve) { + if (experimentalImportMetaResolve && !isLoaderWorker()) { meta.resolve = createImportMetaResolve(url); } From 9ff1de7c266aec726a54d1291877dad4ea00ce24 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Thu, 22 Jun 2023 23:51:05 +0200 Subject: [PATCH 12/13] fix: existing `job` detection --- lib/internal/modules/esm/loader.js | 22 +++++++++++++++------- lib/internal/modules/esm/module_job.js | 8 ++++++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index c3da331fb118ea..59aba4ec5f213f 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -151,22 +151,28 @@ class DefaultModuleLoader { return this.getJobFromResolveResult(resolveResult, parentURL, importAssertions); } - getJobFromResolveResult( + async getJobFromResolveResult( resolveResult, parentURL, importAssertions = resolveResult.importAssertions, ) { const { url, format } = resolveResult; -debug('getJobFromResolveResult:', { [url]: { format, moduleMap: this.moduleMap } }); + + // `job` may be a Promise, which the engine INSANELY _sometimes_ considers undefined and + // sometimes not. This is why we use `has` instead of `get` to check for its existence. + // ! Do NOT try to check against its value; the engine will **gladly** screw you over. + if (!this.moduleMap.has(url, importAssertions.type)) { + debug('getJobFromResolveResult: did NOT find existing entry for %s', url) + return this.#createModuleJob(url, parentURL, importAssertions, format); + } + let job = this.moduleMap.get(url, importAssertions.type); + debug('getJobFromResolveResult: found existing entry for %s %o', url, job); - // CommonJS will set functions for lazy job evaluation. - if (typeof job === 'function') { + if (typeof job === 'function') { // CommonJS will set functions for lazy job evaluation. this.moduleMap.set(url, undefined, job = job()); } - job ??= this.#createModuleJob(url, parentURL, importAssertions, format); - return job; } @@ -189,6 +195,8 @@ debug('getJobFromResolveResult:', { [url]: { format, moduleMap: this.moduleMap } process.send({ 'watch:import': [url] }); } + debug('#createModuleJob', { url, parentURL, importAssertions, format }) + const { format: finalFormat, responseURL, @@ -210,7 +218,7 @@ debug('getJobFromResolveResult:', { [url]: { format, moduleMap: this.moduleMap } const ModuleJob = require('internal/modules/esm/module_job'); const job = new ModuleJob( - responseURL, + url, // TODO: should this be resolvedURL? moduleWrapper, importAssertions, isMain, diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index 816069643ed154..f5d2bab5a8e6d2 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -94,9 +94,13 @@ class ModuleJob { const dependencyJobs = []; debug('this.module.link: %o', { [url]: this.module.link.toString() }); const promises = this.module.link(async (specifier, assertions) => { - debug('this.module.link(%o)', { specifier, assertions }); const moduleLoader = require('internal/process/esm_loader').esmLoader; - debug('this.module.link() moduleLoader: %s', moduleLoader.constructor.name); + debug('this.module.link(%o)', { + specifier, + parentURL: url, + assertions, + moduleLoader: moduleLoader.constructor.name + }); const job = await moduleLoader.getModuleJob(specifier, url, assertions); debug('this.module.link() job: %o', job); ArrayPrototypePush(dependencyJobs, job); From 6fa7dc7b3a2a22bb76389f983f07b771aa4db604 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Thu, 22 Jun 2023 23:57:40 +0200 Subject: [PATCH 13/13] WIP: investigate missing/tardy job entry in ModuleMap --- lib/internal/modules/esm/loader.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 59aba4ec5f213f..03f7a830ecb449 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -195,8 +195,10 @@ class DefaultModuleLoader { process.send({ 'watch:import': [url] }); } - debug('#createModuleJob', { url, parentURL, importAssertions, format }) + debug('#createModuleJob (before load)', { url, parentURL, importAssertions, format }) + // ! The module needing creation is not added to ModuleMap before the next `getModuleJob()`, + // ! which is a problem. const { format: finalFormat, responseURL, @@ -213,7 +215,7 @@ class DefaultModuleLoader { } const isMain = parentURL === undefined; - debug('#createModuleJob', { url, finalFormat, responseURL, source }); + debug('#createModuleJob (loaded, untranslated) %o', { url, finalFormat, responseURL, source }); const moduleWrapper = FunctionPrototypeCall(translator, this, responseURL, source, isMain); const ModuleJob = require('internal/modules/esm/module_job'); @@ -225,6 +227,8 @@ class DefaultModuleLoader { inspectBrk, ); + debug('#createModuleJob (translated) %o', { url, job }); + this.moduleMap.set(url, importAssertions.type, job); return job;