はじめに
投稿時点で、筆者は知識ゼロの状態から勉強を初めて2ヶ月程度の実力です。
そのため、理解不足や説明不足、誤った内容や呼び方をしている可能性があります。
万が一参考にする場合は上記の点を考慮した上でご一読ください。
説明文の内容はChatGPT先生からの引用も多いです。
本題
「CommonJS Modules」
以下、ChatGPT先生より引用。
ECMAScript ModulesとCommonJS Modulesは、JavaScriptのモジュールシステムの2つの主要な形式です。これらの形式は、異なるシステムで使用されるため、それぞれの違いを理解することが重要です。
CommonJS Modulesは、Node.jsで広く使用される形式です。この形式は、require関数を使用してモジュールを読み込み、module.exportsまたはexportsオブジェクトを使用してモジュールをエクスポートします。また、動的な構造を持ち、モジュールの読み込みは同期的に行われます。
簡単に言うと、CommonJS Modulesは、require関数を使用して、動的な構造を持ち、同期的にモジュールを読み込むことができます。
ECMAScriptのエクスポート・インポート記述については下記にまとめました。
【初心者】エクスポート・インポート記述メモ( ECMAScript Modules [ ESM ] 構文 )【備忘録】
ここでは関数、定数のエクスポート・インポートを例にしています。
エクスポート
エクスポート(出力)する手段は下記の3パターンがある。
module.exports = 値
module.exports.名前 = 値
exports.名前 = 値
module.exports
エクスポート例(1つの値のみを渡す場合)
~ 通常の関数の場合 ~
1.名前なし関数(関数式 [ 無名関数 / 関数リテラル ])
module.exports = function () { /*…*/ }; // 無名関数を直接エクスポート
const Counter = function () { /*…*/ }; // 無名関数をCounterという定数名で定義
module.exports = Counter; // 定数Counterをエクスポート
2.名前付き関数(関数宣言 [ function文 ] )
module.exports = function Counter() { /*…*/ }; // Counterという関数を定義し、それを直接エクスポート
function Counter() { /*…*/ }; // 関数をCounterという関数名で定義
module.exports = Counter; // 関数Counterをエクスポート
function myFunction() { /*…*/ }; // 関数をmyFunctionという関数名で定義
const Counter = myFunction; // 関数myFunctionをCounterという定数名で定義
module.exports = Counter; // 定数Counterをエクスポート
~ アロー関数の場合 ~
アロー関数では、通常の関数定義とは異なり、名前付き関数を定義することはできません。
アロー関数では function
文を使用せずに名前付き関数を定義することはできません。
名前なし関数(関数式 [ 無名関数 / 関数リテラル ])
module.exports = () => { /*…*/ }; // 無名のアロー関数を直接エクスポート
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
module.exports = Counter; // 定数Counterをエクスポート
エクスポート例(復数の値のみを渡す場合)
まずはNG例から紹介します。
~ NG例 ~
module.exports = function Counter() { /*…*/ }; // Counterという関数を定義し、それを直接エクスポート
module.exports = function Button() { /*…*/ }; // Buttonという関数を定義し、それを直接エクスポート
module.exports = () => { /*…*/ }; // 無名のアロー関数を直接エクスポート
module.exports = () => { /*…*/ }; // 無名のアロー関数を直接エクスポート
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = Counter; // 定数Counterをエクスポート
module.exports = Button; // 定数Buttonをエクスポート
上記のパターンは全てNGです。
理由は、2回目のmodule.exports
が、1回目のmodule.exports
を上書きしてしまうためです。
複数の値を渡したい場合は下記のようなやり方があります。
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = { Counter, Button }; // 定数Counterと定数Buttonをエクスポート
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = {
Counter: Counter, // 定数CounterをCounterという名前でエクスポート
Button: Button // 定数ButtonをButtonという名前でエクスポート
};
これは2つとも同じ役割となり、どちらも分割代入を利用してエクスポートしています。
1つめの記述は2つめの記述を省略したやり方です。
2つめをみると、エクスポートする際にどれが何かを識別できるように名前を付与しているのがわかります。
このように、復数の値をエクスポートする際には名前を付与する必要があります。
もし別名でエクスポートしたい場合は下記の例がわかりやすいです。
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = {
myCounter: Counter, // 定数CounterをmyCounterという名前でエクスポート
myButton: Button // 定数ButtonをmyButtonという名前でエクスポート
};
実際は、複数のモジュールで同じ変数名を使用することが多いため、本来はプロパティ名と変数名を一致させるほうが一般的だそうです。(例:Counter: Counter
)
ここで、復数の値をエクスポートするやり方を紹介しましたが、復数の値を渡す際のやり方は他にもあります。
そのやり方は、module.exports
の後に.名前
で名前を付与することです。
ですので、 module.exports.名前 = 値
という記述になります。
module.exports.myCounter = function Counter() { /*…*/ }; // Counterという関数を定義し、それをmyCounterという名前で直接エクスポート
module.exports.myButton = function Button() { /*…*/ }; // Buttonという関数を定義し、それをmyButtonという名前で直接エクスポート
module.exports.myModule1 = () => { /*…*/ }; // 無名のアロー関数をmyModule1という名前で直接エクスポート
module.exports.myModule2 = () => { /*…*/ }; // 無名のアロー関数をmyModule2という名前で直接エクスポート
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports.myCounter = Counter; // 定数CounterをmyCounterという名前でエクスポート
module.exports.myButton = Button; // 定数ButtonをmyButtonという名前でエクスポート
ちなみに、下記2つは同じ役割になります。
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports.myCounter = Counter; // 定数CounterをmyCounterという名前でエクスポート
module.exports.myButton = Button; // 定数ButtonをmyButtonという名前でエクスポート
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = {
myCounter: Counter, // 定数CounterをmyCounterという名前でエクスポート
myButton: Button // 定数ButtonをmyButtonという名前でエクスポート
};
exports
module.exports.名前 = 値
の先頭のmojule.
を消して、exports.名前 = 値
という記述でもエクスポートすることができます。
module.exports
とexports
は本質的に役割が異なるとChatGPT先生は仰っていますが、よくわかりませんでした。
自分は、exports
はmodule.exports
の省略形、module.exports
= exports
みたいに覚えています。
唯一異なるのは、module.exports = 値
はOKだが、exports = 値
はNGということぐらいでしょうか。
一応最後に、module.exports
とexports
を比較した例を作ったので、よければご覧ください。
以下には、ChatGPT先生に質問した回答メモを残しておきます。
exports
を使用する意味は、module.exports
を簡略化することにあります。
つまり、exports
は、module.exports
オブジェクトへの参照を持つ特別なオブジェクトです。
これにより、exports
を使用して、module.exports
に直接プロパティを追加することができます。
しかし、exports
オブジェクトに直接プロパティを割り当てた場合、exports
オブジェクトがmodule.exports
オブジェクトと異なるオブジェクトになってしまい、予期しないエラーが発生する可能性があることに注意する必要があります。
そのため、一般的には、exports
を使用する代わりに、常にmodule.exports
を使用することをお勧めします。
ただし、複数の関数やオブジェクトをエクスポートする場合、exports
を使用してオブジェクトを構築することができます。
exports
オブジェクトは、module.exports
オブジェクトへの参照を持つ特別なオブジェクトであるため、exports
オブジェクトに直接プロパティを割り当てた場合、module.exports
オブジェクトとは別のオブジェクトになってしまいます。
この場合、exports
オブジェクトに割り当てたプロパティはmodule.exports
オブジェクトには影響を与えず、module.exports
オブジェクトは空のオブジェクトのままになります。
module.exports
は最初に空のオブジェクトとして作成されるため、exports
オブジェクトに直接プロパティを割り当てた場合、module.exports
オブジェクトには反映されません。
これは、exports
オブジェクトがmodule.exports
オブジェクトを参照しているものの、exports
オブジェクトに直接割り当てたプロパティはmodule.exports
オブジェクトには影響を与えないためです。
例えば、以下のようにmodule.exports
オブジェクトに直接プロパティを割り当てると、module.exports
オブジェクトにプロパティが追加されます。
module.exports.名前 = 値;
しかし、
exports
オブジェクトに直接プロパティを割り当てた場合は、module.exports
オブジェクトには反映されません。
exports.名前 = 値;
この場合、
exports
オブジェクトはmodule.exports
オブジェクトを参照していますが、module.exports
オブジェクト自体には何も割り当てられていないため、空のオブジェクトのままになります。
ですので、上記3つのパターンは下記のように置き換えることができます。
exports.myCounter = function Counter() { /*…*/ }; // Counterという関数を定義し、それをmyCounterという名前で直接エクスポート
exports.myButton = function Button() { /*…*/ }; // Buttonという関数を定義し、それをmyButtonという名前で直接エクスポート
exports.myModule1 = () => { /*…*/ }; // 無名のアロー関数をmyModule1という名前で直接エクスポート
exports.myModule2 = () => { /*…*/ }; // 無名のアロー関数をmyModule2という名前で直接エクスポート
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
exports.myCounter = Counter; // 定数CounterをmyCounterという名前でエクスポート
exports.myButton = Button; // 定数ButtonをmyButtonという名前でエクスポート
ではここからは、最初のmodule.exports
の記述例を全てexports
に置き換えてみました。(自分がわかりやすくするため)
~ 通常の関数の場合 ~
1.名前なし関数(関数式 [ 無名関数 / 関数リテラル ])
exports.Counter = function () { /*…*/ }; // 無名関数をCounterという名前で直接エクスポート
const myConst = function () { /*…*/ }; // 無名関数をmyConstという定数名で定義
exports.Counter = myConst; // 定数myConstをCounterという名前でエクスポート
2.名前付き関数(関数宣言 [ function文 ] )
exports.Counter = function myFunction() { /*…*/ }; // myFunctionという関数を定義し、それをCounterという名前で直接エクスポート
function myFunction() { /*…*/ }; // 関数をmyFunctionという関数名で定義
exports.Counter = myFunction; // 関数myFunctionをCounterという名前でエクスポート
function myFunction() { /*…*/ }; // 関数をmyFunctionという関数名で定義
const myConst = myFunction; // 関数myFunctionをmyConstという定数名で定義
exports.Counter = myConst; // 定数myConstをCounterという名前でエクスポート
~ アロー関数の場合 ~
名前なし関数(関数式 [ 無名関数 / 関数リテラル ])
exports.Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという名前で直接エクスポート
const myConst = () => { /*…*/ }; // 無名のアロー関数をmyConstという定数名で定義
exports.Counter = myConst; // 定数myConstをCounterという名前でエクスポート
まとめると、
復数の値を1つずつエクスポートする場合は
module.exports.Counter = function Counter() { /*…*/ }; // Counterという関数を定義し、それをCounterという名前で直接エクスポート
module.exports.Button = function Button() { /*…*/ }; // Buttonという関数を定義し、それをButtonという名前で直接エクスポート
最後にまとめてエクスポートする場合は、
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = { Counter, Button }; // 定数Counterと定数Buttonをエクスポート
というやり方が良いのではないでしょうか。
インポート
インポート(読込)する手段は下記の2パターンがあります。
const 名前 = require('./ファイル名');
const { 名前 } = require('./ファイル名');
1.const 名前 = require('./ファイル名');
の場合は、任意の名前を指名することができ、module.exports = 値
としてエクスポートされた場合に使用できます。
2.const { 名前 } = require('./ファイル名');
の場合は、名前をエクスポート時に指名した名前と一致させる必要があり、module.exports.名前 = 値
、または、exports.名前 = 値
としてエクスポートされた場合に使用できます。
下記に例をあげていきます。
1.エクスポートされた値をインポート
const Counter = require('./modules'); // './modules'からエクスポートされた全ての値をインポートして、Counterという定数名で定義
2.復数エクスポートされた中から一部のみをインポートする場合
const { Counter } = require('./modules'); // './modules'からCounterという名前でエクスポートされた値をインポートして、Counterという定数名で定義
エクスポート時と同様で、分割代入を利用しています。
そして、上記は省略した記述方法となり、実際は下記のようになっています。
const {
Counter : Counter, // Counterという名前のデータをCounterという名前でインポート
} = require('./modules'); // './modules'からエクスポートされたデータをインポート
復数エクスポートする時と同じ記述と思えばわかりやすいかもしれません。
3.エクスポートされた値を1つにインポートする場合
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = { Counter, Button }; // 定数Counterと定数Buttonをエクスポート
const Counter = require('./modules'); // './modules'からエクスポートされた全ての値をインポートして、Counterという定数名で定義
このとき、Counterには下記の2つの値が入っています。
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
4.エクスポートされた中から一部のみをインポートする場合
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = { Counter, Button }; // 定数Counterと定数Buttonをエクスポート
const { Counter } = require('./modules'); // './modules'からCounterという名前でエクスポートされた値をインポートして、Counterという定数名で定義
このとき、Counterには下記の値のみが入っています。
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
~ 複数の値をインポート ~
基本的にエクスポート時と同じ記述です。
const { Counter, Button } = require('./modules'); // './modules'からエクスポートされた、Counter・Buttonというそれぞれの名前のデータを、同じ名前でインポート
上記は省略した記述方法で、実際は下記のようになっています。
const {
Counter : Counter, // Counterという名前のデータをCounterという名前でインポート
Button : Button // Buttonという名前のデータをButtonという名前でインポート
} = require('./modules'); // './modules'からエクスポートされたデータをインポート
もし名前を変更したい場合は下記のようになります。
const {
Counter : myCountre, // Counterという名前のデータをmyCounterという名前でインポート
Button : myButton // Buttonという名前のデータをmyButtonという名前でインポート
} = require('./modules'); // './modules'からエクスポートされたデータをインポート
エクスポートとインポートを交えた例
1.シンプル(module.exports)
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
module.exports = Counter; // 定数Counterをエクスポート
const myCounter = require('./modules'); // './modules'からエクスポートされた全ての値をインポートして、myCounterという定数名で定義
2.復数エクスポート(module.exports)
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
module.exports = { Counter, Button }; // 定数Counterと定数Buttonをエクスポート
const myModules = require('./modules'); // './modules'からエクスポートされた全ての値をインポートして、myModulesという定数名で定義
3.シンプル(exports)
const myConst = () => { /*…*/ }; // 無名のアロー関数をmyConstという定数名で定義
exports.Counter = myConst; // 定数myConstをCounterという名前でエクスポート
const { Counter } = require('./modules'); // './modules'からCounterという名前でエクスポートされた値をインポートして、Counterという定数名で定義
4.復数エクスポート(exports)
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
exports.Modules = { Counter, Button }; // 定数Counterと定数ButtonをModulesという名前でエクスポート
const { Modules } = require('./modules'); // './modules'からModulesという名前でエクスポートされた値をインポートして、Modulesという定数名で定義
5.復数エクスポート、復数インポート(exports)
const Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという定数名で定義
const Button = () => { /*…*/ }; // 無名のアロー関数をButtonという定数名で定義
exports.myCounter = Counter; // 定数CounterをmyCounterという名前でエクスポート
exports.myButton = Button; // 定数ButtonをmyButtonという名前でエクスポート
const { myCounter, myButton } = require('./modules'); // './modules'からエクスポートされた、myCounter・myButtonというそれぞれの名前のデータを、同じ名前でインポート
NG例
const myConst = () => { /*…*/ }; // 無名のアロー関数をmyConstという定数名で定義
exports.Counter = myConst; // 定数myConstをCounterという名前でエクスポート
const myCounter = require('./modules'); // './modules'からエクスポートされた全ての値をインポートして、myCounterという定数名で定義
exports.Counter
という形式でエクスポートされた値をインポートする場合は、エクスポート時と同じ名前を指定して取り出す必要があります。
ですので正しく修正すると下記になります。
const { Counter } = require('./modules'); // './modules'からCounterという名前でエクスポートされた値をインポートして、Counterという定数名で定義
復数エクスポートの例で例えるとわかりやすいかもしれません。
NG例2
const myConst1 = () => { /*…*/ }; // 無名のアロー関数をmyConst1という定数名で定義
const myConst2 = () => { /*…*/ }; // 無名のアロー関数をmyConst1という定数名で定義
exports.Counter1 = myConst1; // 定数myConst1をCounter1という名前でエクスポート
exports.Counter2 = myConst2; // 定数myConst2をCounter2という名前でエクスポート
const myCounter = require('./modules'); // './modules'からエクスポートされた全ての値をインポートして、myCounterという定数名で定義
復数にすると、なぜ指定して取り出す必要があるのかわかりやすくなったと思います。これでは、myCounter
に何を当てればいいのか判断できませんね。
exports
では、./modules
内の値の全てではなく一部分にフォーカスを当てているというイメージです。
~ 応用 ~
module.exports
とexports
は同時に使用可能です。
module.exports.Counter = () => { /*…*/ }; // 無名のアロー関数をCounterという名前で直接エクスポート
exports.Button = () => { /*…*/ }; // 無名のアロー関数をButtonという名前で直接エクスポート
const { Counter, Button } = require('./modules'); // './modules'からエクスポートされた、Counter・Buttonというそれぞれの名前のデータを、同じ名前でインポート
わかりやすくするために、console.log
を使用した例を上げていきます。
例1
module.exports.Counter = () => {
return 'counter text';
};
exports.Button = () => {
return 'button text';
};
const myModule = require('./modules');
console.log(myModule.Counter()); // 出力結果:counter text
console.log(myModule.Button()); // 出力結果:button text
もしインポートの仕方を変えた場合はこのような出力結果となります。
const { Counter } = require('./modules');
console.log(Counter()); // 出力結果:counter text
console.log(Button()); // 出力結果:エラー
例2
module.exports = () => {
return 'counter text';
};
exports.Button = () => {
return 'button text';
};
const myModule = require('./modules');
console.log(myModule()); // 出力結果:counter text
console.log(myModule.Button()); // 出力結果:button text
間違えそうな例
module.exports.Counter = () => {
return 'counter text';
};
exports.Button = () => {
return 'button text';
};
const Counter = require('./modules');
console.log(Counter()); // 出力結果:エラー
console.log(Button()); // 出力結果:エラー
// 正しくは下記
console.log(Counter.Counter()); // 出力結果:counter text
console.log(Counter.Button()); // 出力結果:button text
~ module.exports と exports の違いを知る ~
こうやったまとめると、全く違いがないようにみえます…。
実機で試さずに、ChatGPT先生に出力に聞きながら書いたので、間違っていたらすみません。
例1
module.exports = () => {
return 'counter text';
};
// パターン1
const Counter = require('./modules');
console.log(Counter()); // 出力結果:counter text
// パターン2
const { Counter } = require('./modules');
console.log(Counter()); // 出力結果:エラー
例2
exports = () => {
return 'counter text';
};
// パターン1
const Counter = require('./modules');
console.log(Counter()); // 出力結果:エラー
// パターン2
const { Counter } = require('./modules');
console.log(Counter()); // 出力結果:エラー
例3
module.exports.Counter = () => {
return 'counter text';
};
// パターン1
const Counter = require('./modules');
console.log(Counter()); // 出力結果:{ Counter: [Function: Counter] }
// パターン2
const Counter = require('./modules');
console.log(Counter.Counter()); // 出力結果:counter text
// パターン3
const { Counter } = require('./modules');
console.log(Counter()); // 出力結果:counter text
例4
exports.Counter = () => {
return 'counter text';
};
// パターン1
const Counter = require('./modules');
console.log(Counter()); // 出力結果:{ Counter: [Function: Counter] }
// パターン2
const Counter = require('./modules');
console.log(Counter.Counter()); // 出力結果:counter text
// パターン3
const { Counter } = require('./modules');
console.log(Counter()); // 出力結果:counter text
参考リンク
Node.js でのモジュールとエクスポートの方法
Node.js の exports と module.exports
モジュール化(importとrequireの違い)