DEV Community

loading...

Explicit ESM in Node.js with .mjs

Tierney Cyren
⬡.js - Developer Advocacy at Microsoft. Does: Node.js, Electron, TC39, OpenJS Foundation, npm. Is: twete expert. script kiddie. Stringly typed. Opinions mine. they/he. Demiboy.
Updated on ・2 min read

A while ago, Node.js introduced support for ECMAScript Modules (ESM). ESM is the standardized modules implementation that's been built-in to JavaScript. This differs rather significantly from CommonJS (CJS), which is the module system that Node.js has shipped with for over a decade that make them relatively incompatible.

There are a number of different components of Node.js that have been intentionally structured to allow you to use standard (as in, how the ECMAScript Specification defines it) ESM by default and extend/augment that experience if you'd like to.

Today, I want to get into one of the baisc elements of ESM in Node.js: the .mjs and .cjs extensions.

Why .mjs (and .cjs)?

The Quick Answer

The straightforward answer to this is that having different file extensions allows you to be explicit in how you want to run your code - .mjs will always be run as ESM, .cjs will always be run as CommonJS.

The Answer With Context

Because of the differences in how ESM and CommonJS work, Node.js runs them differently by default. This results in the runtime needing an indicator about which way you want to run your code - as ESM or as CommonJS.

There's three different ways that this indicator could be expressed: explicitly, implicitly, and by default.

To not break over a decade of projects and over a million modules that are expecting to Just Work, the default the project landed on was CommonJS - sensible, especially when you consider millions of lines of code and multitudes of applications already running in this way.

The way to explicitly assert that the code you're running is ESM and should be run as such is to just use the .mjs file extension (which, if you're concerned, is also supported in web browsers as long as the Content-Type: text/javascript header is sent and is actually recommended by V8). The official overview of this is documented in the determining module system section of the Node.js Packages documentation.

When you use .mjs, Node.js knows that you've written ESM and will parse your JavaScript as such. The same is true for .cjs - Node.js knows that .cjs should run as CommonJS, and will parse your JavaScript as CommonJS.

Discussion (4)

Collapse
tojacob profile image
Jacob Samuel G.

.ts 😋

Collapse
beernutz profile image
beernutz

Nice concise article. Thank you!

Collapse
bnb profile image
Tierney Cyren Author

Thank you! <3

Collapse
jismaelcr profile image
jismaelcr

Excelente artículo 👍