惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Full Disclosure
WordPress大学
WordPress大学
小众软件
小众软件
Cloudbric
Cloudbric
AWS News Blog
AWS News Blog
腾讯CDC
量子位
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Vulnerabilities – Threatpost
Scott Helme
Scott Helme
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
Jina AI
Jina AI
Attack and Defense Labs
Attack and Defense Labs
S
SegmentFault 最新的问题
Simon Willison's Weblog
Simon Willison's Weblog
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
Google Online Security Blog
Google Online Security Blog
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
罗磊的独立博客
L
LINUX DO - 最新话题
博客园 - Franky
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
The Last Watchdog
The Last Watchdog
J
Java Code Geeks
AI
AI
C
Cisco Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Cyber Attacks, Cyber Crime and Cyber Security
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
雷峰网
雷峰网
Help Net Security
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
I
Intezer
S
Securelist

ByteofDev

Making Postgres 42,000x slower because I am unemployed A Quick(ish) Introduction to Tuning Postgres JavaScript numbers have an (adoption) problem My 2025 JavaScript Wishlist JavaScript Benchmarking Is a Mess Replacing Disqus Commenting A deep dive into distributed database architectures 10 ways to speed up web image loading Tailwind has a scalability problem. How can we solve that? Lerna vs Turborepo vs Rush: Which is better in 2023? The 6 JavaScript Projects to watch in 2023 Top 5 Alternatives to React in 2023 React vs Svelte: Which is better in 2023? 10 ways to speed up JavaScript loading What is Bun, and does it live up to the hype? State of JS 2021 Results and Analysis State of the Web: React 10 ways to speed up web font loading State of the Web: Atomic CSS State of the Web: Static Site Generators State of the Web: Bundlers & Build Tools Migrating ByteofDev from SvelteKit to Astro State of the Web: Serverless Functions State of the Web: Deno Array.map() versus Array.forEach() A quick introduction to JavaScript Maps How I Built the Fastest JavaScript Data Differ State of the Web: WebAssembly When you should and shouldn't use React
How to use ESM on the web and in Node.js
Jacob Jackson · 2022-09-04 · via ByteofDev

ESM, also known as ES Modules (which itself is an acronym of ECMAScript Modules), is a modern module format built to replace older alternatives like CommonJS, AMD, and UMD. While its double acronym is anything but simple, ESM itself is relatively straightforward due to its JavaScript integration. However, because software like Node was built before ESM existed, we have to add some configuration to use ESM.

What is ESM?

ES Modules is a module format created as part of the ECMAScript (read: JavaScript) standard. ES6, which you might know for adding features like Promises and arrow functions, was the first version to include it. ES Modules aims to solve a significant problem in JavaScript: There is no built-in way to share code between scripts. Bundlers like Webpack and runtimes like Node include CommonJS (you probably have seen its require()s), but these tools have to implement CommonJS in a layer on top of vanilla JavaScript, and you can’t use CommonJS without that layer. Older module formats also suffer from outdated design decisions—many were designed >15 years ago when the web was a very different place.

ESM aims to solve all of these issues while making one module format universal. Today, Chrome, Safari, and Firefox fully support ESM, so you should not have any problem running it in modern browsers. Additionally, Node v12+ supports ESM, although you have to tell it you are using ESM rather than CommonJS. We will talk more about how to do this later on.

ESM Syntax

The syntax used to import and export modules is slightly more complicated than in other module systems like CommonJS, but it is still fairly easy to pick up. The most basic example is this:

// library.js
export const example = function () {
	console.log("hello world");
};

// script.js
import { example } from "./library.js";
example();

Here we import a function called example from library.js. Pretty simple. Of course, we only import one function there. If we want to import multiple things from the same file, we would do this:

// library.js
export const example = function () {
	console.log("hello world");
};
export const example2 = function () {
	console.log("Hello world 2");
};

// script.js
import { example, example2 } from "./library.js";

As you can see, we put both example and example2 between the brackets to import both exported variables. Now, what if a file only had one thing to export? We simplify imports by removing brackets if we use export default instead of export.

// library.js
export default function () {
	console.log("hello world");
}

// script.js
import defaultExample from "./library.js";
defaultExample();

Default exports also allow us to name the imported value whatever we want. We can do this with named exports, but it is a little more complicated:

import { example as newExampleName } from "./library.js";
newExampleName();

Unfortunately, all of these examples require imports to be at the top of the file. If you only want to import something when a particular condition is true, or even import with a dynamic URL, you can use dynamic imports. Dynamic imports allow you to import with a function, similar to require() in CommonJS, except asynchronous.

