Skip to content

Commit 5838a72

Browse files
committed
module: add 'webassembly' and 'javascript' types
1 parent bd71030 commit 5838a72

12 files changed

Lines changed: 162 additions & 35 deletions

β€Ždoc/api/esm.mdβ€Ž

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ import fs from 'node:fs/promises';
229229
added: v17.1.0
230230
-->
231231

232+
> Stability: 1 - Experimental
233+
232234
The [Import Assertions proposal][] adds an inline syntax for module import
233235
statements to pass on more information alongside the module specifier.
234236

@@ -239,11 +241,30 @@ const { default: barData } =
239241
await import('./bar.json', { assert: { type: 'json' } });
240242
```
241243

242-
Node.js supports the following `type` values:
244+
Node.js supports the following `type` values, for which the assertion is
245+
mandatory:
246+
247+
| Assertion `type` | Needed for |
248+
| ---------------- | ---------------- |
249+
| `'json'` | [JSON modules][] |
250+
| `'webassembly'` | [Wasm modules][] |
243251

244-
| `type` | Resolves to |
245-
| -------- | ---------------- |
246-
| `'json'` | [JSON modules][] |
252+
Imports of CommonJS and ES module JavaScript files, and Node.js core modules, do
253+
not need an import assertion; but they can be given a `type` of `'javascript'`
254+
if desired:
255+
256+
```js
257+
const url = getUrlOfModuleToBeDynamicallyImported();
258+
259+
let assertType = 'javascript';
260+
if (url.endsWith('.json')) {
261+
assertType = 'json';
262+
} else if (url.endsWith('.wasm')) {
263+
assertType = 'webassembly';
264+
}
265+
266+
const importedModule = await import(url, { assert: { type: assertType } });
267+
```
247268

248269
## Builtin modules
249270

@@ -554,6 +575,8 @@ node index.mjs # fails
554575
node --experimental-json-modules index.mjs # works
555576
```
556577
578+
The `assert { type: 'json' }` syntax is mandatory; see [Import Assertions][].
579+
557580
<i id="esm_experimental_wasm_modules"></i>
558581
559582
## Wasm modules
@@ -570,7 +593,7 @@ This integration is in line with the
570593
For example, an `index.mjs` containing:
571594
572595
```js
573-
import * as M from './module.wasm';
596+
import * as M from './module.wasm' assert { type: 'webassembly' };
574597
console.log(M);
575598
```
576599
@@ -582,6 +605,9 @@ node --experimental-wasm-modules index.mjs
582605
583606
would provide the exports interface for the instantiation of `module.wasm`.
584607
608+
The `assert { type: 'webassembly' }` syntax is mandatory; see
609+
[Import Assertions][].
610+
585611
<i id="esm_experimental_top_level_await"></i>
586612
587613
## Top-level `await`
@@ -1391,12 +1417,14 @@ success!
13911417
[Dynamic `import()`]: https://wiki.developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports
13921418
[ECMAScript Top-Level `await` proposal]: https://github.com/tc39/proposal-top-level-await/
13931419
[ES Module Integration Proposal for WebAssembly]: https://github.com/webassembly/esm-integration
1420+
[Import Assertions]: #import-assertions
13941421
[Import Assertions proposal]: https://github.com/tc39/proposal-import-assertions
13951422
[JSON modules]: #json-modules
13961423
[Node.js Module Resolution Algorithm]: #resolver-algorithm-specification
13971424
[Terminology]: #terminology
13981425
[URL]: https://url.spec.whatwg.org/
13991426
[WHATWG JSON modules specification]: https://html.spec.whatwg.org/#creating-a-json-module-script
1427+
[Wasm modules]: #wasm-modules
14001428
[`"exports"`]: packages.md#exports
14011429
[`"type"`]: packages.md#type
14021430
[`ArrayBuffer`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer

β€Žlib/internal/modules/esm/assert.jsβ€Ž

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const {
55
ObjectCreate,
66
ObjectValues,
77
ObjectPrototypeHasOwnProperty,
8-
Symbol,
98
} = primordials;
109
const { validateString } = require('internal/validators');
1110

@@ -15,23 +14,25 @@ const {
1514
ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED,
1615
} = require('internal/errors').codes;
1716

18-
const kImplicitAssertType = Symbol('implicit assert type');
17+
// Per the HTML spec, import statements without an assertion type imply a
18+
// `type` of `'javascript'`.
19+
const kImplicitAssertType = 'javascript';
1920

2021
/**
21-
* Define a map of module formats to import assertion types (the value of `type`
22-
* in `assert { type: 'json' }`).
23-
* @type {Map<string, string | typeof kImplicitAssertType}
22+
* Define a map of module formats to import assertion types (the value of
23+
* `type` in `assert { type: 'json' }`).
24+
* @type {Map<string, string>}
2425
*/
2526
const formatTypeMap = {
2627
'__proto__': null,
2728
'builtin': kImplicitAssertType,
2829
'commonjs': kImplicitAssertType,
2930
'json': 'json',
3031
'module': kImplicitAssertType,
31-
'wasm': kImplicitAssertType, // Should probably be 'webassembly' per https://github.com/tc39/proposal-import-assertions
32+
'wasm': 'webassembly',
3233
};
3334

34-
/** @type {Array<string, string | typeof kImplicitAssertType} */
35+
/** @type {string[]} */
3536
const supportedAssertionTypes = ObjectValues(formatTypeMap);
3637

3738

@@ -50,25 +51,29 @@ function validateAssertions(url, format,
5051

5152
switch (validType) {
5253
case undefined:
53-
// Ignore assertions for module types we don't recognize, to allow new
54+
// Ignore assertions for module formats we don't recognize, to allow new
5455
// formats in the future.
5556
return true;
5657

5758
case importAssertions.type:
5859
// The asserted type is the valid type for this format.
60+
// This case also covers when the implicit type is declared explicitly
61+
// (`assert { type: 'javascript' }`).
62+
process.emitWarning('Import assertions are experimental.',
63+
'ExperimentalWarning');
5964
return true;
6065

6166
case kImplicitAssertType:
62-
// This format doesn't allow an import assertion type, so the property
63-
// must not be set on the import assertions object.
67+
// This format allows the type to be implied. (If it were declared
68+
// explicitly, it would have been caught already by the previous case.)
6469
if (!ObjectPrototypeHasOwnProperty(importAssertions, 'type')) {
6570
return true;
6671
}
6772
return handleInvalidType(url, importAssertions.type);
6873

6974
default:
7075
// There is an expected type for this format, but the value of
71-
// `importAssertions.type` was not it.
76+
// `importAssertions.type` might not have been it.
7277
if (!ObjectPrototypeHasOwnProperty(importAssertions, 'type')) {
7378
// `type` wasn't specified at all.
7479
throw new ERR_IMPORT_ASSERTION_TYPE_MISSING(url, validType);
@@ -86,7 +91,7 @@ function handleInvalidType(url, type) {
8691
// `type` might have not been a string.
8792
validateString(type, 'type');
8893

89-
// `type` was not one of the types we understand.
94+
// `type` might not have been one of the types we understand.
9095
if (!ArrayPrototypeIncludes(supportedAssertionTypes, type)) {
9196
throw new ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED(type);
9297
}

β€Žlib/internal/modules/esm/module_map.jsβ€Ž

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,17 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
1212
const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;
1313
const { validateString } = require('internal/validators');
1414

15-
const validateAssertType = (type) =>
16-
type === kImplicitAssertType || validateString(type, 'type');
17-
1815
// Tracks the state of the loader-level module cache
1916
class ModuleMap extends SafeMap {
2017
constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
2118
get(url, type = kImplicitAssertType) {
2219
validateString(url, 'url');
23-
validateAssertType(type);
20+
validateString(type, 'type');
2421
return super.get(url)?.[type];
2522
}
2623
set(url, type = kImplicitAssertType, job) {
2724
validateString(url, 'url');
28-
validateAssertType(type);
25+
validateString(type, 'type');
2926
if (job instanceof ModuleJob !== true &&
3027
typeof job !== 'function') {
3128
throw new ERR_INVALID_ARG_TYPE('job', 'ModuleJob', job);
@@ -39,7 +36,7 @@ class ModuleMap extends SafeMap {
3936
}
4037
has(url, type = kImplicitAssertType) {
4138
validateString(url, 'url');
42-
validateAssertType(type);
39+
validateString(type, 'type');
4340
return super.get(url)?.[type] !== undefined;
4441
}
4542
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
'use strict';
2+
const common = require('../common');
3+
const { strictEqual } = require('assert');
4+
5+
async function test() {
6+
{
7+
const [secret0, secret1] = await Promise.all([
8+
import('../fixtures/es-modules/default-export.mjs'),
9+
import(
10+
'../fixtures/es-modules/default-export.mjs',
11+
{ assert: { type: 'javascript' } }
12+
),
13+
]);
14+
15+
strictEqual(secret0.default.ofLife, 42);
16+
strictEqual(secret1.default.ofLife, 42);
17+
strictEqual(secret0.default, secret1.default);
18+
strictEqual(secret0, secret1);
19+
}
20+
21+
{
22+
const [secret0, secret1] = await Promise.all([
23+
import('../fixtures/es-modules/default-export.mjs?test'),
24+
import(
25+
'../fixtures/es-modules/default-export.mjs?test',
26+
{ assert: { type: 'javascript' } }
27+
),
28+
]);
29+
30+
strictEqual(secret0.default.ofLife, 42);
31+
strictEqual(secret1.default.ofLife, 42);
32+
strictEqual(secret0.default, secret1.default);
33+
strictEqual(secret0, secret1);
34+
}
35+
36+
{
37+
const [secret0, secret1] = await Promise.all([
38+
import('../fixtures/es-modules/default-export.mjs#test'),
39+
import(
40+
'../fixtures/es-modules/default-export.mjs#test',
41+
{ assert: { type: 'javascript' } }
42+
),
43+
]);
44+
45+
strictEqual(secret0.default.ofLife, 42);
46+
strictEqual(secret1.default.ofLife, 42);
47+
strictEqual(secret0.default, secret1.default);
48+
strictEqual(secret0, secret1);
49+
}
50+
51+
{
52+
const [secret0, secret1] = await Promise.all([
53+
import('../fixtures/es-modules/default-export.mjs?test2#test'),
54+
import(
55+
'../fixtures/es-modules/default-export.mjs?test2#test',
56+
{ assert: { type: 'javascript' } }
57+
),
58+
]);
59+
60+
strictEqual(secret0.default.ofLife, 42);
61+
strictEqual(secret1.default.ofLife, 42);
62+
strictEqual(secret0.default, secret1.default);
63+
strictEqual(secret0, secret1);
64+
}
65+
66+
{
67+
const [secret0, secret1] = await Promise.all([
68+
import('data:text/javascript,export default { ofLife: 42 }'),
69+
import(
70+
'data:text/javascript,export default { ofLife: 42 }',
71+
{ assert: { type: 'javascript' } }
72+
),
73+
]);
74+
75+
strictEqual(secret0.default.ofLife, 42);
76+
strictEqual(secret1.default.ofLife, 42);
77+
}
78+
}
79+
80+
test().then(common.mustCall());
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import '../common/index.mjs';
2+
import { strictEqual } from 'assert';
3+
4+
/* eslint-disable no-duplicate-imports */
5+
import { inspect as implicitlyTyped } from 'node:util';
6+
import { inspect as explicitlyTyped }
7+
from 'node:util' assert { type: 'javascript' };
8+
9+
strictEqual(implicitlyTyped, explicitlyTyped);
10+
11+
const variableType = 'javascript'; // eslint-disable-line no-unused-vars
12+
const { inspect: dynamicallyTyped } = await import('node:util',
13+
{ assert: { type: variableType }});
14+
15+
strictEqual(implicitlyTyped, dynamicallyTyped);

β€Žtest/es-module/test-esm-import-assertion-validation.jsβ€Ž

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ const { validateAssertions } = require('internal/modules/esm/assert');
99
const url = 'test://';
1010

1111
assert.ok(validateAssertions(url, 'builtin', {}));
12+
assert.ok(validateAssertions(url, 'builtin', { type: 'javascript' }));
1213
assert.ok(validateAssertions(url, 'commonjs', {}));
14+
assert.ok(validateAssertions(url, 'commonjs', { type: 'javascript' }));
1315
assert.ok(validateAssertions(url, 'json', { type: 'json' }));
1416
assert.ok(validateAssertions(url, 'module', {}));
15-
assert.ok(validateAssertions(url, 'wasm', {}));
17+
assert.ok(validateAssertions(url, 'module', { type: 'javascript' }));
18+
assert.ok(validateAssertions(url, 'wasm', { type: 'webassembly' }));
1619

1720
assert.throws(() => validateAssertions(url, 'json', {}), {
1821
code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING',
@@ -22,12 +25,6 @@ assert.throws(() => validateAssertions(url, 'module', { type: 'json' }), {
2225
code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED',
2326
});
2427

25-
// This should be allowed according to HTML spec. Let's keep it disabled
26-
// until WASM module import is sorted out.
27-
assert.throws(() => validateAssertions(url, 'module', { type: 'javascript' }), {
28-
code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED',
29-
});
30-
3128
assert.throws(() => validateAssertions(url, 'module', { type: 'css' }), {
3229
code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED',
3330
});

β€Žtest/es-module/test-esm-loader-modulemap.jsβ€Ž

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const { strictEqual, throws } = require('assert');
77
const { ESMLoader } = require('internal/modules/esm/loader');
88
const ModuleMap = require('internal/modules/esm/module_map');
99
const ModuleJob = require('internal/modules/esm/module_job');
10-
const { kImplicitAssertType } = require('internal/modules/esm/assert');
1110
const createDynamicModule = require(
1211
'internal/modules/esm/create_dynamic_module');
1312

@@ -38,14 +37,14 @@ const jsonModuleJob = new ModuleJob(loader, stubJsonModule.module,
3837
strictEqual(moduleMap.get(jsonModuleDataUrl, 'json'), jsonModuleJob);
3938

4039
strictEqual(moduleMap.has(jsModuleDataUrl), true);
41-
strictEqual(moduleMap.has(jsModuleDataUrl, kImplicitAssertType), true);
40+
strictEqual(moduleMap.has(jsModuleDataUrl, 'javascript'), true);
4241
strictEqual(moduleMap.has(jsonModuleDataUrl, 'json'), true);
4342

4443
strictEqual(moduleMap.has('unknown'), false);
4544

4645
// The types must match
4746
strictEqual(moduleMap.has(jsModuleDataUrl, 'json'), false);
48-
strictEqual(moduleMap.has(jsonModuleDataUrl, kImplicitAssertType), false);
47+
strictEqual(moduleMap.has(jsonModuleDataUrl, 'javascript'), false);
4948
strictEqual(moduleMap.has(jsonModuleDataUrl), false);
5049
strictEqual(moduleMap.has(jsModuleDataUrl, 'unknown'), false);
5150
strictEqual(moduleMap.has(jsonModuleDataUrl, 'unknown'), false);

β€Žtest/es-module/test-esm-wasm.mjsβ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// Flags: --experimental-wasm-modules
22
import '../common/index.mjs';
33
import { path } from '../common/fixtures.mjs';
4-
import { add, addImported } from '../fixtures/es-modules/simple.wasm';
4+
import { add, addImported }
5+
from '../fixtures/es-modules/simple.wasm' assert { type: 'webassembly' };
56
import { state } from '../fixtures/es-modules/wasm-dep.mjs';
67
import { strictEqual, ok } from 'assert';
78
import { spawn } from 'child_process';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default { ofLife: 42 };
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
import { add, addImported } from './simple.wasm';
1+
import { add, addImported } from './simple.wasm' assert { type: 'webassembly' };
22
import { state } from './wasm-dep.mjs';

0 commit comments

Comments
Β (0)