ECMAScript feature: import attributes

[2025-01-09] dev, javascript, es proposal
(Ad, please don’t block)

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 other than JavaScript modules (ESM)  

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.

Use cases for importing non-JavaScript artifacts  

These are a few use cases for importing non-JavaScript artifacts:

  • Importing JSON configuration data
  • Importing WebAssembly code as if it were a JavaScript module
  • Importing CSS to build user interfaces

For more use cases, you can take a look at the list of webpack’s loaders.

Import attributes  

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?

  • The server may be deliberately misconfigured – e.g., an external server not controlled by the people who wrote the code. It could swap an imported JSON file with code that would be executed by the importer.
  • The server may be accidentally misconfigured. With import attributes, we get feedback more quickly.
  • Given that the expected content type is not explicit in the code, the attributes also document the expectations of the programmer.

The syntax of import attributes  

Let’s examine in more detail what import attributes look like.

Static import statements  

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:

  • Unquoted keys and quoted keys
  • The values must be strings

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:

  • Attributes change what is imported, so simply ignoring them is risky because that changes the runtime behavior of code.
  • A side benefit is that this makes it easier to add more features in the future because no one will use keys and values in unexpected ways.

Dynamic imports  

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.

Re-export statements  

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

Upcoming features based on import attributes  

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.

JSON modules  

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

CSS modules  

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

Importing WebAssembly  

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>

Potential future features  

Ignorable attributes  

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

More type values  

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

The history of the import attributes proposal  

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.

Further reading  

Sources of this blog post:

Other material:

  • The chapter on “Modules” in “Exploring JavaScript” is an in-depth introduction to ECMAScript modules.
  • The article “Import attributes” by Shu-yu Guo for v8.dev explains why the proposal changed from assertion semantics to attribute semantics (as mentioned in the section on the history of the proposal).