ブラウザでJavaScriptのモジュールを使うときモジュールを管理し、依存関係を解決する方法

# 背景
ブラウザの標準機能(type=module)でモジュールを扱いたいと思いました。近い将来古いブラウザに縛られる必要性が薄れ、ネイティブなJavaScriptで十分なエコシステムを築けるのではないでしょうか。ブラウザの基本機能でいけるのが最も簡単なので、そうなってほしいと思っています。

ブラウザの標準機能でモジュールを使うとしても、モジュールを管理する方法は必要です。モジュール管理といえばやはりnpmかなぁと思います。node.js用のモジュール管理ツールですが、これからのブラウザのモジュール管理にも使えるのではないかと思いました。
[Verdaccio](https://github.com/verdaccio/verdaccio)を使えば、自前のnpmサーバもできるので、モジュール管理も簡単です。

しかし、モジュール開発後、npmでモジュールを管理し、ブラウザのimportでモジュールを呼び出すと問題が起こりました。
それはブラウザのimportが絶対もしくは相対のURLしか使えないために、2重の依存関係があるときにモジュールをうまく呼び出せなくなるということです。対処法に悩みましたので、ここに書き残しておきます。

# 要約
開発時に./node_modules/moduleA/index.jsとかで参照していたモジュールは、モジュール配布時には並列のディレクトリ構造化におかれるので参照できなくなる。
/scripts/node_modules/moduleB/node_modules/moduleA/index.js
が見つからない等といわれてしまう。

考えた解決方法は次の２つです。
(方法1)
シンボリックリンクでnode_modulesの中のモジュールにリンクを張るとともに、importのfromの先をシンボリックリンクの場所にする

```javascript:moduleA/index.js
export const funcA = (a)=> 2*a
```

```javascript:moduleB/index.js
import {funcA} from "../moduleA/index.js"
const a = funcA(1)
console.log(a) // -> 2

const funcB = (b)=>3*b
export {funcB}

```

```javascript:moduleC/index.js
import {funcB} from "../moduleB/index.js"
const b = funcB(1)
console.log(b) // -> 3
```

(方法2)
絶対参照で参照しやすいURLにすべてのモジュールをおく。

```javascript:moduleA/index.js
export const funcA = (a)=> 2*a
```

```javascript:moduleB/index.js
import {funcA} from "/modules/node_modules/moduleA/index.js"
const a = funcA(1)
console.log(a) // -> 2

const funcB = (b)=>3*b
export {funcB}
```


```javascript:moduleC/index.js
import {funcB} from "/modules/node_modules/moduleB/index.js"
const b = funcB(1)
console.log(b) // -> 3
```

#内容
## 依存が1階層の場合は問題ない
moduleBがmoduleAに依存しているケース
 moduleB → moduleA

moduleAディレクトリを用意して、その中で

```shell
npm init
```
を実行し、モジュール管理をする準備をした後、index.jsを作成し、保存します。
verdaccioでローカルのリポジトリをしているとして、次のコマンドでリポジトリにpushします。

```
npm publish
```

続いて、moduleBディレクトリを作り、moduleAと同様の操作を行ったのち、

```
npm install moduleA
```
でmoduleAをローカルにインストールします。
これで下記のようなディレクトリ構成となります。


```
scripts
|- moduleA
|   |- index.js
|   |- package.json
```

```
scripts
|- moduleB
|   |-index.js
|   |-package.json
|   |-package-lock.json
|   |-node_modules
|      |- moduleA
|         |- index.js
|         |- package.json
```

```javascript:moduleA/index.js
export const funcA = (a)=> 2*a
```

```javascript:moduleB/index.js
import {funcA} from "./node_modules/moduleA/index.js"
const a = funcA(1)
console.log(a) // -> 2

const funcB = (b)=>b*3
export {funcB}
```

moduleBの中からmoduleAを呼ぶには、"./node_modules/index.js"を参照すればいけます。


## 依存が深い場合に問題
moduleCがmoduleBに依存し、moduleBがmoduleAに依存しているケース
 moduleC → moduleB → moduleA

上記の依存が浅い場合のmoduleBをnpm publishでレポジトリに登録して、moduleCのディレクトリ下でnpm install moduleBを実行すると下記のようなディレクトリ構成になります。moduleBの依存関係をnpmが解釈して、moduleAもインストールしてくれますが、ディレクトリ構成が並列になるので、上記のようにmoduleBでmoduleAをimportしていると、パスが存在せずエラーになります。



```
scripts
|- moduleC
|   |-index.js
|   |-package.json
|   |-package-lock.json
|   |-node_modules
|      |- moduleA
|      |  |- index.js
|      |  |- package.json
|      |- moduleB
|      |  |- index.js
|      |  |- package.json

`````


```javascript:moduleC/index.js
import {funcA} from "./node_modules/moduleB/index.js"
const c = funcA(1)
console.log(c) // -> 2
```

/scripts/node_modules/moduleB/node_modules/moduleA/index.js
が見つからないとブラウザでエラーが出る。入れ子の構造でimporしようとするけれど、npmのインストールは依存関係にあるモジュールをすべてnode_modulesの中に並列に置くので、開発時に想定してたURL構造がモジュール配布時に壊れてしまいます。

## 方法
### 方法1: シンボリックリンクを張る
モジュールの開発時にシンボリックリンクを使い、その場所を参照するようにすることで、モジュール配布時と同じパスにする方法です。

moduleAに依存したmoduleBを開発するとき、下記のようにmoduleBと並列にmoduleAへのシンボリックリンクを用意することで、moduleBの中でのmoduleAの呼び出しをモジュール配布時と同じにできます。node_modulesと書かなくてよいので、見た目もいい感じになります。

```
scripts
|- moduleAへのシンボリックリンク
|- moduleB
|   |-index.js
|   |-package.json
|   |-package-lock.json
|   |-node_modules
|      |- moduleA
|         |- index.js
|         |- package.json
```

```javascript:moduleB/index.js
import {funcA} from "../moduleA/index.js"
const b = funcA(1)
console.log(b) // -> 2
export {funcA}
```

このmoduleBにさらに依存するmoduleCを開発する時も同じです。

```
scripts
|- moduleAへのリンク
|- moduleBへのリンク
|- moduleC
|   |-index.js
|   |-package.json
|   |-package-lock.json
|   |-node_modules
|      |- moduleA
|      |  |- index.js
|      |  |- package.json
|      |- moduleB
|      |  |- index.js
|      |  |- package.json

```

```javascript:moduleC/index.js
import {funcB} from "../moduleB/index.js"
const b = funcB(1)
console.log(b) // -> 3
```

ここで、注意しないといけないのが、moduleCから直接moduleAを呼んでいなくても、moduleAへのシンボリックリンクを張っておかないといけないということです。moduleBからmoduleAを呼ぶに行くとき、moduleBのindex.jsはmoduleBのシンボリックリンクの下の階層にいることになっているからです(ブラウザからするとnode_modulesの下にいることにはなっていません)。

### 方法2: 絶対パスで参照できるわかりやすい場所にインストールする

モジュールを開発した後は、開発者がアクセスできる分かりやすい場所にモジュールをまとめておけば、絶対パスで参照しに行くだけなので、簡単です。しかし、この方法を使うと他の環境に持っていくと全然動かない可能性があります。また、node.jsで使えなくなってしまって、ユニバーサル性を保てなくなるように思います。また、依存関係を書かなくても使えてしまうため、依存関係を書き忘れないか心配です。

```
modules
|- package.json
|- node_modules
|    |- moduleA
|    |  |- index.js
|    |  |- package.json
|    |- moduleB
|    |  |- index.js
|    |  |- package.json
```
```javascript:moduleB/index.js
import {funcA} from "/modules/node_modules/moduleA/index.js"
const a = funcA(1)
console.log(a) // -> 2

const funcB = (b)=>3*b
export {funcB}
```

moduelCを開発するときはmoduleBを絶対参照で呼べば大丈夫です。
moduleCを開発し終わった後はmoduleBと同じ階層に入れておけば、他からmoduleCを呼ぶこともできます。

```javascript:moduleC/index.js
import {funcB} from "/modules/node_modules/moduleB/index.js"
const b = funcB(1)
console.log(b) // -> 3
```

# 感想
ブラウザ標準機能のimport, export周りをどうやってかくべきなのか、日々悩んでいます。今回、モジュールの入れ子依存問題に数時間頭を悩ませました。最初は方法2で行こうと思ったのですが、絶対パス書くのは汎用性に欠けるのではないかと思ったのと、node.jsで動かなさそうと思ったのでしばらく方法1で行ってみようかと思います。