Node.js の exports と module.exports

Node.js を使ったモジュールのエクスポートとインポート、require、exports、module.exports についての覚書です。以下は Node.js がインストールされていることを前提にしています。

作成日:2020年5月26日

Node.js では CommonJS (CJS) フォーマットが使われ、モジュールとその依存ファイルの定義には require と exports や module.exports を使います。

参考サイト:Understanding module.exports and exports in Node.js

また、 CommonJS モジュールとは、Node.js 環境での JavaScript のモジュール化の仕組みです。

参考サイト:JavaScript Primer/CommonJSモジュール

require

require はモジュールやファイルをインポートする関数です。

プログラムの最初に require() を記述することで、指定したモジュールやファイルを Node.js で扱えるようになります。

引数にはモジュール(ファイル)のパスや node_module ディレクトリにあるモジュールの名前、またはビルトインモジュールの名前を指定します。拡張子は省略可能です。

require はエクスポートされたモジュールを戻り値として返すので、変数に代入してその変数を使うことでモジュールを扱えるようになります。

//同じ階層のローカルファイル file1.js をインポート(拡張子は省略可能)
const foo = require('./file1');  

//node_module ディレクトリにあるモジュール bar をインポート  
const bar = require('bar');  

//ビルトインモジュール fs をインポート
const fs = require('fs');  

require('bar') のようにモジュール名のみを指定した場合は、最初にビルトインモジュールが検索され、その後 module.paths に記載されているディレクトリが検索されます。

module.paths は以下で確認できます。実際に表示されるパスは環境はコマンドを実行した位置により異なります。-e(--eval)は引数を JavaScript として評価するオプションです(Command Line Options)。

$ node -e "console.log(module.paths);"  return
[
  '/Applications/MAMP/htdocs/sample/pr/js/ex/node_modules',
  '/Applications/MAMP/htdocs/sample/pr/js/node_modules',
  '/Applications/MAMP/htdocs/sample/pr/node_modules',
  '/Applications/MAMP/htdocs/sample/node_modules',
  '/Applications/MAMP/htdocs/node_modules',
  '/Applications/MAMP/node_modules',
  '/Applications/node_modules',
  '/node_modules'
]

Node.js にはインストールせずに使用できるビルトインモジュールが用意されています。これらのモジュールを使用する場合にも require を使用してインポートします。

どのようなビルトインモジュールがあるかは以下で確認できます。

$ node -e "console.log(require('module').builtinModules);"   return
[
  '_http_agent',       '_http_client',        '_http_common',
  '_http_incoming',    '_http_outgoing',      '_http_server',
  '_stream_duplex',    '_stream_passthrough', '_stream_readable',
  '_stream_transform', '_stream_wrap',        '_stream_writable',
  '_tls_common',       '_tls_wrap',           'assert',
  'async_hooks',       'buffer',              'child_process',
  'cluster',           'console',             'constants',
  'crypto',            'dgram',               'dns',
  'domain',            'events',              'fs',
  'fs/promises',       'http',                'http2',
  'https',             'inspector',           'module',
  'net',               'os',                  'path',
  'perf_hooks',        'process',             'punycode',
  'querystring',       'readline',            'repl',
  'stream',            'string_decoder',      'sys',
  'timers',            'tls',                 'trace_events',
  'tty',               'url',                 'util',
  'v8',                'vm',                  'worker_threads',
  'zlib'
]

例えば、ディレクトリのファイルのリストを表示するには組み込みのファイルシステムのモジュール(file system module) fs の readdir メソッドを使うことができます。

以下は require を使用してファイルシステムのモジュール(fs)をインポートし、そのメソッドを使ってデスクトップのファイルを表示する例です。

モジュールをインポートして変数 fs に格納して、そのモジュールのメソッド readdir を使ってパスで指定したディレクトリにあるファイルを表示します。

main.js(エントリポイントのファイル)
//ファイルシステムのモジュール fs をインポートして変数 fs に格納
const fs = require('fs');  

//ユーザのデスクトップディレクトリのパス
const folderPath = '/Users/foo/Desktop';  

//fs のメソッド readdir にディレクトリのパスを指定して実行
fs.readdir(folderPath, (err, files) => {
  files.forEach(file => {
    console.log(file); //Node.js の場合、console.log の出力先は標準出力
  });
});

ターミナルで node コマンドの引数にエントリーポイントのファイルを指定して実行します。Node.js の console.log メソッドの出力先は標準出力になります。

ターミナルで実行すると以下のようになります。
$ node main.js   return
          
//デスクトップのファイルが表示される
$RECYCLE.BIN  
.DS_Store
.localized
sample_01.jpg
・・・

