module.exports / exports(CommonJS エクスポート)
Node.jsでは他のファイルから利用できるように変数・関数・クラスを『エクスポート』します。CommonJS方式では module.exports や exports を使い、ES Modules方式では export や export default を使います。
CommonJS — module.exports
module.exports にオブジェクト・関数・値を代入することで、他のファイルから require() で読み込めるようになります。
kiryu.js
// kiryu.js — 桐生一馬の情報をエクスポートする
function getProfile() {
return {
name: "桐生一馬",
organization: "堂島組",
bgm: "ばかみたい"
};
}
function getIntro(name) {
return name + "は「堂島の龍」として知られている。";
}
// オブジェクトとしてまとめてエクスポートする
module.exports = {
getProfile: getProfile,
getIntro: getIntro
};
main.js
// main.js — kiryu.js を読み込んで使う
var kiryu = require('./kiryu');
var profile = kiryu.getProfile();
console.log(profile.name + " / " + profile.organization);
console.log(kiryu.getIntro(profile.name));
node main.js 桐生一馬 / 堂島組 桐生一馬は「堂島の龍」として知られている。
module.exports と exports の違い
exports は module.exports への参照(ショートカット)として最初から用意されています。プロパティを追加するだけなら exports.プロパティ名 = 値 と書けます。
// exports に直接プロパティを追加する(OK)
exports.name = "真島吾朗";
exports.family = "真島組";
// exports を丸ごと上書きすると module.exports との参照が切れる(機能しない)
exports = { name: "真島吾朗" }; // これは機能しない
exports を = で代入すると参照が切れてエクスポートが機能しなくなります。オブジェクト全体を代入するときは必ず module.exports を使います。
| 書き方 | 動作 |
|---|---|
module.exports = { ... } | エクスポートするオブジェクトを丸ごと差し替えます。関数ひとつだけをエクスポートするときにも使えます。 |
exports.key = value | 既存の module.exports オブジェクトにプロパティを追加します。代入(=)で上書きすると機能しなくなります。 |
ES Modules — export / export default
ES Modulesでは2種類のエクスポートがあります。名前付きエクスポート(export)とデフォルトエクスポート(export default)です。
majima.mjs
// majima.mjs — 名前付きエクスポートとデフォルトエクスポートの例
// 名前付きエクスポート(複数書ける)
export var organization = "真島組";
export function getQuote() {
return "どこまでも付き合うたるわ";
}
// デフォルトエクスポート(1ファイル1個だけ)
export default {
name: "真島吾朗",
bgm: "GET TO THE TOP!"
};
app.mjs
// app.mjs — majima.mjs を読み込む
import majima, { organization, getQuote } from './majima.mjs';
console.log(majima.name + " / " + organization);
console.log(getQuote());
console.log("BGM: " + majima.bgm);
node app.mjs 真島吾朗 / 真島組 どこまでも付き合うたるわ BGM: GET TO THE TOP!
名前付きエクスポートとデフォルトエクスポートの使い分け
| 種類 | 構文 | 読み込み側の構文 | 用途 |
|---|---|---|---|
| 名前付きエクスポート | export var x = ...export function f() {} | import { x, f } from './file.js' | ユーティリティ関数・定数などを複数エクスポートするとき。 |
| デフォルトエクスポート | export default value | import anything from './file.js' | そのファイルの「メイン」となるクラスや関数を1つだけエクスポートするとき。 |
サンプルコード — 複数ファイルの連携
複数のキャラクター情報をモジュールとして分割するサンプルです。
characters.js(CommonJS)
// characters.js — 龍が如くキャラクター情報モジュール
var characters = [
{ name: "桐生一馬", organization: "堂島組", bgm: "ばかみたい" },
{ name: "真島吾朗", organization: "真島組", bgm: "GET TO THE TOP!" },
{ name: "春日一番", organization: "イチジョウグループ", bgm: "Receive and Bite You" },
{ name: "錦山彰", organization: "錦山組", bgm: "Judgement" },
{ name: "澤村遥", organization: "フリーランス", bgm: "Always in My Heart" }
];
// 全キャラクターの一覧を返す関数
function getAll() {
return characters;
}
// 名前でキャラクターを検索する関数
function findByName(name) {
return characters.find(function(c) {
return c.name === name;
}) || null;
}
module.exports = {
getAll: getAll,
findByName: findByName
};
app.js
// app.js — characters.js モジュールを読み込んで使う
var characters = require('./characters');
// 全キャラを一覧表示する
console.log("--- 龍が如くキャラクター ---");
characters.getAll().forEach(function(c, i) {
console.log((i + 1) + ". " + c.name + " [" + c.organization + "]");
});
// 名前で検索する
var found = characters.findByName("春日一番");
if (found) {
console.log("\n見つかりました: " + found.name + " / BGM: " + found.bgm);
}
node app.js --- 龍が如くキャラクター --- 1. 桐生一馬 [堂島組] 2. 真島吾朗 [真島組] 3. 春日一番 [イチジョウグループ] 4. 錦山彰 [錦山組] 5. 澤村遥 [フリーランス] 見つかりました: 春日一番 / BGM: Receive and Bite You
CommonJS と ES Modules の相互運用
CommonJSとES Modulesは混在できますが、いくつかの制約があります。
| 読み込む側 | 読み込まれる側 | 方法 |
|---|---|---|
| ES Modules(import) | CommonJS(module.exports) | そのまま import pkg from './file.cjs' で読み込めます。 |
| CommonJS(require) | ES Modules(export) | require() では読み込めません。動的 import() を使います。 |
よくあるミス
exports を = で代入して上書きしてしまう
exports は module.exports への参照として機能しますが、= で代入するとその参照が切れてしまいます。代入後に exports に何を設定しても module.exports には反映されず、読み込み側には空のオブジェクトが返ります。
NGの書き方(exports を上書きしている):
// NG: exports を丸ごと上書きすると module.exports との参照が切れる
exports = { name: "桐生一馬" };
node main.js
{}
OKの書き方(module.exports を使う):
// OK: オブジェクト全体を代入するときは module.exports を使う
module.exports = { name: "桐生一馬" };
node main.js
{ name: '桐生一馬' }
プロパティを追加するだけなら exports.name = "桐生一馬" のようにプロパティへの代入は問題ありません。問題になるのは exports = ... と exports 変数そのものを上書きする場合です。
2つのモジュールが互いに require() する循環参照
ファイルAがファイルBを require() し、ファイルBもファイルAを require() するような循環参照が発生すると、Node.jsは無限ループを防ぐために最初に読み込まれたモジュールを「まだ初期化中」として空のオブジェクト {} を返します。
a.js
// a.js
var b = require('./b');
console.log('a.js が読み込んだ b:', b);
module.exports = { from: 'a' };
b.js
// b.js
var a = require('./a'); // a.js はまだ初期化中なので {} が返る
console.log('b.js が読み込んだ a:', a);
module.exports = { from: 'b' };
node a.js
b.js が読み込んだ a: {}
a.js が読み込んだ b: { from: 'b' }
b.js が a.js を読み込んだ時点では a.js の module.exports への代入がまだ実行されていないため、b.js 側には空のオブジェクト {} が渡ります。循環参照が発生しないようにモジュール構成を見直すか、関数の中で遅延して require() することで回避できます。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。