DEV Community

Cover image for What Exactly Are Import Attributes in JavaScript?
Codeguage
Codeguage

Posted on • Originally published at codeguage.com

What Exactly Are Import Attributes in JavaScript?

Table of contents


There was a time when JavaScript had no concept of modules at all and then came in ECMAScript 6. ES6 brought forth a rich set of features to JavaScript, including a robust module system, thusly referred to as ECMAScript modules.

ECMAScript modules marked a big win in the history of JavaScript. But since their advent, the development of features around modules hasn't ended — the TC39 committee is continuously working on improving modules to every extent they could.

In this article, we shall discuss a relatively new feature in ES modules known as import attributes.

We'll learn what exactly are import attributes; what motivated their inception; and how to use them in the browser and, most importantly, in Node.js. We'll consider a few examples to supplement our discussion.

Let's go!


What are import attributes?

To begin with, let's familiarize ourselves with what exactly are import attributes from a technical standpoint.

Import attributes are a means of providing extra info while importing a module.

In theory, this extra info could be anything ranging from "how to import the module" to "how to parse it" to an endless amount of things. The sky's the limit.

However, at the time of this writing, import attributes are only used in JavaScript to natively handle JSON modules, CSS modules, and Wasm modules.

💡 Notice: The standard anticipates the possible introduction of many other attributes in the future, as their applications arise.

In other words, import attributes as of now are mainly used to specify "how to import a module" in JavaScript.

Speaking of which, the attribute here is called type.

We'll learn more about type shortly below. For now, let's see the general form of laying out import attributes in JavaScript.


Syntax of import attributes

What I really like about the syntax for import attributes in JavaScript is that it doesn't replace or improvise in the existing import syntax. Rather, it just exceeds the existing syntax.

That is, after the standard import syntax, we have the with keyword, followed by a pair of curly braces ({}). This pair of braces holds the individual attributes alongside their corresponding values:

import name from specifier with { attr1: value1, ... };
Enter fullscreen mode Exit fullscreen mode

To better understand this trivial syntax, let's place the spotlight on the type attribute.

