VOOZH about

URL: https://blog.logrocket.com/es-modules-in-node-js-12-from-experimental-to-release/

โ‡ฑ ES modules in Node.js 12, from experimental to release - LogRocket Blog


2019-07-22
1578
#node
Brian De Sousa
3860
๐Ÿ‘ Image

See how LogRocket's Galileo AI surfaces the most severe issues for you

No signup required

Check it out

Different forms of modularization have been around in the JavaScript ecosystem for years. Developers have used well-defined specifications such as AMD or CommonJS as well as simple coding patterns like the revealing module pattern to gain the benefits of a well-modularized solution.

๐Ÿ‘ ES Modules In Node.js 12

Modules can be used on the client side in browsers or on the server side in Node.js. Sometimes code is transpiled from one module format to another using tools like Babel. All of this makes for a messy and complex JavaScript module state.

Enter ES modules โ€” more specifically, ES modules in Node.js.

Tip: This article focuses on ES modules in Node.js. Check out โ€œCommonJS vs AMD vs RequireJS vs ES6 Modulesโ€ for an excellent comparison of module systems not specific to Node.js.

๐Ÿš€ Sign up for The Replay newsletter

The Replay is a weekly newsletter for dev and engineering leaders.

Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.

A brief history of ES module support

Letโ€™s look at some of the key milestones for ES module support:

  • June 2015 โ€“ September 2017: Major browsers add experimental support for ES modules hidden behind developer flags. The primary means of developing JavaScript using ES modules is by transpiling code using tools like Babel.
  • September 2017: Node.js v8.5 includes experimental support for ES modules.
  • September 2017 โ€“ May 2018: Major browsers begin to support the ES module specification without developer flags, including:
    1. Chrome 61, on 5 September 2017
    2. Safari 11, on 18 September 2017
    3. Firefox 60, on 8 May 2018
  • October 2018: A new module implementation plan is created. The plan includes several phases for replacing the current experimental implementation with a new implementation, following three guiding principles from day one:
    1. Comply with the ES specification
    2. Node should do things the same way browsers do as much as possible
    3. Donโ€™t break existing CommonJS modules

Tip: The Node.js Modules team has provided a more detailed set of guiding principles for the new implementation.

  • October 2019: Node 12 entered long-term support. The goal was to release full support for ES modules.

What is ES module support and why is it so important for Node.js?

For a couple reasons. For one thing, all major browsers already support ES modules โ€” you can see for yourself here. Supporting ES modules on the server side in Node.js out of the box will allow full-stack developers to naturally write modular, reusable JavaScript for both the client and server.

For another thing, experimental features in Node.js are subject to non-backward-compatible changes or removal in future versions. That being said, experimental ES module support has been around in Node for a few years and is not expected to change dramatically before October 2019.

Modules in Node.js

What are CommonJS modules?

The de facto standard for modules in Node.js currently (mid-2019 at the time of writing) is CommonJS. CommonJS modules are defined in normal .js files using module.exports. Modules can be used later within other .js files with the require() function. For example:

// foo.js
module.exports = function() { 
 return 'Hello foo!';
}

// index.js
var foo = require('./foo');
console.log(foo()); // Hello foo!

Use Node to run this example with node index.js.

ES modules

Since Node v8.5, developers have been able to run variations of support for the ES modules specification using the --experimental-modules flag. As of Node v12.4, modules can be defined in .mjs files (or .js files under certain circumstances). For example:

// foo.mjs
export function foo() { 
 return 'Hello foo!'; 
}

// index.mjs
import { foo } from './foo.mjs';
console.log(foo()); // Hello foo!

Use Node to run this example with node --experimental-modules index.mjs.

Using CommonJS and ES modules in the same application

In some ways, supporting ES modules in browsers may have been a little simpler than supporting ES modules in Node because Node already had a well-defined CommonJS module system. Luckily, the community has done a fantastic job of ensuring developers can work with both types of modules at the same time and even import from one to the other.

For example, letโ€™s say we have two modules. The first is a CommonJS module, and the second is an ES module (note the different file extensions):

// cjs-module-a.js
module.exports = function() {
 return 'I am CJS module A';
};

// esm-module-a.mjs
export function esmModuleA() {
 return 'I am ESM Module A';
};
export default esmModuleA;

To use the CommonJS module in an ES module script (note the .mjs extension and use of the import keyword):

// index.mjs
import esmModuleA from './esm-module-a.mjs';
import cjsModuleA from './cjs-module-a.js';
console.log(`esmModuleA loaded from an ES Module: ${esmModuleA()}`);
console.log(`cjsModuleA loaded from an ES Module: ${cjsModuleA()}`);

Use Node to run this example with node --experimental-modules index.mjs.

To use the ES module in a standard CommonJS script (note the .js extension and use of the require() function):

// index.js
// synchronously load CommonJS module
const cjsModuleA = require('./cjs-module-a');
console.log(`cjsModuleA loaded synchronously from an CJS Module: ${cjsModuleA()}`);

// asynchronously load ES module using CommonJS
async function main() {
 const {esmModuleA} = await import('./esm-module-a.mjs');
 console.log(`esmModuleA loaded asynchronously from a CJS module: ${esmModuleA()}`);
}
main();

These examples provide a basic demonstration of how to use CommonJS and ES modules together in the same application. Check out โ€œNative ES Modules in NodeJS: Status and Future Directions, Part Iโ€ by Gil Tayar for a deeper dive into CommonJS and ES Module interoperability.

Modules in Node.js: Future state

