Tierney Cyren

Implicit ESM in Node.js with "type": "module"

Originally posted on as part 2 of the ESM in Node.js series.

Continuing the Node.js ESM content, I'd like to talk about the comparitively straightforward alternative to using .mjs to get your Node.js applications to run as ECMAScript Modules (ESM) rather than CommonJS: including "type": "module" in your package.json.

Usage of "type": "module"

Let's assume we've started with the following package.json for a zero (production) dependency application:

{
  "name": "apollo-lunar-module",
  "version": "0.0.1",
  "description": "A simple, fast, nice lunar lander module",
  "main": "index.js",
  "scripts": {
    "lint": "standard"
  },
  "author": "Tierney Cyren <hello@bnb.im> (https://bnb.im/)",
  "license": "MIT",
  "devDependencies": {
    "standard": "^16.0.3"
  }
}

To have implicit ESM - that is, have our .js files parsed as ESM - we' need to make the following change:

{
"name": "apollo-lunar-module",
"version": "0.0.1",
"description": "A simple, fast, nice lunar lander module",
"main": "index.js",
+ "type": "module",
"scripts": {
"lint": "standard"
},
"author": "Tierney Cyren <hello@bnb.im> (https://bnb.im/)",
"license": "MIT",
"devDependencies": {
"standard": "^16.0.3"
}
}

This specifically tells Node.js to parse your .js files under this package.json as ESM. Otherwise, by default (or when you use "type": "commonjs"), Node.js will parse your .js files as CommonJS. There's a few things to note:

Node.js specifically looks for the closest package.json to determine whether or not to parse .js as ESM or CommonJS.

"Closest" is important here. If there's a package.json that's closer to .js files than your project's package.json, and it does not have "type": "module" (or a dual export, which is out of the scope of this post), CommonJS will be used for those .js files. The most common/obvious example of this is the code within your /node_modules/ that may not be ESM, and shouldn't be parsed as such.

Further, it's worth noting that explicitly using .cjs overrides "type": "module". This is extremely useful if you're converting a codebase from CommonJS to ESM.

Why "type": "module"?

The Quick Answer

For you, the user, the straightforward answer to this is that using "type": "module" is a better developer experience than having to explicitly use .mjs in every single JavaScript file in your project if you're going to have a non-trivial number of files.

The Answer With More Context

Using "type": "module" is often going to be a better developer experience for maintainers for a number of reasons: