DEV Community

Tierney Cyren
Tierney Cyren

Posted on

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

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

  • It minimizes manual changes and potential mistakes, allowing a single line of text to control parsing.
  • It makes migrating from CommonJS to ESM easier.
    • It depends on how you'd like to do it, but one strategy is to chunk out work of converting your applications to ESM one bit at a time by setting "type": "module" and converting all the CommonJS code to use the .cjs file extension.
  • It allows ecosystem tooling to quickly determine if your projects are using ESM or not.
    • Note that JSON modules (and therefore importing package.json) are only supported behind the --experimental-json-modules flag. It does seem that necessary proposals to streamline this seem to be making pretty decent progress through the relevant standards processes.

Top comments (0)