The ECMAScript proposal “Import assertions” (by Myles Borins, Sven Sauleau, Dan Clark, and Daniel Ehrenberg) introduces syntax for associating metadata with import statements. In this blog post, we examine what that looks like and why it’s useful.
The motivating use case for import assertions was importing JSON data as a module. That looks as follows (and is further specified in a separate proposal):
import config from './data/config.json' assert { 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.
Therefore, there is a risk of doing importing wrong if a server has incorrectly configured content types. Specifying the necessary metadata at the import location solves this issue.
Before we take a more detailed look at how import assertions work, let’s examine the history of importing non-JavaScript artifacts in the world of JavaScript.
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.
Let’s examine in more detail what import assertions look like.
We have already seen a normal (static) import statement:
import config from './data/config.json' assert { type: 'json' };
The import assertions start with the keyword assert
. 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 are encouraged to throw an exception if they don’t support a key and/or a value. That makes it easier to add more features in the future because no one will use keys and values in unexpected ways.
To support import assertions, dynamic imports get a second parameter – an object with configuration data:
import('./data/config.json', { assert: { type: 'json' } })
The import assertions don’t exist at the top level; they are specified via the property assert
. 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 assertions:
export { default as config } from './data/config.json' assert { type: 'json' };
Import assertions are really just syntax. They lay the foundation for actual features that make use of that syntax.
The first feature based on import assertions is probably going to be JSON modules.
Whether or not import assertions 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', assert: { type: 'webassembly' } })
And we’d also need import assertions in HTML script elements:
<script src="my-app.wasm" type="module" asserttype="webassembly"></script>
The chapter on “Modules” in “JavaScript for impatient programmers” is an in-depth introduction to ECMAScript modules.