Whilst updating an older Express project to use TypeScript, I came across a file where I was pulling in a JSON file directly using require
. I wanted to remove the require
usage and switch to import
, but this was not quite as straightforward as I initially expected.
It turns out that this can be demonstrated really easily with just a single JS file, and the package.json
that you would almost certainly have in any JS project. So the setup for this example is going to be super simple.
cd /tmp
mkdir node-json-import-test
cd node-json-import-test
npm init -y
{
name: 'json-import-test',
version: '1.0.0',
description: '',
main: 'index.js',
scripts: { test: 'echo "Error: no test specified" && exit 1' },
author: '',
license: 'ISC'
}
Code language: Shell Session (shell)
Right so to quickly recap, create a new directory called node-json-import-test
and run npm init
accepting all the defaults (-y
) which then spits out the resulting package.json
contents to the console.
We can then create our index.js
file and get cracking:
touch index.js
Code language: Shell Session (shell)
If you’re using require
syntax, the way to load a JSON file is actually super easy. Just require
it:
// index.js
const packageJson = require('./package.json');
console.log(packageJson);
console.log(packageJson.name);
Code language: JavaScript (javascript)
And then run the script:
➜ node-json-import-test node index.js
{
name: 'json-import-test',
version: '1.0.0',
description: '',
main: 'index.js',
scripts: { test: 'echo "Error: no test specified" && exit 1' },
author: '',
license: 'ISC'
}
json-import-test
Code language: JavaScript (javascript)
Which logs out the full package.json
contents as expected, and then the specific value of the given key.
That works really nicely, and away you go.
However, there isn’t (at the time of writing) a direct swap to an equivalent when using import
:
// won't work
import importedPackageJson from './package.json';
console.log(importedPackageJson);
console.log(importedPackageJson.name);
Code language: JavaScript (javascript)
If we run this we get an error:
➜ node-json-import-test node index.js
(node:3272544) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/home/chris/Development/node-json-import-test/index.js:7
import importedPackageJson from './package.json';
^^^^^^
SyntaxError: Cannot use import statement outside a module
at Object.compileFunction (node:vm:360:18)
at wrapSafe (node:internal/modules/cjs/loader:1088:15)
at Module._compile (node:internal/modules/cjs/loader:1123:27)
at Module._extensions..js (node:internal/modules/cjs/loader:1213:10)
at Module.load (node:internal/modules/cjs/loader:1037:32)
at Module._load (node:internal/modules/cjs/loader:878:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
at node:internal/main/run_main_module:23:47
Node.js v18.11.0
Code language: Shell Session (shell)
OK, so a couple of problems to address.
The actual problem here is:
SyntaxError: Cannot use import statement outside a module
Code language: Shell Session (shell)
But the error message is also kind enough to tell us exactly how to fix this:
Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
Code language: Shell Session (shell)
I’m going with the “set “type”: “module” in the package.json” suggestion:
{
"name": "json-import-test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Code language: JSON / JSON with Comments (json)
Now, without changing the index.js
contents, this should all start to work, right?
➜ node-json-import-test node index.js
node:internal/errors:484
ErrorCaptureStackTrace(err);
^
TypeError [ERR_IMPORT_ASSERTION_TYPE_MISSING]: Module "file:///home/chris/Development/node-json-import-test/package.json" needs an import assertion of type "json"
at new NodeError (node:internal/errors:393:5)
at validateAssertions (node:internal/modules/esm/assert:82:15)
at defaultLoad (node:internal/modules/esm/load:84:3)
at nextLoad (node:internal/modules/esm/loader:163:28)
at ESMLoader.load (node:internal/modules/esm/loader:605:26)
at ESMLoader.moduleProvider (node:internal/modules/esm/loader:457:22)
at new ModuleJob (node:internal/modules/esm/module_job:63:26)
at #createModuleJob (node:internal/modules/esm/loader:480:17)
at ESMLoader.getModuleJob (node:internal/modules/esm/loader:434:34)
at async ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:78:21) {
code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING'
}
Node.js v18.11.0
Code language: Shell Session (shell)
Hmm, not quite.
But again, the error message here is pretty helpful, if a little scary looking:
TypeError [ERR_IMPORT_ASSERTION_TYPE_MISSING]: Module "file:///tmp/node-json-import-test/package.json" needs an import assertion of type "json"
Code language: Shell Session (shell)
This was the new thing I discovered, and what led to this post:
needs an import assertion of type "json"
Code language: Shell Session (shell)
What Are Import Assertions In NodeJS?
Import assertions are a feature introduced in ECMAScript 2021 (ES12) that allow developers to specify additional metadata about the modules being imported. These assertions are available in Node.js versions 14.0.0 and higher.
With import assertions, you can specify metadata about the module being imported, such as its integrity checksum or its media type. The syntax for import assertions is as follows:
import defaultExport from 'module' assert { assertion };
import * as namedExports from 'module' assert { assertion };
import { namedExport } from 'module' assert { assertion };
Code language: JavaScript (javascript)
Here, assertion
is an object that specifies the metadata for the imported module. It can have one or more properties that define the metadata. For example:
import { foo } from './module.js' assert {
type: 'json',
integrity: 'sha384-ABC123'
};
Code language: JavaScript (javascript)
In this example, the import statement includes an assertion object that specifies that the module being imported is a JSON file and includes a SHA384 integrity checksum.
Import assertions can help improve the security and reliability of your code by providing additional checks and metadata about the modules being imported.
Using Import Assertions In NodeJS To Import JSON
Armed with this knowledge, we can add the required import assertion to our import
statement and get a working solution (with a caveat):
➜ node-json-import-test node index.js
{
name: 'json-import-test',
version: '1.0.0',
description: '',
main: 'index.js',
type: 'module',
scripts: { test: 'echo "Error: no test specified" && exit 1' },
author: '',
license: 'ISC'
}
json-import-test
(node:3274350) ExperimentalWarning: Importing JSON modules is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Code language: Shell Session (shell)
A little bit easier to see in the terminal output:
So it works nicely, but it comes with a warning because this feature is experimental.
If you don’t want the warning, there are two further links that offer explanations and work arounds:
- How to import JSON files in ES modules (Node.js)
- All you need to know to move from CommonJS to ECMAScript Modules (ESM) in Node.js
For me, I could accept the experimental warning so it was fine.
What is the Difference Between require
and import
in nodejs?
In Node.js, both require
and import
are used to include code from other files or modules in your JavaScript code. However, there are some differences between them:
- Syntax:
require
is a function that takes a string argument, whileimport
is a statement that uses the ES6 module syntax. - Scope:
require
is used to load modules using CommonJS syntax, which creates a new scope for each module. On the other hand,import
creates a lexical scope and only imports the bindings specified in the statement. - Asynchronous vs. Synchronous:
require
is a synchronous function, meaning that it blocks the execution until the required module is loaded. On the other hand,import
is an asynchronous operation and it doesn’t block the execution of the program. - Compatibility:
require
is the standard way to load modules in Node.js, whileimport
is part of the ES6 module specification and is supported by some versions of Node.js (v13.2.0 or higher) and modern browsers.
In summary, require
is used for synchronous module loading using CommonJS syntax, while import
is used for asynchronous module loading using ES6 module syntax.
Hi 🙂
The proposal now disallows unknown assertions, so that example with
integrity
would throw (given that currently the only existing assertion istype
). There are some projects on GitHub that already copy-pasted that example: https://github.com/search?q=+language%3AJavaScript+%2Fimport.*assert%5Cs*%5C%7B.*integrity.*%2F&type=codeWould it be possible to remove it, and only keep the
type
example?