Skip to content

Commit

Permalink
esm: use import attributes instead of import assertions
Browse files Browse the repository at this point in the history
The old import assertions proposal has been
renamed to "import attributes" with the follwing major changes:

1. The keyword is now `with` instead of `assert`.
2. Unknown assertions cause an error rather than being ignored,

This commit updates the documentation to encourage folks to use the new
syntax, and add aliases for module customization hooks.

PR-URL: #50140
Backport-PR-URL: #51136
Fixes: #50134
Refs: v8/v8@159c82c
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Reviewed-By: Jacob Smith <jacob@frende.me>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
  • Loading branch information
aduh95 authored and richardlau committed Mar 18, 2024
1 parent c13969e commit a193be3
Show file tree
Hide file tree
Showing 18 changed files with 68 additions and 62 deletions.
5 changes: 1 addition & 4 deletions .eslintrc.js
Expand Up @@ -44,10 +44,7 @@ module.exports = {
parserOptions: {
babelOptions: {
plugins: [
[
Module._findPath('@babel/plugin-syntax-import-attributes'),
{ deprecatedAssertSyntax: true },
],
Module._findPath('@babel/plugin-syntax-import-attributes'),
],
},
requireConfigFile: false,
Expand Down
20 changes: 11 additions & 9 deletions doc/api/esm.md
Expand Up @@ -7,6 +7,9 @@
<!-- YAML
added: v8.5.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/50140
description: Add experimental support for import attributes.
- version: v18.19.0
pr-url: https://github.com/nodejs/node/pull/44710
description: Module customization hooks are executed off the main thread.
Expand Down Expand Up @@ -202,7 +205,7 @@ added: v12.10.0

```js
import 'data:text/javascript,console.log("hello!");';
import _ from 'data:application/json,"world!"' assert { type: 'json' };
import _ from 'data:application/json,"world!"' with { type: 'json' };
```

`data:` URLs only resolve [bare specifiers][Terminology] for builtin modules
Expand Down Expand Up @@ -243,26 +246,25 @@ added:
- v17.1.0
- v16.14.0
changes:
- version: v18.19.0
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/50140
description: Switch from Import Assertions to Import Attributes.
-->

> Stability: 1.1 - Active development
> This feature was previously named "Import assertions", and using the `assert`
> keyword instead of `with`. Because the version of V8 on this release line does
> not support the `with` keyword, you need to keep using `assert` to support
> this version of Node.js.
> keyword instead of `with`. Any uses in code of the prior `assert` keyword
> should be updated to use `with` instead.
The [Import Attributes proposal][] adds an inline syntax for module import
statements to pass on more information alongside the module specifier.

```js
import fooData from './foo.json' assert { type: 'json' };
import fooData from './foo.json' with { type: 'json' };

const { default: barData } =
await import('./bar.json', { assert: { type: 'json' } });
await import('./bar.json', { with: { type: 'json' } });
```

Node.js supports the following `type` values, for which the attribute is
Expand Down Expand Up @@ -555,10 +557,10 @@ separate cache.
JSON files can be referenced by `import`:

```js
import packageConfig from './package.json' assert { type: 'json' };
import packageConfig from './package.json' with { type: 'json' };
```
The `assert { type: 'json' }` syntax is mandatory; see [Import Attributes][].
The `with { type: 'json' }` syntax is mandatory; see [Import Attributes][].
The imported JSON only exposes a `default` export. There is no support for named
exports. A cache entry is created in the CommonJS cache to avoid duplication.
Expand Down
7 changes: 7 additions & 0 deletions src/node.cc
Expand Up @@ -745,6 +745,13 @@ int ProcessGlobalArgs(std::vector<std::string>* args,
"--no-harmony-import-assertions") == v8_args.end()) {
v8_args.emplace_back("--harmony-import-assertions");
}
// TODO(aduh95): remove this when the harmony-import-attributes flag
// is removed in V8.
if (std::find(v8_args.begin(),
v8_args.end(),
"--no-harmony-import-attributes") == v8_args.end()) {
v8_args.emplace_back("--harmony-import-attributes");
}

auto env_opts = per_process::cli_options->per_isolate->per_env;
if (std::find(v8_args.begin(), v8_args.end(),
Expand Down
10 changes: 5 additions & 5 deletions test/es-module/test-esm-assertionless-json-import.js
Expand Up @@ -9,7 +9,7 @@ async function test() {
import('../fixtures/experimental.json'),
import(
'../fixtures/experimental.json',
{ assert: { type: 'json' } }
{ with: { type: 'json' } }
),
]);

Expand All @@ -24,7 +24,7 @@ async function test() {
import('../fixtures/experimental.json?test'),
import(
'../fixtures/experimental.json?test',
{ assert: { type: 'json' } }
{ with: { type: 'json' } }
),
]);

Expand All @@ -39,7 +39,7 @@ async function test() {
import('../fixtures/experimental.json#test'),
import(
'../fixtures/experimental.json#test',
{ assert: { type: 'json' } }
{ with: { type: 'json' } }
),
]);

