🐣

CommonJSとES Modulesとは結局なんなのか

に公開

TL;DR

  • JavaScriptでコードをモジュール化するための仕組み
  • CommonJSは従来からある仕組みで、require()module.exportsを使用。拡張子は.cjs
  • ES Modulesはモダンな仕組みで、importexportを使用。拡張子は.mjs
  • 最近はES Modulesが主流になりつつある

はじめに

JavaScriptでは、コードの再利用や分割のためにモジュールシステムが導入されています。

その中でも現在主流になっているのがES Modulesであり、
普段何気なく使っているimportexport文はこのES Modulesでモジュールをやり取りするための構文です。

CommonJS

CommonJS(以下CJS)はNode.jsで長く採用されているモジュールシステムです。

サーバーサイドで実行されるため、そのままではブラウザでは動作しません。
ブラウザで利用する場合はWebpackなどのバンドラを使い、ブラウザ対応の形式(主にES Modules)へ変換する必要があります。

拡張子がcjsのファイルはCJSとして扱われ、以下のようにrequiremodule.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として扱われ、以下のようにimportexportを使用します。

// インポート
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.jsontypeフィールドで制御できるためです。

// 例
{
  "type": "module" // この場合、ESMとして扱われる
}
  • "type": "module".jsはESMとして扱われる
  • "type": "commonjs"またはフィールド未指定 → .jsはCJSとして扱われる(デフォルト)

ただし.mjs(常にESM)と.cjs(常にCJS)は拡張子で明示できるため、混在するプロジェクトではこれらを使い分けるのが安全です。

普段これらを意識していない理由

多くの方は、requireをほとんど使ったことがない、またはモジュールについて意識したことがないかもしれません。私もそうです。

これは、webpackViteなどのバンドラが、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の歴史を知るとより理解しやすいので、知識を深めたい方はモジュールシステムが生まれた背景から深掘りしてみることをおすすめします。

モダンなフロントエンド開発に慣れていると、便利な分こういった基礎知識が抜けがちなので、ちゃんと理解していきたいところです。

参考

https://qiita.com/Itsuki54/items/d4f1be24ffc3a93af83b
https://zenn.dev/yodaka/articles/596f441acf1cf3
https://zenn.dev/oukayuka/books/cb48853473000fb7fcde/viewer
https://zenn.dev/medicalforce/articles/28a95c3c14aa9b

Discussion