exports

モジュールを他のプログラムで使用できるようにするには exports や module.exports を使います。

以下は独自のモジュールを作成して他のプログラムで使用できるように exports を使ってエクスポートする例です。getName という関数をエクスポートするファイル user.js を作成します。

以下の例では exports のプロパティ getName に定義した関数 getName を代入しています。

user.js
// getName という関数を定義
const getName = () => { 
  return 'Foo';
};

// exports を使って他のファイルから getName をインポートできるようにする
exports.getName = getName;  

以下のように関数などの定義と同時に exports のプロパティとして設定することもできます。以下は上記と同じことです。

user.js
//exports.getName に関数を定義
exports.getName = () => {  
  return 'Foo';
};

以下はアロー関数を使わない場合です。

const getName = function() {
  return 'Foo';
};
exports.getName = getName;

//または以下でも同じ
exports.getName = function() {
  return 'Foo';
};

上記のファイル user.js と同じ階層に以下のエントリポイントのファイル index.js を作成し、require を使って user.js の getName をインポートします。require の引数には ./ を指定しているので同じ階層のローカルファイルのパスを表し、また拡張子は省略しています。

console コマンドで getName メソッドの戻り値をテンプレートリテラルを使って出力しています。

index.js
// user.js をインポート
const user = require('./user');  

//インポートした user の getName メソッドを使用
console.log(`ユーザ名: ${user.getName()}`); 

ターミナルで index.js を引数にして node コマンドを実行すると「ユーザ名: Foo」とターミナルに表示されます。

$ node index.js   return  //以下がターミナルに表示される
ユーザ名: Foo 

同様の方法で複数の関数や値をエクスポートすることもできます。以下は2つのメソッド(関数)と値をエクスポートする例です。

user.js
// getName という関数を定義
const getName = () => {  
  return 'Foo';
};

// getLocation という関数を定義
const getLocation = () => { 
  return 'New York';
};

// dateOfBirth という変数を定義
const dateOfBirth = '1997年5月25日'; 

exports.getName = getName; // getName を exports.getName に設定
exports.getLocation = getLocation; // getLocation を exports.getLocation に設定
exports.dob = dateOfBirth; // dateOfBirth を exports.dob に設定

以下のように記述しても同じことです。

user.js
//exports.getName に関数を定義
exports.getName = () => {  
  return 'Foo';
};

//exports.getLocation に関数を定義
exports.getLocation = () => {  
  return 'New York';
};

//exports.dob に値を定義
exports.dob = '1997年5月25日';  
index.js
// user.js をインポートして変数 user に格納
const user = require('./user');  

// user のメソッドやプロパティを使用してテンプレートリテラルで出力
console.log(
`ユーザ名: ${user.getName()} 
居住地: ${user.getLocation()} 
誕生日: ${user.dob}`
);

ターミナルで index.js を引数にして node コマンドを実行すると以下のようになります。

$ node index.js   return  

ユーザ名: Foo 
居住地: New York 
誕生日: 1997年5月25日

分割代入を使ったインポート

分割代入を使えば、簡単に必要なものだけをピックアップしてインポートすることができます。

index.js
const { getName, dob } = require('./user');  //分割代入
          
console.log(
  `${getName()}の誕生日は${dob}です。`
);

ターミナルで index.js を引数にして node コマンドを実行すると以下のようになります。

$ node index.js   return 

Fooの誕生日は1997年5月25日です。

module.exports

前述の例では関数と値を個別にエクスポートしましたが、1つだけをエクスポートするモジュールがある場合などは module.exports を使用するのがより一般的です。

CommonJS モジュールは Node.js のグローバル変数 module を使って変数や関数などをエクスポートします。 CommonJS モジュールでは module.exports プロパティに代入されたオブジェクトが、その JavaScript ファイルからエクスポートされます(JavaScript Primer より引用)。

以下は1つのクラスだけをエクスポートするモジュールの例です。

user.js
class User {
  constructor(name, age, email) {
    this.name = name;
    this.age = age;
    this.email = email;
  }

  getUserInfo() {
    return `
  名前: ${this.name}
  年齢: ${this.age}
  メール: ${this.email}
`;
  }
}

//クラス User だけを module.exports を使ってエクスポート
module.exports = User;

インポートする側のファイル index.js は以下のようになります。

index.js
// user.js をインポートして変数 User に代入
const User = require('./user');

//User を使ってオブジェクト foo を生成
const foo = new User('Foo', 23, 'foo@example.com');

//User オブジェクトのメソッドを使用
console.log(foo.getUserInfo());

