2.2.1 公開モジュールパターン
JavaScriptは「ネームスペース」が存在しないので、グローバルな変更が容易です。これを回避するために使うパターンが「Living Module Pattern」です。
const module = (() => {
const privateFoo = () => {...}
const privateBar = () => []
const exported = {
publicFoo: () => {...},
publicBar: () => {...}
}
})()
console.log(module)
JavaScriptがプライベートなスコープを形成するという性質を利用して、必要なものだけ公開します。
2.2.2 Node JSモジュールシステムの詳細
2.2.2.1 自作のモジュールローダ
function loadModule(filename, module, require) {
const wrappedSrc = `(function(module, exports, require) {
${fs.readFileSync(filename, 'utf8')}
})(module, module.exports, require)`
eval(wrappedSrc)
}
const requireMine = (moduleName) => {
console.log(`RequireMine invoked for module: ${moduleName}`)
const id = requireMine.resolve(moduleName)
if (requireMine.cache[id]) {
return requireMine.cache[id].exports
}
const module = {
exports: {},
id: id
}
requireMine.cache[id] = module
loadModule(id, module, requireMine)
return module.exports
}
requireMine.cache = {}
requireMine.resolve = (moduleName) => {
// モジュール名を完全な識別子に変換する。
}
※evalとは、引数に指定した文字列をJavaScriptプログラムコードとして評価・実行する機能をもつ関数です。
security holeを作ってしまうので、気をつけてください。
2.2.2.2 モジュールの定義
const dependency = require('./anotherModule')
function log() {
console.log('Well done ${dependency.username}')
}
module.exports.run = () => {
log()
}
module.exportに代入されない限りモジュールないの全ての変数が非公開になります。
2.2.2.3 グローバル変数の定義
globalオブジェクトに定義されたプロパティは、自動的にグローバル変数/関数として参照可能となります。しかし、モジュールシステムのカプセル化の利点を損なうので、一般的には使わないです。
2.2.2.4 module.exportsとexportsの使い分け
単純に新しいプロパティ追加
exports.hello = () => {
console.log('Hello')
}
誤ったコード
exports = () => {
console.log('Hello')
}
exportsに何か入れたい時には
module.exports = () => {
console.log('Hello')
}
2.2.2.5 requireは同期関数
require()関数は同期関数なので、module.exportsオブジェクトへの操作も同期的に行われる必要があります。
→requireで呼ばれるモジュールの定義は同期処理として実装するしかありません。
2.2.2.6 依存解決
myApp/
foo.js
node_modules
depA
index.js
depB
bar.js
node_modules
depA
index.js
depC
foobar.js
node_modules
depA
index.js
・/myApp/foo.jsからrequire('depA')を呼び出した場合
→/myApp/node_modules/depA/index.jsをロードする
・/myApp/node_modules/depB/bar.jsからrequire('depA')を呼び出した場合
→/myApp/node_modules/depB/node_modules/depA/index.jsをロードする
・/myApp/node_modules/depC/foobar.jsからrequire('depA')を呼び出した場合
→/myApp/node_modules/depC/node_modules/depA/index.jsをロードする
このような呼び方で依存地獄を解決します。
2.2.2.7 モジュールのキャッシュ
パフォーマンス上不可欠ですが、次の天に注意してください。
・キャッシュによりモジュールの循環参照が可能になる。
・あるパッケージ内で同じモジュールが複数回requireされた場合、それらは同じインスタンスを参照する。
・require.cacheのキーをdeleteして、キャッシュを削除することも可能だが、しないほうがいい。
2.2.2.8 モジュールの循環参照
exports.loaded = false;
const b = require('./b')
module.exports = {
bWasLoaded: b.loaded,
loaded: true
}
exports.loaded = false;
const a = require('./a')
module.exports = {
aWasLoaded: a.loaded,
loaded: true
}
const a = require('./a')
const b = require('./b')
console.log(a)
console.log(b)
結果
{ bWasLoaded: true, loaded: true }
{ aWasLoaded: true, loaded: true }
→キャッシュの影響
2.2.3 モジュール定義におけるパターン
参考文献
Node.jsデザインパターン 第2版 - Mario Casciaro (著), Luciano Mammino (著), 武舎 広幸 (翻訳), 阿部 和也 (翻訳)