Expand All @@ -54,7 +54,7 @@ async function test() {
import('../fixtures/experimental.json?test2#test'),
import(
'../fixtures/experimental.json?test2#test',
{ assert: { type: 'json' } }
{ with: { type: 'json' } }
),
]);

Expand All @@ -69,7 +69,7 @@ async function test() {
import('data:application/json,{"ofLife":42}'),
import(
'data:application/json,{"ofLife":42}',
{ assert: { type: 'json' } }
{ with: { type: 'json' } }
),
]);

Expand Down
10 changes: 5 additions & 5 deletions test/es-module/test-esm-data-urls.js
Expand Up @@ -60,36 +60,36 @@ function createBase64URL(mime, body) {
}
{
const ns = await import('data:application/json;foo="test,"this"',
{ assert: { type: 'json' } });
{ with: { type: 'json' } });
assert.deepStrictEqual(Object.keys(ns), ['default']);
assert.strictEqual(ns.default, 'this');
}
{
const ns = await import(`data:application/json;foo=${
encodeURIComponent('test,')
},0`, { assert: { type: 'json' } });
},0`, { with: { type: 'json' } });
assert.deepStrictEqual(Object.keys(ns), ['default']);
assert.strictEqual(ns.default, 0);
}
{
await assert.rejects(async () =>
import('data:application/json;foo="test,",0',
{ assert: { type: 'json' } }), {
{ with: { type: 'json' } }), {
name: 'SyntaxError',
message: /Unexpected end of JSON input/
});
}
{
const body = '{"x": 1}';
const plainESMURL = createURL('application/json', body);
const ns = await import(plainESMURL, { assert: { type: 'json' } });
const ns = await import(plainESMURL, { with: { type: 'json' } });
assert.deepStrictEqual(Object.keys(ns), ['default']);
assert.strictEqual(ns.default.x, 1);
}
{
const body = '{"default": 2}';
const plainESMURL = createURL('application/json', body);
const ns = await import(plainESMURL, { assert: { type: 'json' } });
const ns = await import(plainESMURL, { with: { type: 'json' } });
assert.deepStrictEqual(Object.keys(ns), ['default']);
assert.strictEqual(ns.default.default, 2);
}
Expand Down
8 changes: 4 additions & 4 deletions test/es-module/test-esm-dynamic-import-attribute.js
Expand Up @@ -5,7 +5,7 @@ const { strictEqual } = require('assert');
async function test() {
{
const results = await Promise.allSettled([
import('../fixtures/empty.js', { assert: { type: 'json' } }),
import('../fixtures/empty.js', { with: { type: 'json' } }),
import('../fixtures/empty.js'),
]);

Expand All @@ -16,7 +16,7 @@ async function test() {
{
const results = await Promise.allSettled([
import('../fixtures/empty.js'),
import('../fixtures/empty.js', { assert: { type: 'json' } }),
import('../fixtures/empty.js', { with: { type: 'json' } }),
]);

strictEqual(results[0].status, 'fulfilled');
Expand All @@ -25,7 +25,7 @@ async function test() {

{
const results = await Promise.allSettled([
import('../fixtures/empty.json', { assert: { type: 'json' } }),
import('../fixtures/empty.json', { with: { type: 'json' } }),
import('../fixtures/empty.json'),
]);

Expand All @@ -36,7 +36,7 @@ async function test() {
{
const results = await Promise.allSettled([
import('../fixtures/empty.json'),
import('../fixtures/empty.json', { assert: { type: 'json' } }),
import('../fixtures/empty.json', { with: { type: 'json' } }),
]);

strictEqual(results[0].status, 'rejected');
Expand Down
8 changes: 4 additions & 4 deletions test/es-module/test-esm-dynamic-import-attribute.mjs
Expand Up @@ -3,7 +3,7 @@ import { strictEqual } from 'assert';

{
const results = await Promise.allSettled([
import('../fixtures/empty.js', { assert: { type: 'json' } }),
import('../fixtures/empty.js', { with: { type: 'json' } }),
import('../fixtures/empty.js'),
]);

Expand All @@ -14,7 +14,7 @@ import { strictEqual } from 'assert';
{
const results = await Promise.allSettled([
import('../fixtures/empty.js'),
import('../fixtures/empty.js', { assert: { type: 'json' } }),
import('../fixtures/empty.js', { with: { type: 'json' } }),
]);

strictEqual(results[0].status, 'fulfilled');
Expand All @@ -23,7 +23,7 @@ import { strictEqual } from 'assert';

{
const results = await Promise.allSettled([
import('../fixtures/empty.json', { assert: { type: 'json' } }),
import('../fixtures/empty.json', { with: { type: 'json' } }),
import('../fixtures/empty.json'),
]);

Expand All @@ -34,7 +34,7 @@ import { strictEqual } from 'assert';
{
const results = await Promise.allSettled([
import('../fixtures/empty.json'),
import('../fixtures/empty.json', { assert: { type: 'json' } }),
import('../fixtures/empty.json', { with: { type: 'json' } }),
]);

strictEqual(results[0].status, 'rejected');
Expand Down
2 changes: 1 addition & 1 deletion test/es-module/test-esm-import-attributes-1.mjs
@@ -1,6 +1,6 @@
import '../common/index.mjs';
import { strictEqual } from 'assert';

import secret from '../fixtures/experimental.json' assert { type: 'json' };
import secret from '../fixtures/experimental.json' with { type: 'json' };

strictEqual(secret.ofLife, 42);
4 changes: 2 additions & 2 deletions test/es-module/test-esm-import-attributes-2.mjs
@@ -1,9 +1,9 @@
import '../common/index.mjs';
import { strictEqual } from 'assert';

import secret0 from '../fixtures/experimental.json' assert { type: 'json' };
import secret0 from '../fixtures/experimental.json' with { type: 'json' };
const secret1 = await import('../fixtures/experimental.json', {
assert: { type: 'json' },
with: { type: 'json' },
});

strictEqual(secret0.ofLife, 42);
Expand Down
4 changes: 2 additions & 2 deletions test/es-module/test-esm-import-attributes-3.mjs
@@ -1,9 +1,9 @@
import '../common/index.mjs';
import { strictEqual } from 'assert';

import secret0 from '../fixtures/experimental.json' assert { type: 'json' };
import secret0 from '../fixtures/experimental.json' with { type: 'json' };
const secret1 = await import('../fixtures/experimental.json',
{ assert: { type: 'json' } });
{ with: { type: 'json' } });

strictEqual(secret0.ofLife, 42);
strictEqual(secret1.default.ofLife, 42);
Expand Down
16 changes: 8 additions & 8 deletions test/es-module/test-esm-import-attributes-errors.js
Expand Up @@ -7,27 +7,27 @@ const jsonModuleDataUrl = 'data:application/json,""';

async function test() {
await rejects(
import('data:text/css,', { assert: { type: 'css' } }),
import('data:text/css,', { with: { type: 'css' } }),
{ code: 'ERR_UNKNOWN_MODULE_FORMAT' }
);

await rejects(
import('data:text/css,', { assert: { unsupportedAttribute: 'value' } }),
import('data:text/css,', { with: { unsupportedAttribute: 'value' } }),
{ code: 'ERR_IMPORT_ATTRIBUTE_UNSUPPORTED' }
);

await rejects(
import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}assert{type:"json"}`),
import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}with{type:"json"}`),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' }
);

await rejects(
import(jsModuleDataUrl, { assert: { type: 'json' } }),
import(jsModuleDataUrl, { with: { type: 'json' } }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' }
);

await rejects(
import(jsModuleDataUrl, { assert: { type: 'unsupported' } }),
import(jsModuleDataUrl, { with: { type: 'unsupported' } }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
);

Expand All @@ -37,17 +37,17 @@ async function test() {
);

await rejects(
import(jsonModuleDataUrl, { assert: {} }),
import(jsonModuleDataUrl, { with: {} }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
);

await rejects(
import(jsonModuleDataUrl, { assert: { foo: 'bar' } }),
import(jsonModuleDataUrl, { with: { foo: 'bar' } }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
);

await rejects(
import(jsonModuleDataUrl, { assert: { type: 'unsupported' } }),
import(jsonModuleDataUrl, { with: { type: 'unsupported' } }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
);
}
Expand Down
14 changes: 7 additions & 7 deletions test/es-module/test-esm-import-attributes-errors.mjs
Expand Up @@ -7,22 +7,22 @@ const jsonModuleDataUrl = 'data:application/json,""';
await rejects(
// This rejects because of the unsupported MIME type, not because of the
// unsupported assertion.
import('data:text/css,', { assert: { type: 'css' } }),
import('data:text/css,', { with: { type: 'css' } }),
{ code: 'ERR_UNKNOWN_MODULE_FORMAT' }
);

await rejects(
import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}assert{type:"json"}`),
import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}with{type:"json"}`),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' }
);

await rejects(
import(jsModuleDataUrl, { assert: { type: 'json' } }),
import(jsModuleDataUrl, { with: { type: 'json' } }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' }
);

await rejects(
import(import.meta.url, { assert: { type: 'unsupported' } }),
import(import.meta.url, { with: { type: 'unsupported' } }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
);

Expand All @@ -32,16 +32,16 @@ await rejects(
);

await rejects(
import(jsonModuleDataUrl, { assert: {} }),
import(jsonModuleDataUrl, { with: {} }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
);

await rejects(
import(jsonModuleDataUrl, { assert: { foo: 'bar' } }),
import(jsonModuleDataUrl, { with: { foo: 'bar' } }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
);

await rejects(
import(jsonModuleDataUrl, { assert: { type: 'unsupported' } }),
import(jsonModuleDataUrl, { with: { type: 'unsupported' } }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
);
2 changes: 1 addition & 1 deletion test/es-module/test-esm-json-cache.mjs
Expand Up @@ -6,7 +6,7 @@ import { createRequire } from 'module';

import mod from '../fixtures/es-modules/json-cache/mod.cjs';
import another from '../fixtures/es-modules/json-cache/another.cjs';
import test from '../fixtures/es-modules/json-cache/test.json' assert
import test from '../fixtures/es-modules/json-cache/test.json' with
{ type: 'json' };

const require = createRequire(import.meta.url);
Expand Down

0 comments on commit a193be3

Please sign in to comment.