import example from "example-package";
example.helloworld();
module.exports = {
helloworld: function () {
console.log("hello world");
This feature is very helpful as it allows you to use NPM packages built with CommonJS.
Importing ES Modules from CJS
Unfortunately, Node does not support importing ES Modules from CommonJS modules. Luckily, many compilers allow you to convert ESM to CommonJS, allowing you to write a library that offers a great experience for both groups. TypeScript is the most notable in this category. You can write code using ES Modules and target both ESM and CJS.
Migrating from CJS To ESM
There are tools to help make migrating code from CommonJS to ES Modules easy. One of the most popular tools is cjstoesm, a command line tool that automatically transforms CommonJS code in Node to its ESM equivalent. Almost all CommonJS code is transformed. However, there are some things that are not transformed. The most notable is the __dirname
. __dirname
is not part of Node.js, but it is one thing that Node does not suppport in “ESM mode”. Luckily, there are replacements. A simple way to polyfill __dirname
is to do this:
const __dirname = new URL(".", import.meta.url).pathname;
This snippet uses import.meta.url
, which is available in all ESM contexts.
Another thing that is harder to migrate is conditional imports. You can use dynamic importing to replace running require()
dynamically, but problems arise with that due to how import()
is asynchronous and require()
is not. Luckily, there is a simple solution to this. If you are using Node.js v14.8 or later, you can use top-level await to make import()
synchronous. For example,
const module = boolean ? require("module1") : require("module2");
const module = await (boolean ? import("module1") : import("module2"));
With these tips, you should not have much trouble at all converting CommonJS code to ES Modules.
Using ESM on the Web
Native ESM
The simplest way to use ESM on the web is by utilizing the native support for it. For about over 95% of users (Caniuse), ESM is supported without polyfills. To load a script with ESM, you need to add type="module"
in the script tag.
<script src="./esmscript.js" type="module">
Then, all modules you import from that script will also load as ES modules.
To create a backup script for browsers that do not support ES Modules, you can use the nomodule
attribute. nomodule
tells any browser that supports ES Modules not to load the script, so only browsers without ESM support will load it.
Bundlers
While just using the browser’s native ESM support is simple to start with, you will be missing out on a lot of features and optimizations, like tree shaking, support for CommonJS dependencies or automatic fallback generation. Luckily, many bundlers support ESM by default. The most notable modern ESM bundlers for the web is Vite. Vite is a bundler created by the Vue team that is extremely fast and feature-rich. By default, Vite minifies and optimizes your code, and you can do a lot more with Vite/Rollup plugins. To create a Vite project, you have to run npm create [email protected]
. This will help you set up a project with Vite using ES Modules. If you want legacy browser support using nomodule
, you can use the plugin @vitejs/plugin-legacy. Another feature that Vite, along with most other bundlers, has is support for dependencies that use CommonJS. Obviously, CommonJS requires transformation and therefore can add some code weight, but it is better than nothing, and you can still use ESM along with it.
Conclusion
Now you are familiar with ES Modules’ syntax and how to implement it on the web and in Node.js. Make sure to sign up for the newsletter or RSS below. I hope this article has been helpful for you, and thanks for reading.