GeezerVison is showing github.com — module: allow ESM that failed to be required to be re-imported · nodejs/node@8d33f78
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/esm/module_job.js +20-4Lines changed: 20 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -21,7 +21,7 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { | 21 | 21 | debug = fn; | 22 | 22 | }); | 23 | 23 |
| 24 | | -const { ModuleWrap, kEvaluated } = internalBinding('module_wrap'); | | 24 | +const { ModuleWrap, kInstantiated } = internalBinding('module_wrap'); | 25 | 25 |
| 26 | 26 | const { decorateErrorStack, kEmptyObject } = require('internal/util'); | 27 | 27 | const { | @@ -346,10 +346,26 @@ class ModuleJobSync extends ModuleJobBase { | 346 | 346 | } | 347 | 347 |
| 348 | 348 | async run() { | | 349 | + // This path is hit by a require'd module that is imported again. | 349 | 350 | const status = this.module.getStatus(); | 350 | | - assert(status === kEvaluated, | 351 | | - `A require()-d module that is imported again must be evaluated. Status = ${status}`); | 352 | | - return { __proto__: null, module: this.module }; | | 351 | + if (status > kInstantiated) { | | 352 | + if (this.evaluationPromise) { | | 353 | + await this.evaluationPromise; | | 354 | + } | | 355 | + return { __proto__: null, module: this.module }; | | 356 | + } else if (status === kInstantiated) { | | 357 | + // The evaluation may have been canceled because instantiateSync() detected TLA first. | | 358 | + // But when it is imported again, it's fine to re-evaluate it asynchronously. | | 359 | + const timeout = -1; | | 360 | + const breakOnSigint = false; | | 361 | + this.evaluationPromise = this.module.evaluate(timeout, breakOnSigint); | | 362 | + await this.evaluationPromise; | | 363 | + this.evaluationPromise = undefined; | | 364 | + return { __proto__: null, module: this.module }; | | 365 | + } | | 366 | + | | 367 | + assert.fail('Unexpected status of a module that is imported again after being required. ' + | | 368 | + `Status = ${status}`); | 353 | 369 | } | 354 | 370 |
| 355 | 371 | runSync() { | |
Collapse file Copy file name to clipboard +35Lines changed: 35 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +// This tests that after failing to require an ESM that contains TLA, | | 2 | +// retrying with import() still works, and produces consistent results. | | 3 | +'use strict'; | | 4 | +const common = require('../common'); | | 5 | +const assert = require('assert'); | | 6 | + | | 7 | +const { exportedReject } = require('../fixtures/es-modules/tla/export-promise.mjs'); | | 8 | + | | 9 | +assert.throws(() => { | | 10 | + require('../fixtures/es-modules/tla/await-export-promise.mjs'); | | 11 | +}, { | | 12 | + code: 'ERR_REQUIRE_ASYNC_MODULE' | | 13 | +}); | | 14 | + | | 15 | +const interval = setInterval(() => {}, 1000); // Keep the test running, because await alone doesn't. | | 16 | +const err = new Error('rejected'); | | 17 | + | | 18 | +const p1 = import('../fixtures/es-modules/tla/await-export-promise.mjs') | | 19 | + .then(common.mustNotCall(), common.mustCall((e) => { | | 20 | + assert.strictEqual(e, err); | | 21 | + })); | | 22 | + | | 23 | +const p2 = import('../fixtures/es-modules/tla/await-export-promise.mjs') | | 24 | + .then(common.mustNotCall(), common.mustCall((e) => { | | 25 | + assert.strictEqual(e, err); | | 26 | + })); | | 27 | + | | 28 | +const p3 = import('../fixtures/es-modules/tla/await-export-promise.mjs') | | 29 | + .then(common.mustNotCall(), common.mustCall((e) => { | | 30 | + assert.strictEqual(e, err); | | 31 | + })); | | 32 | + | | 33 | +exportedReject(err); | | 34 | + | | 35 | +Promise.all([p1, p2, p3]).then(common.mustCall(() => { clearInterval(interval); })); |
Collapse file Copy file name to clipboard +32Lines changed: 32 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +// This tests that after failing to require an ESM that contains TLA, | | 2 | +// retrying with import() still works, and produces consistent results. | | 3 | +'use strict'; | | 4 | +const common = require('../common'); | | 5 | +const assert = require('assert'); | | 6 | + | | 7 | +const { exportedResolve } = require('../fixtures/es-modules/tla/export-promise.mjs'); | | 8 | + | | 9 | +assert.throws(() => { | | 10 | + require('../fixtures/es-modules/tla/await-export-promise.mjs'); | | 11 | +}, { | | 12 | + code: 'ERR_REQUIRE_ASYNC_MODULE' | | 13 | +}); | | 14 | + | | 15 | +const interval = setInterval(() => {}, 1000); // Keep the test running, because await alone doesn't. | | 16 | +const value = { hello: 'world' }; | | 17 | + | | 18 | +const p1 = import('../fixtures/es-modules/tla/await-export-promise.mjs').then(common.mustCall((ns) => { | | 19 | + assert.strictEqual(ns.default, value); | | 20 | +}), common.mustNotCall()); | | 21 | + | | 22 | +const p2 = import('../fixtures/es-modules/tla/await-export-promise.mjs').then(common.mustCall((ns) => { | | 23 | + assert.strictEqual(ns.default, value); | | 24 | +}), common.mustNotCall()); | | 25 | + | | 26 | +const p3 = import('../fixtures/es-modules/tla/await-export-promise.mjs').then(common.mustCall((ns) => { | | 27 | + assert.strictEqual(ns.default, value); | | 28 | +}), common.mustNotCall()); | | 29 | + | | 30 | +exportedResolve(value); | | 31 | + | | 32 | +Promise.all([p1, p2, p3]).then(common.mustCall(() => { clearInterval(interval); })); |
Collapse file Copy file name to clipboard +26Lines changed: 26 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +// This tests that after loading a ESM with import() and then retrying | | 2 | +// with require(), it errors as expected, and produces consistent results. | | 3 | +'use strict'; | | 4 | +const common = require('../common'); | | 5 | +const assert = require('assert'); | | 6 | + | | 7 | +let ns; | | 8 | +async function test() { | | 9 | + const newNs = await import('../fixtures/es-modules/tla/export-async.mjs'); | | 10 | + if (ns === undefined) { | | 11 | + ns = newNs; | | 12 | + } else { | | 13 | + // Check that re-evalaution is returning the same namespace. | | 14 | + assert.strictEqual(ns, newNs); | | 15 | + } | | 16 | + assert.strictEqual(ns.hello, 'world'); | | 17 | + | | 18 | + assert.throws(() => { | | 19 | + require('../fixtures/es-modules/tla/export-async.mjs'); | | 20 | + }, { | | 21 | + code: 'ERR_REQUIRE_ASYNC_MODULE' | | 22 | + }); | | 23 | +} | | 24 | + | | 25 | +// Run the test twice to check consistency after caching. | | 26 | +test().then(common.mustCall(test)).catch(common.mustNotCall()); |
Collapse file Copy file name to clipboard +25Lines changed: 25 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +// This tests that after failing to require an ESM that contains TLA, | | 2 | +// retrying with import() still works, and produces consistent results. | | 3 | +'use strict'; | | 4 | +const common = require('../common'); | | 5 | +const assert = require('assert'); | | 6 | + | | 7 | +let ns; | | 8 | +async function test() { | | 9 | + assert.throws(() => { | | 10 | + require('../fixtures/es-modules/tla/export-async.mjs'); | | 11 | + }, { | | 12 | + code: 'ERR_REQUIRE_ASYNC_MODULE' | | 13 | + }); | | 14 | + const newNs = await import('../fixtures/es-modules/tla/export-async.mjs'); | | 15 | + if (ns === undefined) { | | 16 | + ns = newNs; | | 17 | + } else { | | 18 | + // Check that re-evalaution is returning the same namespace. | | 19 | + assert.strictEqual(ns, newNs); | | 20 | + } | | 21 | + assert.strictEqual(ns.hello, 'world'); | | 22 | +} | | 23 | + | | 24 | +// Run the test twice to check consistency after caching. | | 25 | +test().then(common.mustCall(test)).catch(common.mustNotCall()); |
Collapse file Copy file name to clipboard +4Lines changed: 4 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +import promise from './export-promise.mjs'; | | 2 | +let result; | | 3 | +result = await promise; | | 4 | +export default result; |
Collapse file Copy file name to clipboard +2Lines changed: 2 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +let hello = await Promise.resolve('world'); | | 2 | +export { hello }; |
Collapse file Copy file name to clipboard +8Lines changed: 8 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +let exportedResolve; | | 2 | +let exportedReject; | | 3 | +const promise = new Promise((resolve, reject) => { | | 4 | + exportedResolve = resolve; | | 5 | + exportedReject = reject; | | 6 | +}); | | 7 | +export default promise; | | 8 | +export { exportedResolve, exportedReject }; |
|