ターミナルで index.js を引数にして node コマンドを実行すると以下のようになります。

$ node index.js   return

  名前: Foo
  年齢: 23
  メール: foo@example.com

以下のように module の exports プロパティ(module.exports)に関数や値を設定することもできます。

calc.js
module.exports = {
  add: (x, y) => {
    return x + y;
  },

  subtract: (x, y) => {
    return x - y;
  }
};

または、以下のように記述しても同じことになります。

calc.js
module.exports.add = (x, y) => {
  return x + y; 
};
module.exports.subtract = (x, y) => {
  return x - y;
};

以下はモジュールをインポートする側のファイル index.js の例です。

index.js
const calc = require('./calc');

const x = 200, y = 100;

console.log( `${x} + ${y} = ${calc.add(x, y)}`); 
console.log( `${x} - ${y} = ${calc.subtract(x, y)}`); 

ターミナルで index.js を引数にして node コマンドを実行すると以下のようになります。

node index.js   return
200 + 100 = 300
200 - 100 = 100

exports と module.exports

exports は module.exports のショートカットのようなもので、初期状態では exportsmodule.exports への参照です。

foo.js というファイルを作成して以下を記述します。

foo.js
console.log(module);

ターミナルで node foo.js を実行すると以下のような出力になります。以下の結果から module は実行したファイル(foo.js)への参照のように見えます。

また、exports というプロパティ(modules.exports)があり、現在は空のオブジェクトになっています。

$ node foo.js   return
Module {
  id: '.',
  path: '/Applications/MAMP/htdocs/sample/pr/js/ex',
  exports: {},  //exports プロパティ(modules.exports)
  parent: null,
  filename: '/Applications/MAMP/htdocs/sample/pr/js/ex/foo.js',
  loaded: false,
  children: [],
  paths: [  // modules.paths (モジュールの検索パス) 
    '/Applications/MAMP/htdocs/sample/pr/js/ex/node_modules',
    '/Applications/MAMP/htdocs/sample/pr/js/node_modules',
    '/Applications/MAMP/htdocs/sample/pr/node_modules',
    '/Applications/MAMP/htdocs/sample/node_modules',
    '/Applications/MAMP/htdocs/node_modules',
    '/Applications/MAMP/node_modules',
    '/Applications/node_modules',
    '/node_modules'
  ]
}

foo.js を編集して exports オブジェクトにプロパティを指定してみます。

foo.js
exports.a = 'A';  //exports にプロパティを指定
exports.b = 'B'; //exports にプロパティを指定
console.log(module);

ターミナルで node foo.js を実行すると、以下のように exports オブジェクトに指定したプロパティが modules.exports に追加されています。

$ node foo.js   return
Module {
  id: '.',
  path: '/Applications/MAMP/htdocs/sample/pr/js/ex',
  exports: { a: 'A', b: 'B' }, //exports プロパティ(modules.exports)
  parent: null,
  filename: '/Applications/MAMP/htdocs/sample/pr/js/ex/foo.js',
  ・・・以下省略・・・
}

これは exports が modules.exports への参照であるためです。以下のようにしても確認できます。

foo.js
exports.a = 'A';
exports.b = 'B';

console.log(exports);  //{ a: 'A', b: 'B' }
console.log(module.exports);  //{ a: 'A', b: 'B' }
console.log(exports === module.exports);  // true

exports と modules.exports が異なる場合

常に exports が modules.exports の参照とはならない場合がある点に注意が必要です。

以下の場合は exports は modules.exports の参照です(同じオブジェクト)。

foo.js
module.exports.a = 'A';
exports.b = 'B';

console.log(exports === module.exports);  //true(exportsとmodule.exportsは同じ)
console.log(exports);  //{ a: 'A', b: 'B' }
console.log(module.exports);  //{ a: 'A', b: 'B' }

但し、以下のように modules.exports にオブジェクトを指定すると、exports は modules.exports の参照ではなくなります。

foo.js
module.exports = {a: 'A'};  //オブジェクトを指定
exports.b = 'B';

console.log(exports === module.exports);  //false(exportsとmodule.exportsは異なる)
console.log(exports);  //{ b: 'B' }
console.log(module.exports);  //{ a: 'A' }

実際にインポートしてみると以下のようになります。

foo.js
module.exports = {a: 'A'};
exports.b = 'B'; 

foo.js をインポートするファイル index.js

const foo = require('./foo');  
 
console.log(foo.a); 
console.log(foo.b); 

ターミナルで index.js を引数にして node コマンドを実行

$ node index.js
A
undefined  //foo.b はエクスポートされない

詳細は Node.js : exports と module.exports の違い(解説編) が参考になります。