(Oops, the spotlight isn't working!)


The type import attribute

As the name suggests, type specifies the type of the underlying module. That is, it states whether the module is a JSON module, a CSS module, or a Wasm module.

Here's the list of possible values of type:

  • 'json'
  • 'css'
  • 'wasm'

Any other value besides these isn't usually entertained since it's NOT part of the official standard.

For JavaScript programmers working in Node, 'json' is of particular importance. Can you think why?


Importing JSON modules natively in browsers

The browser environment has never ever had a native way for importing JSON files.

In fact, as you already know, browsers had no native module system for JavaScript, let alone a way to import JSON files.

The only way developers leveraged JSON imports was by means of build tools and bundlers as follows:

// Code parsed by a bundler like Webpack, Vite, etc.
import jsonData from './foo.json';
Enter fullscreen mode Exit fullscreen mode

These tools concatenated all build files together into one single JavaScript file that could easily be parsed by the browser, so the JSON data was always embedded directly into the single JavaScript file which explains how it all worked.

It was quite simple and straightforward to work with JSON data in this way on the client end.

However, the point is that there was NO native way of importing JSON modules.

Even following the advent of ECMAScript modules in JavaScript, and therefore in the browser, there was no concept of being able to import JSON files using the import syntax.

If one had to use idiomatic JavaScript in the browser for importing a JSON file, here's what was done typically:

fetch('./foo.json')
   .then(response => response.json())
   .then(jsonData => {
      // Work with jsonData
   });
Enter fullscreen mode Exit fullscreen mode

Of course, this wasn't easy to work with, for the entire set of code that depended upon the JSON file had to reside as part of a promise's then() or following an await.

Clearly not cool at all!

But thanks to a TC39 proposal to add native support to be able to import a JSON file using the standard import syntax, JavaScript eventually got import attributes.

And now you could easily do the following:

import jsonData from './foo.json' with { type: 'json' };
Enter fullscreen mode Exit fullscreen mode

The with { type: 'json' } syntax instructs the browser to explicitly treat the imported file as JSON data, and thereby load it as a JSON value into jsonData.

💡 Notice: Of course, this code assumes that you aren't using a bundler because bundlers typically doesn't enforce the inclusion of the with {} syntax at the time of this writing.

Let's consider a concrete example.


A concrete example for the browser

Suppose we have the following HTML document:

<!DOCTYPE html>
<html>
<head>
   <title>Working with import attributes</title>
   <script type="module" src="main.js"></script>
</head>
<body>
   <h1>Working with import attributes</h1>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

And in the same directory where this HTML document dwells, we also have the following JSON file, named languages.json:

languages.json

[
   {
      "name": "JavaScript",
      "ext": ".js"
   },
   {
      "name": "Python",
      "ext": ".py"
   },
   {
      "name": "C++",
      "ext": ".cpp"
   }
]
Enter fullscreen mode Exit fullscreen mode

It simply contains a list of languages, each with its name and file extension.

Our wish is to import this JSON file directly in the main.js file (which resides in the same location as the HTML document and the JSON file).

Alright. Then let's do so.

Here's the code of main.js to import the JSON file:

main.js

import languages from './languages.json' with { type: 'json' };
Enter fullscreen mode Exit fullscreen mode

Let's inspect languages and see what it contains:

main.js

import languages from './languages.json' with { type: 'json' };

console.log(languages instanceof Array);
console.log(languages);
Enter fullscreen mode Exit fullscreen mode

Chrome's console screenshot for the code above

As can be seen, the first log is true which indicates that languages is clearly an array. The second log showcases languages.

You can even try this example as follows:

Live Example

Interesting, right?


Importing JSON modules natively in Node

If you are an experienced JavaScript developer for Node, you'll know that Node has had the ability to import JSON modules directly since a long time, thanks to the CommonJS module system.

In particular, the call to require() in a CommonJS module is able to import a JSON file — the one whose name ends with the .json extension — directly as JSON data.

const jsonData = require('./languages.json');
Enter fullscreen mode Exit fullscreen mode

However, with the advent of the standard ECMAScript module system, and the hype to shift to this superior and native module system, importing JSON files (or let's call them JSON modules) became somewhat of an issue in Node.

Developers using ECMAScript modules in Node had to manually read and parse JSON files using a combination of a file-reading utility, e.g. fs.readFileSync(), and JSON.parse().

import fs from 'node:fs';

let jsonData = JSON.parse(fs.readFileSync('./foo.json'));
Enter fullscreen mode Exit fullscreen mode

This worked, of course, but it wasn't really elegant.

Needless to say, Node developers demanded a native way of importing JSON modules in ECMAScript modules using the modern import syntax, without having to resort to workarounds.

Fast-forward to today, Node supports native imports of JSON modules from within ECMAScript modules out of the box, courtesy of the standard implementation of import attributes in Node.

The same code above now becomes:

import jsonData from './foo.json' with { type: 'json' };
Enter fullscreen mode Exit fullscreen mode

No need to import the fs module for reading a file manually or calling on to the JSON.parse() method — it's all baked into Node.

💡 Notice: Some time before this native support of import attributes in Node, the feature to import JSON modules via import existed albeit behind the experimental flag --experimental-json-modules.

Let's consider a more concrete example.


A concrete example for Node.js

Suppose we have the following JSON file, languages.json, just as before:

languages.json

[
   {
      "name": "JavaScript",
      "ext": ".js"
   },
   {
      "name": "Python",
      "ext": ".py"
   },
   {
      "name": "C++",
      "ext": ".cpp"
   }
]
Enter fullscreen mode Exit fullscreen mode

Now we want to import this as JSON in JavaScript and then log its value (which'll obviously be an array).

First, let's consider what happens if we import this using the CommonJS module system.

Here, we have a main.cjs file (which is guaranteed to use the CommonJS module system regardless of package.json configuration) with the desired require() call to the JSON file (in the same directory):

main.cjs

const languages = require('./languages.json');
Enter fullscreen mode Exit fullscreen mode

Let's inspect languages now:

main.cjs

const languages = require('./languages.json');

console.log(languages instanceof Array);
console.log(languages);
Enter fullscreen mode Exit fullscreen mode

Output:

true
[
   { name: 'JavaScript', ext: '.js' },
   { name: 'Python', ext: '.py' },
   { name: 'C++', ext: '.cpp' }
]
Enter fullscreen mode Exit fullscreen mode

As you can see, the log confirms that the import went well — languages holds an array representing the parsed contents of languages.json.

Now, it's time for the real deal: to replicate this behavior in an ECMAScript module.

Following we have a main.mjs file (which is guaranteed to use the ECMAScript module system regardless of package.json configuration) with the desired import statement for importing the JSON file:

main.mjs

import languages from './languages.json' with { type: 'json' };
Enter fullscreen mode Exit fullscreen mode

Notice the added with { type: 'json } syntax. This inclusion of the type import attribute is necessary in order to correctly import the contents of the file as JSON.

In fact, the import attribute is necessary in order to prevent an import error. That is, if we remove the type import attribute from here, the import will fail, as demonstrated below:

main.mjs

import languages from './languages.json';
Enter fullscreen mode Exit fullscreen mode

Output:

node:internal/modules/esm/assert:88
        throw new ERR_IMPORT_ATTRIBUTE_MISSING(url, 'type', validType);
              ^

TypeError [ERR_IMPORT_ATTRIBUTE_MISSING]: Module ".../languages.json" needs an import attribute of "type: json"
Enter fullscreen mode Exit fullscreen mode

The reason for this error is simple: Node recognizes that the imported file has a .json extension and is likewise a JSON file (at least, Node assumes that) and so must be flagged as such with the help of the type import attribute in the code.

Anyways, resuming where we left, let's inspect languages as before and see what exactly does it hold:

main.mjs

import languages from './languages.json' with { type: 'json' };

console.log(languages instanceof Array);
console.log(languages);
Enter fullscreen mode Exit fullscreen mode

Output:

true
[
   { name: 'JavaScript', ext: '.js' },
   { name: 'Python', ext: '.py' },
   { name: 'C++', ext: '.cpp' }
]
Enter fullscreen mode Exit fullscreen mode

Voila! It's the same kind of array, holding the parsed contents of languages.json. Our import is working flawlessly.


Further reading

Import attributes are still a relatively recent addition to standard JavaScript and it may take a while before the whole community feels at home using them.

If you're interested in digging deeper into import attributes, here are some worthwhile resources to check out:


Top comments (0)