if (window.localStorage.get("loadModule") == "1") {
	// ./library.js will only load if `loadModule`==`1`
	import("./library.js").then((library) => {
		// handle the promise here
		library.default();
	});
}

Finally, you might want to import all exports in a module, which you can do using import * as:

// library.js
export const example = function () {
	console.log("hello world");
};
export const example2 = function () {
	console.log("hello world 2");
};

// script.js
import * as library from "./library.js";
library.example();
library.example2();

Using ESM with Node

Make sure you are using Node v12 or higher

Per-file

The simplest way to use ESM in Node.js is to use .mjs instead of .js as the file extension of your script. The new file extension tells Node to treat the file as ESM rather than CJS. This naming scheme also works the other way. You can use .cjs to designate a file as CommonJS if the default is ESM (more on that in the next section).

Package-wide

Often, you want a whole package to be ESM by default. To do this, you must add "type": "module" to your package.json. Adding "type": "module" means files will be treated as ESM files unless they have a .cjs extension. However, dependencies can still use CommonJS.

Importing CJS Modules From ESM

To ease the transition to ESM, Node can load CommonJS modules from ESM. You can retrieve the module.exports object by importing it as a default export. For example:

// example-package index.cjs
module.exports = {
	helloworld: function () {
		console.log("hello world");
	},
};

// script.mjs
import example from "example-package";
example.helloworld();

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 almost all CommonJS code in Node to its ESM equivalent. However, cjstoesm can’t convert all CJS features. The most notable feature missing is __dirname. __dirname is part of Node.js’ CommonJS environment but is excluded 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.

You will also have to manually migrate conditional imports. You can use dynamic imports to replace running require() dynamically, but problems arise due to how import() is asynchronous and require() is not. Luckily, there is a simple solution to this. If you are on Node.js v14.8 or later, you can use top-level await to make import() synchronous. For example,

// CommonJS
const module = boolean ? require("module1") : require("module2");

// ES Modules
const module = await (boolean ? import("module1") : import("module2"));

With these tips, you should not have much trouble converting CommonJS code to ES Modules.

Avoiding Node Entirely

In the past few years, developers sick of Node’s legacy design decisions like defaulting to CommonJS have created new runtimes, like Deno and Bun. Not only do these runtimes include ESM support, but they use ESM by default. Of course, you can still use Node-style CommonJS modules if necessary—in fact, unlike Node, Bun supports importing ES Module code from CommonJS code.

These runtimes include a variety of other unique features, but Node is catching up, so don’t jump to them too quickly. Either way, they are good choices to consider.

Using ESM on the Web

Native ESM

The simplest way to use ESM on the web is by utilizing native support. For about over 95% of users (Can I Use), ESM is supported without polyfills. To load a script with ESM, 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.

<script src="./fallback.js" nomodule>

Bundlers

While using the browser’s native ESM support is simple, you will be missing out on many features and optimizations to speed up your JavaScript loading, like tree shaking, support for CommonJS dependencies, and automatic fallback generation. Luckily, many bundlers support ESM by default. The web’s most notable modern ESM bundler 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 while allowing you to do more with plugins. Vite also supports including CommonJS code is necessary. Of course, including CommonJS still has the disadvantages already mentioned.

To create a Vite project, run npm create vite@latest, which 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.

Import Maps

While bundlers are great, you may want to avoid using them due to the increased complexity of the build process. However, you might still want to import things using Node-style short package specifiers (e.g. “react” rather than “https://esm.run/react”). Or perhaps you want to use a library that imports via specifiers like these, but you don’t want to modify its code. Luckily, modern browsers have a solution to this problem: import maps.

\Import maps are scripts with type="importmap" that contain JSON objects routing certain specifiers to URLs. For example, let’s say you wanted to be able to import things from "react". You would create the following import map:

<script type="importmap">
	{
		"imports": {
			"react": "https://esm.run/[email protected]"
		}
	}
</script>

The above import map essentially turns this import:

import { useEffect } from "react";

Into this:

import { useEffect } from "https://esm.run/[email protected]";

This will affect all modules on that webpage. You can also map by a prefix:

<script type="importmap">
	{
		"imports": {
			"scripts/": "https://example.com/scripts/"
		}
	}
</script>

This will reroute all scripts/... imports to https://example.com/scripts/.... For example, scripts/test would reroute to https://example.com/scripts/test.

Imports maps are currently all modern browsers, but some older versions

Conclusion

ES Modules is the future of JavaScript and should continue to see adoption rise as more tools support it. Unfortunately, despite having existed for almost ten years, it still isn’t the standard in many areas due to the difficulty of migrating legacy software. Hopefully, this guide helped you integrate ESM into your software.