Node.jsのexportsとmodule.exportsのメモ
はじめに
nodejsのモジュール機構がよくわからない。ES6以降だとJavaScriptにモジュール機構の仕様(import)があるが、node.jsで使われるCommonJS(exportsとかrequireとか)の動きがよくわからん。
ということで、メモ。
参考になる記事は沢山あり。以下記事の動作を確認しながらメモをしていく。
一つ目
2つ目
まずは、1つ目から。
Node.jsのexportsとmodule.exports
- Node.jsではCommonJS(CJK)フォーマットが使われる
- モジュールとその依存ファイルの定義には以下が使われる
- require
- exports
- module.exports
- 参考Understanding module.exports and exports in Node.js
require
- モジュールやファイルをインポートする関数
- 引数
- モジュール(ファイル)のパス
- node_moduleディレクトリにあるモジュールの名前
- ビルトインモジュールの名前
- 拡張子は省略可能(ESMでは省略不可らしい)
- 引数
- エクスポートされたモジュールを戻り値として返す
- 変数に代入してその変数を使うことでモジュールを扱える
//// file1.js module.exports = "hoge"; //// main.js // ローカルファイルfile1.jsをインポート const hoge = require("./file1"); console.log(hoge) ; // hoge // node_modulesディレクトリにあるbarをインポート const bar = require("bar"); // node_modules/barディレクトリを作って、 // npm init -y でpacakge.jsonを作って、 // mainに指定されているindex.jsに「module.exports = "bar"」を記載 // これで、モジュールとしてbarが読めた // これが最小構成なのかも // ビルトインモジュールfsをインポート const fs = rquire("fs");
モジュールの検索パス
$ node -e "console.log(module.paths)" [ '/home/xxxx/git/cjs_esm/node_modules', '/home/xxxx/git/node_modules', '/home/xxxx/node_modules', '/home/node_modules', '/node_modules' ]
ビルトインモジュール
node -e "console.log(require('module').builtinModules )" [ '_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', 'assert/strict', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console', 'constants', 'crypto', 'dgram', 'diagnostics_channel', 'dns', 'dns/promises', 'domain', 'events', 'fs', 'fs/promises', 'http', 'http2', 'https', 'inspector', 'module', 'net', 'os', 'path', 'path/posix', 'path/win32', 'perf_hooks', 'process', 'punycode', 'querystring', 'readline', 'repl', 'stream', 'stream/consumers', 'stream/promises', 'stream/web', 'string_decoder', 'sys', 'timers', 'timers/promises', 'tls', 'trace_events', 'tty', 'url', 'util', 'util/types', 'v8', 'vm', 'worker_threads', 'zlib' ]
module
というグローバル変数もあるけど、require('module')
とは別なのか。
exports
- モジュールを他のプログラムで使用できるようにすには以下を使う
- exports
- module.exports
//user.js exports.getName = () => { return "hoge" } // main.js const user = require('./user.js'); console.log(user.getName());
分割代入
const { getName, dob } = require('./user');
module.exports
- 1つだけをエクスポートするモジュールがある場合などはmodule.exportsを使用するのが一般的
class User { xxxx } module.exports = User;
exportsとmodule.exports
- exportsはmodule.exportsのショートカットのようなもの
- 初期状態ではexportsはmodule.exportsへの参照
exports.a = "A"; exports.b = "B"; console.log(exports); console.log(module.exports); console.log(exports === module.exports); // true
exportsとmodule.exportsが異なる場合
- exports( or module.exports) の参照を上書きしてしまうと異なるものを指す
- 参照上書きしてしまうと、module.exportsの方が有効になるぽいな
exports = { hoge: "hoge" }; console.log(exports === module.exports); // false
次は2つ目の記事を読みます
Node.js : exports と module.exports の違い(解説編)
moduleとexports
- module
- 現在のモジュールへの参照
- module.exportsはexportsオブジェクトと同じ
- moduleは実際はグルーバルではなくて各モジュール毎のローカル
- exports
- module.exportsが本体。exportsはlittle helperらしい。
- だから、module.exports = { xxx } みたいに参照を更新したら、exportsは使えないのか。
使い分け
- モジュールを特定のオブジェクト型にしたいならmodule.exportsを使う
module.exports = User
みたいな。- require()の戻り値を、コンストラクター関数や配列・文字列などにしたい場合
- 通常のmoduleインスタンスとして使うなら、exportsを使う
const obj = require("./myobj"); console.log(obj.hoge)
みたいな。
src/node.jsを読む
- `NativeModule.wrapという処理で、モジュール内のソースを無名関数でラップしている
- exports, require, module,filename, dirnameはその無名関数の引数
- いまはESM対応もされているしこのあたりのロジックは結構変わってるのかな
(function(exports, require, module, __filename, __dirname) { モジュールファイル内の JavaScript コード });
最新っぽいソース
- bootstrap/loader.jsとmodues/cjs(or esm)/loader.jsになってる
node/loaders.js at master · nodejs/node · GitHub
node/loader.js at master · nodejs/node · GitHub
記事の擬似コードがとても参考になる。
- 本体のコード(requireが記載されているコード)より先に、moduleが読み込まれる。
- moduleオブジェクトはrequireされる毎にインスタンス化される
- requireからの戻り値はmodule.exports変数。
基本的には、module.exportsが本体なのでこっちが優勢。exportsは参照が上書きされることがある。
ただし、module = { exports : { xxx} }
みたいにすると、moduleはrequire毎にインスタンス化されるので、exportsの方が優勢になる。
よくわかりました。
次は、ESMの方もキャッチアップしたい。