CommonJSとES Modulesとは結局なんなのか
TL;DR
- JavaScriptでコードをモジュール化するための仕組み
-
CommonJSは従来からある仕組みで、
require()とmodule.exportsを使用。拡張子は.cjs -
ES Modulesはモダンな仕組みで、
importとexportを使用。拡張子は.mjs - 最近はES Modulesが主流になりつつある
はじめに
JavaScriptでは、コードの再利用や分割のためにモジュールシステムが導入されています。
その中でも現在主流になっているのがES Modulesであり、
普段何気なく使っているimportやexport文はこのES Modulesでモジュールをやり取りするための構文です。
CommonJS
CommonJS(以下CJS)はNode.jsで長く採用されているモジュールシステムです。
サーバーサイドで実行されるため、そのままではブラウザでは動作しません。
ブラウザで利用する場合はWebpackなどのバンドラを使い、ブラウザ対応の形式(主にES Modules)へ変換する必要があります。
拡張子がcjsのファイルはCJSとして扱われ、以下のようにrequireとmodule.exportsを使用します。
// インポート
const module = require('module-name');
// エクスポート
module.exports = {
functionName,
variableName,
};
CJS側からES Modulesを直接require()で読み込むことはできません。
また、CJSでのモジュールの読み込みは同期的に行われるため、モジュールの読み込みが完了するまで他の処理はブロックされます。
ES Modules
ES Modules(以下ESM)はES2015(ES6)で正式に策定された比較的新しいモジュールシステムです。
Webブラウザが標準対応しており、Node.jsでもサポートされています。
クライアントサイド・サーバーサイドの両方で利用可能です。
拡張子がmjsのファイルはESMとして扱われ、以下のようにimportとexportを使用します。
// インポート
import { functionName, variableName } from 'module-name';
// エクスポート
export const functionName = () => { ... };
export const variableName = 'value';
CJSがESMで書かれたモジュールを呼ぶことができなかったのに対し、ESMではCJSで書かれたモジュールも呼ぶことができます。
また、こちらもCJSとは反対に非同期で行われるため、パフォーマンスの面で有利です。
現在はESMにのみをサポートするライブラリも多く、これからはもっと主流になっていくのではないかと思います。
.jsと何が違うのか
実際のプロジェクトでは.jsファイルのみを使っているケースが多いです。
これは、.jsの扱い(CJSかESMか)がpackage.jsonのtypeフィールドで制御できるためです。
// 例
{
"type": "module" // この場合、ESMとして扱われる
}
-
"type": "module"→.jsはESMとして扱われる -
"type": "commonjs"またはフィールド未指定 →.jsはCJSとして扱われる(デフォルト)
ただし.mjs(常にESM)と.cjs(常にCJS)は拡張子で明示できるため、混在するプロジェクトではこれらを使い分けるのが安全です。
普段これらを意識していない理由
多くの方は、requireをほとんど使ったことがない、またはモジュールについて意識したことがないかもしれません。私もそうです。
これは、webpackやViteなどのバンドラが、CJSとEMSの両方を内部で解決・統合してくれているからです。
例えばReactは内部的にはCJS形式で提供されていますが、import React from 'react'と書いても動作するのは、ビルド時にwebpackが依存関係を変換してバンドルしているためです。
ReactやVueなどのフロントエンドフレームワークでは、内部的にWebpackやViteなどのバンドラが使われているため、
モジュールを意識することなくESM構文(import)だけを使うことができるというわけです。
まとめ
最後にCJSとEMSを比較してみます。
| CommonJS | ES Modules | |
|---|---|---|
| 歴史 | 2009年(Node.jsで採用) | 2015年(ES6で正式導入) |
| 拡張子 | .cjs |
.mjs |
| インポート/エクスポート |
require / module.exports
|
import / export
|
| 実行環境 | 主にサーバーサイド(Node.js) | クライアントとサーバーサイド両方 |
| 読み込み | 同期的 | 非同期的 |
| 静的解析 | 不可 | 可能(構文解析で依存関係が分かる) |
| strictモード | 明示的に必要("use strict") |
デフォルトで有効 |
最後に
この記事では触れていませんが、JavaScriptの歴史を知るとより理解しやすいので、知識を深めたい方はモジュールシステムが生まれた背景から深掘りしてみることをおすすめします。
モダンなフロントエンド開発に慣れていると、便利な分こういった基礎知識が抜けがちなので、ちゃんと理解していきたいところです。
参考
Discussion