The ECMAScript feature “Import Attributes” (by Sven Sauleau, Daniel Ehrenberg, Myles Borins, Dan Clark and Nicolò Ribaudo) helps with importing artifacts other than JavaScript modules. In this blog post, we examine what that looks like and why it’s useful.
Import attributes reached stage 4 in October 2024 and will probably be part of ECMAScript 2025.
Importing artifacts that are not JavaScript code as modules, has a long tradition in the JavaScript ecosystem.
For example, the JavaScript module loader RequireJS has support for so-called plugins. To give you a feeling for how old RequireJS is: Version 1.0.0 was released in 2009. Specifiers of modules that are imported via a plugin look like this:
'«specifier-of-plugin-module»!«specifier-of-artifact»'
For example, the following module specifier imports a file as JSON:
'json!./data/config.json'
Inspired by RequireJS, webpack supports the same module specifier syntax for its loaders.
These are a few use cases for importing non-JavaScript artifacts:
For more use cases, you can take a look at the list of webpack’s loaders.
The motivating use case for import attributes was importing JSON data as a module. That looks as follows (and is further specified in a separate proposal):
import configData from './config-data.json' with { type: 'json' };
You may wonder why a JavaScript engine can’t use the filename extension .json
to determine that this is JSON data. However, a core architectural principle of the web is to never use the filename extension to determine what’s inside a file. Instead, content types are used.
If a server is set up correctly then why not do a normal import and omit the import attributes?
Let’s examine in more detail what import attributes look like.
We have already seen a normal (static) import statement:
import configData from './config-data.json' with { type: 'json' };
The import attributes start with the keyword with
. That keyword is followed by an object literal. For now, the following object literal features are supported:
There are no other syntactic restrictions placed on the keys and the values, but engines should throw an exception if they don’t support a key and/or a value:
To support import attributes, dynamic imports get a second parameter – an object with configuration data:
import('./data/config.json', { with: { type: 'json' } })
The import attributes don’t exist at the top level; they are specified via the property with
. That makes it possible to add more configuration options in the future.
A re-export imports and exports in a single step. For the former, we need attributes:
export { default as config } from './data/config.json' with { type: 'json' };
Import attributes are really just syntax. They lay the foundation for actual features that make use of that syntax. In the following subsections, we’ll check out a few candidates.
The first feature based on import attributes is probably going to be JSON modules. We have already seen those in action:
import configData from './config-data.json' with { type: 'json' };
A WHATWG feature proposal (by Dan Clark) for importing CSS works like this:
import styles from './styles.css' with { type: 'css' };
document.adoptedStyleSheets = [...document.adoptedStyleSheets, styles];
Whether or not import attributes will be used to support directly importing WebAssembly from JavaScript is currently under discussion. If they are used, we’ll probably be able to create web workers like this:
new Worker('my-app.wasm', { type: 'module', with: { type: 'webassembly' } })
And we’d also need import attributes in HTML script elements:
<script src="my-app.wasm" type="module" withtype="webassembly"></script>
Currently, a host throws an exception if it doesn’t know an attribute. One conceivable future feature is syntax that tells a host that it should ignore an attribute if it doesn’t know it (source):
import logo from './logo.png' with { type: 'image', 'as?': 'canvas' };
If a host supports as
then the above statement is equivalent to:
import logo from './logo.png' with { type: 'image', as: 'canvas' };
Otherwise, it is equivalent to:
import logo from './logo.png' with { type: 'image' };
Kris Kowal proposes three type
values:
// `text` is a string
import text from 'text.txt' with { type: 'text' };
// `bytes` is an instance of Uint8Array
import bytes from 'bytes.oct' with { type: 'bytes' };
// `imageUrl` is a string
import imageUrl from 'image.jpg' with { type: 'url' };
This proposal went through a lot of changes over the years (source):
2019-12: The first version of the proposal had slightly different syntax but supported multiple attributes:
import data from './data.json' with type: 'json';
2020-02: Only one attribute is allowed and included in the module cache key:
import data from './data.json' as 'json';
2020-06: The proposal was approved for stage 2, but with support for multiple attributes and no attributes being stored in the module cache key.
2020-07: The keyword was first renamed to if
, then to assert
:
import data from './data.json' assert { type: 'json' };
2020-09: The proposal was approved for stage 3, with assertions being stored in the module cache key.
After that, two problems were recognized:
The term assertion implies that an import assertion should only influence if a module is loaded or evaluated, not how. However, on the web, the request for a resource changes depending on how it is intended to be used: different CSP policies, different fetch destinations and accepted response types, etc.
Similarly, import assertions should not be added to the module cache key.
Therefore, in January 2023, import assertions (keyword assert
) were demoted to stage 2 and got the new name “import attributes” (keyword with
).
Import attributes were promoted to stage 3 in March 2023 and to stage 4 in October 2024.
Sources of this blog post:
Other material: