12. CommonJS と ES Modules

Node.js にある 2 つのモジュール形式。require と import、拡張子 .cjs ・ .mjs ・ .js と package.json の "type"、古いコードの見分け方

📅 作成: 2026-10-09 / 更新: 2026-10-09

なぜ 2 つあるのか

10 章で見た import ・ export(ES Modules)は、JavaScript の言語仕様として決まったモジュールの形式です。ところが Node.js は、言語仕様にモジュールが無かった時代に生まれたため、独自の形式 CommonJS(require と module.exports)を先に使い始めました。

CommonJS Node.js が独自に採用 ES Modules 言語仕様(ES2015)で決まる 両方が混ざって動く いまの Node.js 2009 〜 2015 〜 いま 古いコードと npm のパッケージには CommonJS がたくさん残っている。新しく書くなら ES Modules
先に広まった CommonJS と、言語の標準になった ES Modules が共存している

いまの Node.js は両方を扱えます。新しく書くコードは ES Modules にするのが基本ですが、古いコードや npm のパッケージには CommonJS がたくさん残っています。読めるようにしておく必要があります。

書き方の比較

することCommonJSES Modules
外に出すmodule.exports = { add }
exports.add = add
export function add() {}
export default …
取り込むconst { add } = require("./math.cjs")import { add } from "./math.mjs"
取り込むときその行を実行したとき(途中でも書ける)実行の前に、ファイルの先頭でまとめて
拡張子の省略できる(require("./math"))できない
strict モード書かなければ、ならないいつもなる
一番外側の await書けない書ける

CommonJS

math.cjs

function add(a, b) {
  return a + b;
}
module.exports = { add, PI_ISH: 3.14 };

main.cjs

const { add } = require("./math.cjs");
const math = require("./math.cjs");

console.log(add(2, 3));
console.log(math);
5
{ add: [Function: add], PI_ISH: 3.14 }

require はふつうの関数で、呼んだところでファイルを読み込み、module.exports に入れたものを返します。

ES Modules

math.mjs

export function add(a, b) {
  return a + b;
}
export const PI_ISH = 3.14;

main.mjs

import { add, PI_ISH } from "./math.mjs";

console.log(add(2, 3));
console.log(PI_ISH);
5
3.14

拡張子と "type" の決まり

Node.js がファイルをどちらの形式で読むかは、拡張子で決まります。.js のときだけは、近くの package.json の "type" で決まります。

拡張子は? .cjs .js .mjs いつも CommonJS いつも ES Modules 近くの package.json の "type" は? "module" "commonjs" 書いていない ES Modules CommonJS 中身から推測する import ・ export があれば ESM (警告が出ることがある)
迷いたくなければ .cjs ・ .mjs を使うか、package.json に "type" を書く

フォルダ app-esm に "type": "module" の package.json を置くと、その中の .js は ES Modules として読まれます。

app-esm/package.json

{
  "type": "module"
}

app-esm/main.js

import { basename } from "node:path";

console.log(basename(import.meta.filename));
console.log(typeof require);
main.js
undefined

"type": "commonjs" にすると、同じ .js でも CommonJS になります。CommonJS には require があり、ES Modules には無いことが分かります。

app-cjs/package.json

{
  "type": "commonjs"
}

app-cjs/main.js

const { basename } = require("node:path");

console.log(basename(__filename));
console.log(typeof require);
main.js
function

注意: "type" の無い package.json があるフォルダで、.js に import を書くと、動きはしますが「MODULE_TYPELESS_PACKAGE_JSON」という警告が出ます。形式が書かれていないので中身から推測した、という意味です。package.json に "type": "module" を書けば消えます。

お互いを読み込む

2 つの形式は、お互いを読み込めます。ただし、できることに少し差があります。

ES Modules から CommonJS を読む

import で読めます。module.exports 全体は、既定の export(import 名前 from)として受け取れます。名前付きで取り込めるのは、Node.js が中身から見つけられた名前だけです。

cjs-lib.cjs

exports.hello = () => "やあ";
exports.count = 1;

use-cjs.mjs

import lib, { hello } from "./cjs-lib.cjs";

console.log(hello());
console.log(Object.keys(lib));
やあ
[ 'hello', 'count' ]

ヒント: 名前付きの import がエラーになったら、既定の export として受け取ってから分けます。import lib from "./x.cjs"; の次に const { hello } = lib; と書けば、どんな CommonJS でも読めます。エラーのメッセージにも、この書き方が案内されます。

CommonJS から ES Modules を読む

この資料で使う Node.js(v26)では、require で ES Modules も読めます。受け取るのは、export したものをまとめたオブジェクトです。

esm-lib.mjs

export function add(a, b) {
  return a + b;
}
export default "既定の値";

use-esm.cjs

const lib = require("./esm-lib.mjs");

console.log(lib.add(1, 2));
console.log(lib.default);
3
既定の値

注意: 一番外側で await を使っている ES Modules は、require では読めません。また、古い版の Node.js では require で ES Modules を読めず、ERR_REQUIRE_ESM というエラーになります。古い資料に「CommonJS から ES Modules は読めない」と書かれているのはそのためです。

__dirname と import.meta

「このスクリプトのファイルがある場所」を知りたいときの書き方が、形式によって違います。

知りたいことCommonJSES Modules
このファイルのパス__filenameimport.meta.filename
このファイルのあるフォルダ__dirnameimport.meta.dirname
このファイルの URL(無い)import.meta.url(file:///…)

ファイルの読み込みで "memo.txt" のように書いたパスは、スクリプトの場所ではなく、node を実行したフォルダ(カレントフォルダ)から数えられます。スクリプトの隣のファイルを読みたいときは、import.meta.dirname から組み立てます。

tools/show.mjs

import { readFile } from "node:fs/promises";
import { basename, join } from "node:path";

console.log(basename(import.meta.dirname));
const fromScript = await readFile(join(import.meta.dirname, "..", "memo.txt"), "utf8");
const fromCurrent = await readFile("memo.txt", "utf8");
console.log(fromScript === fromCurrent);
tools
true

この例は、memo.txt のあるフォルダで node tools/show.mjs と実行したものです。カレントフォルダから数えた memo.txt と、スクリプトの 1 つ上のフォルダの memo.txt が同じファイルを指しています。別のフォルダから実行すると、カレントフォルダから数えた方だけが見つからなくなります。

古いコードの見分け方

コードや資料を読むときは、次の手がかりで形式を見分けます。

見えたもの形式
require(…) ・ module.exports ・ exports.名前CommonJS
__dirname ・ __filenameCommonJS
拡張子 .cjsCommonJS
import … from ・ exportES Modules
import.meta ・ 一番外側の awaitES Modules
拡張子 .mjs ・ package.json に "type": "module"ES Modules

決まり: 新しく書くコードは ES Modules にします。拡張子を .mjs にするか、package.json に "type": "module" を書きます。CommonJS は読めれば十分で、自分から書く必要はありません。ブラウザ(第2部)が理解するのも ES Modules だけです。