Windows環境における”Error: cannot find module 'モジュール名'”問題

検索すると、結構皆さんハマっている"Error: cannnot find module 'モジュール名'"問題。私の環境でも発生した。

このエラーの原因は様々あると思うが、ここに記載するのはWindows環境下でnpmでモジュールインストール時に'-g'オプションを指定した場合に発生する現象について記載する。
以前の記事で、私は'-g'は利用しない方針で行くことを決めたが、この方針にたどり着く前にこのエラーに悩まされていたので、皆さんの解を基に私の環境での解決案を提示しておく。

まず、対象のプログラム。
ここで使っている'ejs'というテンプレートエンジンのモジュールで発生。

```javascript:app.js
const http = require('http');
const fs = require('fs');
const ejs = require('ejs');

const index_page = fs.readFileSync('./index.ejs', 'utf8');

var server = http.createServer(getFromClient);

server.listen(3000);
console.log('Server start!');

function getFromClient(request, response) {
    var content = ejs.render(index_page);
    response.writeHead(200, {'Content-type': 'text/html'});
    response.write(content);
    response.end();
}
```

これを実行すると、でました。'Error: Cannot find module'エラー。

```
$ node app.js
internal/modules/cjs/loader.js:582
    throw err;
    ^

Error: Cannot find module 'ejs'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:580:15)
    at Function.Module._load (internal/modules/cjs/loader.js:506:25)
    at Module.require (internal/modules/cjs/loader.js:636:17)
    at require (internal/modules/cjs/helpers.js:20:18)
    at Object.<anonymous> (C:\Users\satom\source\repos\NodeLearning\node-app\app
.js:3:13)
    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
    at Function.Module._load (internal/modules/cjs/loader.js:529:3)
```

Windows環境でnode.jsを始めた方はもれなく皆さん最初にハマるポイントではないだろうか。私の場合も、上記の通り'ejs'というnode.jsのテンプレートエンジンで発生した。npmで'-g'オプションを使用したインストールにより以下のフォルダーに保存されるが、なんとデフォルトではここにパスが通っていないので、node本体がモジュールを認識してくれない。

※この書籍はプログラム経験無ぐらいの読者を想定して書かれているため、ここまで詳細には踏み込んだ説明はしていない。

* c:/Users/<ユーザー名>/AppData/Roaming/npm/node_modules/ejs

```shell
$ pwd
/c/Users/satom/AppData/Roaming/npm/node_modules
$ ls
azure-functions-core-tools/  ejs/       eslint/   express-generator/  gulp-cli/
bower/  
```

### nodeモジュールの検索パスの確認'global.module.paths'
まずはnodeのモジュールの検索パスを確認する。nodeコマンドを実行後、プロンプトで'global.module.paths'を確認する。
まだ何も設定していないので、ここで表示されるパスはnode.js標準のデフォルト検索パス。


```
$ pwd
/c/Users/satom/source/repos/NodeLearning/node-app

$ node
> global.module.paths
[ 'C:\\Users\\satom\\source\\repos\\NodeLearning\\node-app\\repl\\node_modules',
  'C:\\Users\\satom\\source\\repos\\NodeLearning\\node-app\\node_modules',
  'C:\\Users\\satom\\source\\repos\\NodeLearning\\node_modules',
  'C:\\Users\\satom\\source\\repos\\node_modules',
  'C:\\Users\\satom\\source\\node_modules',
  'C:\\Users\\satom\\node_modules',
  'C:\\Users\\node_modules',
  'C:\\node_modules',
  'C:\\Users\\satom\\.node_modules',
  'C:\\Users\\satom\\.node_libraries',
  'C:\\MinGW\\msys\\1.0\\home\\satom\\apps\\nodejs\\lib\\node' ]
>
```

設定されいているパスは以下の４種類

* 自分がいるディレクトリ'C:\\Users\\satom\\source\\repos\\NodeLearning\\node-app'から'C:'（ルートディレクトリ）までの各ディレクトリの'node_modules'
* ホームディレクトリ配下の'.node_modules'
* ホームディレクトリ配下の'.node_libraries'
* node.js本体がインストールされているディレクトリの'lib/node'

。。。ね、ないでしょ？

Windows環境のnpmディレクトリが考慮されていないという、なんだろ、バグ？それともこれが仕様？
デフォルトで登録されてもよさそうなもんだがなぁ。

### 環境変数NODE_PATHによりnpmのモジュールパスを'global.module.paths'に追加する

原因は不明だが、解決策はいろいろな方が既に見つけているので一部拝借し、かつ私の環境で私好みの設定方法により解決する。
一番参考になったのは以下の記事。

* [npm と Node.js 上で require を使ったモジュール読み込みの仕組みについてメモ](http://phiary.me/npm-node-js-require-module-memo/)

'global.module.paths'で表示されるモジュール検索パスはあくまでもデフォルトの検索パス。これに検索パスを追加するためには環境変数NODE_PATHを設定する。
で、せっかくGit bashを使っているので、.profileに定義し、Windows本体側の設定は変えないようにする。

単純だがちょっとだけWindows特化の工夫が必要。。。（これだからなーWindowsは。。）

```shell:.profile
# for Node.js environments.
export APP_DATA=c:\\Users\\satom\\AppData\\Roaming
export NPM_MODULE_PATH=$APP_DATA\\npm\\node_modules
export NODE_PATH=$NPM_MODULE_PATH
```

Windows環境下では'%AppData%=c:\Users\<ユーザー名>\AppData\Roaming'という環境変数が定義されているが、残念ながらGit bashにはこの環境変数は存在しない。よって、似たような環境変数'APP_DATA'で同じパスを定義し、npm用の後続パスを環境変数'NPM_MODULE_PATH'として定義、最後に環境変数'NODE_PATH'を定義した。

ここで気を付けるべき箇所はパスの区切り文字。
通常Unix/Linux環境ではスラッシュ'/'がパス区切り文字なのだが、私が使っているNode.js本体はあくまでもWindows用のインストーラーでインストールしたもの。Unix/Linux用のパスは認識しないので、２重のバックスラッシュ（1つ目のバックスラッシュは2つ目のバックスラッシュを'バックスラッシュ'として認識するためのエスケープシーケンス文字）で定義しなければならない。

これで.profileをsourceで再読み込みすればnpmのモジュールパスが認識されてejsモジュールが正しく認識されるようになる。

この状態の'global.module.paths'の表示結果は以下の通り。

```
$ node
> global.module.paths
[ 'C:\\Users\\satom\\repl\\node_modules',
  'C:\\Users\\satom\\node_modules',
  'C:\\Users\\node_modules',
  'C:\\node_modules',
  'c:\\Users\\satom\\AppData\\Roaming\\npm\\node_modules',
  'C:\\Users\\satom\\.node_modules',
  'C:\\Users\\satom\\.node_libraries',
  'C:\\MinGW\\msys\\1.0\\home\\satom\\apps\\nodejs\\lib\\node' ]
>
```

これでOK.