◐ Shell
clean mode source ↗

module: allow ESM that failed to be required to be re-imported · nodejs/node@8d33f78

Original file line numberDiff line numberDiff line change

@@ -21,7 +21,7 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {

2121

debug = fn;

2222

});

2323
24-

const { ModuleWrap, kEvaluated } = internalBinding('module_wrap');

24+

const { ModuleWrap, kInstantiated } = internalBinding('module_wrap');

2525
2626

const { decorateErrorStack, kEmptyObject } = require('internal/util');

2727

const {

@@ -346,10 +346,26 @@ class ModuleJobSync extends ModuleJobBase {

346346

}

347347
348348

async run() {

349+

// This path is hit by a require'd module that is imported again.

349350

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}`);

353369

}

354370
355371

runSync() {

Original file line numberDiff line numberDiff line change

@@ -0,0 +1,35 @@

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); }));

Original file line numberDiff line numberDiff line change

@@ -0,0 +1,32 @@

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); }));

Original file line numberDiff line numberDiff line change

@@ -0,0 +1,26 @@

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());

Original file line numberDiff line numberDiff line change

@@ -0,0 +1,25 @@

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());

Original file line numberDiff line numberDiff line change

@@ -0,0 +1,4 @@

1+

import promise from './export-promise.mjs';

2+

let result;

3+

result = await promise;

4+

export default result;

Original file line numberDiff line numberDiff line change

@@ -0,0 +1,2 @@

1+

let hello = await Promise.resolve('world');

2+

export { hello };

Original file line numberDiff line numberDiff line change

@@ -0,0 +1,8 @@

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 };