0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

2.2 Node JSのモジュールシステムとパターン

Posted at

2.2.1 公開モジュールパターン

JavaScriptは「ネームスペース」が存在しないので、グローバルな変更が容易です。これを回避するために使うパターンが「Living Module Pattern」です。

test.js
const module = (() => {
  const privateFoo = () => {...}
  const privateBar = () => []

  const exported = {
    publicFoo: () => {...},
    publicBar: () => {...}
  }
})()
console.log(module)

JavaScriptがプライベートなスコープを形成するという性質を利用して、必要なものだけ公開します。

2.2.2 Node JSモジュールシステムの詳細

2.2.2.1 自作のモジュールローダ

test.js
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 モジュールの定義

test.js
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の使い分け

単純に新しいプロパティ追加

test.js
exports.hello = () => {
  console.log('Hello')
}

誤ったコード

test.js
exports = () => {
  console.log('Hello')
}

exportsに何か入れたい時には

test.js
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 モジュールの循環参照

a.js
exports.loaded = false;
const b = require('./b')
module.exports = {
  bWasLoaded: b.loaded,
  loaded: true
}
b.js
exports.loaded = false;
const a = require('./a')
module.exports = {
  aWasLoaded: a.loaded,
  loaded: true
}
main.js
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 (著), 武舎 広幸 (翻訳), 阿部 和也 (翻訳)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?