At the time of writing, the new module implementation plan is in its third and final phase. Phase 3 is planned to be completed at the same time that Node 12 LTS is released and when ES module support will be available without the -experimental-modules flag.

Phase 3 will likely bring a few big improvements to round out the ES module implementation.

Loaders solution

Developers expect module loading systems to be flexible and full-featured. Here are a few of the key features in development in the Node.js module loader solution:

  • Code coverage/instrumentation: Enable developer tools to retrieve data about CJS and ESM module usage.
  • Pluggable loaders: Allow developers to include loader plugins in their packages that can define new behaviors for loading modules from specific file extensions or mimetypes, or even files without extensions.
  • Runtime loaders: Allow files referenced in import statements to be transpiled at import time (runtime).
  • Arbitrary sources for modules: Allow modules to be loaded from sources other than the file system (e.g., load a module from a URL).
  • Mock modules: Allow modules to be replaced with mocks while testing.

You can view the full list here.

"exports" object in package.json

While the naming and syntax is not final, the idea here is to have an object somewhere in the package.json file that allows packages to provide โ€œprettyโ€ entry points for different components within the package. Take this package.json as an example of a possible implementation:

{
 "name": "@myorg/mypackage",
 "version": "1.0.0",
 "type": "module",
 "main": "./dist/index.js",
 "exports": {
 ".": "./src/mypackage.mjs",
 "./data": "./data/somedir/someotherdir/index.mjs"
 }
}

Developers would be able to import the data component of @myorg/mypackage like this:

import { MyModule } from '@myorg/mypackage/data

Referencing the package root with the packageโ€™s name

When referencing one module from another module within the same package, you may end up with a lot of backtracking that looks like this:

import coolcomponent from '../../../coolcomponent/module.js

If this change is implemented, then backtracking can be replaced with a reference to the packageโ€™s name as defined in package.json. The new code would look like this:

import coolcomponent from 'mypackage/coolcomponent/module.js

Supporting dual ESM/CommonJS packages

Allowing an npm package to contain CJS and ES modules alongside each other is important to ensure there is a backwards-compatible, developer-friendly path to migrate from CommonJS to ES modules. This has often been referred to as โ€œdual-modeโ€ support.

The status quo approach to dual-mode support is for the existing main entry point in package.json to point to a CommonJS entry point. If an npm package contains ES modules and the developer wants to use them, they need to use deep imports to access those modules (e.g., import 'pkg/module.mjs'). This is the dual-mode solution that is likely to ship with Node.js 12 LTS.


Over 200k developers use LogRocket to create better digital experiences

๐Ÿ‘ Image
Learn more โ†’

There were some other proposals for dual-mode support. This widely debated proposal includes some options for making it easier for developers to ship packages with two separate implementations (ESM and CJS), but this proposal failed to reach consensus.

A newer proposal for require of ESM suggests a different approach that allows developers to resolve ES modules with require(). This proposal is still open but has went silent and is unlikely to be included in Node 12 LTS.

ES modules vs. CommonJS

While the goal is for ES modules to eventually replace CommonJS modules in Node.js, no one knows what the future holds โ€” nor how long before CommonJS module support disappears. But one thing is for sure: Node developers have spent considerable time and effort ensuring a seamless transition to a future without CommonJS.

They have done a fantastic job striking a balance between ensuring both module types interoperate with each other while trying not to introduce too many new dual-mode APIs that would become useless once the critical mass has migrated and it comes time to remove support for CommonJS from Node.

So when will CommonJS be removed from Node.js? Letโ€™s make a wild, baseless prediction and say Node 18 with an --experimental-no-commonjs-modules and Node 20 for the final sunset. The future of modular JavaScript across browsers, servers and everywhere else JavaScript runs is an exciting one!

200s only ๐Ÿ‘ Image
Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If youโ€™re interested in ensuring requests to the backend or third-party services are successful, try LogRocket.

๐Ÿ‘ LogRocket Network Request Monitoring

LogRocket lets you replay user sessions, eliminating guesswork around why bugs happen by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings โ€” compatible with all frameworks.

LogRocket's Galileo AI watches sessions for you, instantly identifying and explaining user struggles with automated monitoring of your entire product experience.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.

๐Ÿ‘ Image
๐Ÿ‘ Image
๐Ÿ‘ Image

Stop guessing about your digital experience with LogRocket

Get started for free

Recent posts:

What is TSRX?: What JSX would look like if it were designed today

TSRX adds first-class control flow, conditional hooks, and scoped styles to React via a TypeScript compiler extension โ€” no new framework required.

๐Ÿ‘ Image
Ikeh Akinyemi
Jun 12, 2026 โ‹… 6 min read

How to add authentication to a React Native app with Better Auth

Learn how to build a full React Native auth system using Better Auth and Expo โ€” with email/password login, Google OAuth, session persistence, and protected routes.

๐Ÿ‘ Image
Chinwike Maduabuchi
Jun 9, 2026 โ‹… 13 min read

AI dev tool power rankings & comparison [June 2026]

Compare the top AI development tools and models of June 2026. View updated rankings, feature breakdowns, and find the best fit for you.

๐Ÿ‘ Image
Chizaram Ken
Jun 8, 2026 โ‹… 11 min read

How to check username availability at scale with Bloom filters

Learn how Bloom filters reduce database lookups for username availability checks while preserving correctness at scale.

๐Ÿ‘ Image
Rosario De Chiara
Jun 8, 2026 โ‹… 6 min read
View all posts

Would you be interested in joining LogRocket's developer community?

Join LogRocketโ€™s Content Advisory Board. Youโ€™ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Sign up now