フロントエンド初心者がJavaScriptの依存関係のあるモジュール分割を動的に解決するよう格闘してみた

# はじめに

本記事は、FlashからHTML+CSS+JavaScriptに移行することになったため、
普段はC++使っているフロントエンド初心者が格闘した記録(Part.2)です。
※注意： ちゃんとわかっていない可能性があります。

## モジュール分割 って？

JavaScript(JS)においてファイルを分割することを、モジュール分割と呼ぶようです。
本気時でもファイル分割のことをモジュール分割と記載します。

# 格闘編

C++的発想で、モジュール分割しといたほうが、障害解析とか楽だろうと思ったため、
結合するツールを調べると、grunt-contrib-concat (gruntのプラグイン)がすぐに見つかった。

1クラス1ファイルにしようと、とりあえず分割しまくりました。

## 事件発生

いざ、JSの動作を確認してみようとおもい、結合してブラウザで開くと…JSが動作しません！
Chromeの解析ツールを呼び出し(F12を押し）て、たどってみると、JSがエラーを出しています。


そう、作ったJSには依存関係があったのです。

```js
 src: ["assets/**/*.js"]
```
なんて書こうものなら、見つかったJSから順に結合するので、
ファイル名の順が、依存解決できる順番になっていないとエラーになります。

ファイル名がたまたま依存関係順になるなんてことはそうそう起きない、
ファイル名の先頭に 000_ 100_ とかつければ解決することもできますが、
依存関係を動的に解決して結合する方法を調べてみることにしました。

## 動的解決の方法

結論から言えば、grunt-contrib-concatに渡すファイル順を、新たなプラグインである
grunt-file-dependenciesに制御してもらうことにしました。

### パティーン1 (2018/9/6 時点の方法)

依存関係を定義などから解決する解析方法を追加してみました。

```js:Grantfile.js(一部)

  file_dependencies: {
    test: {
        options: {
          ...
          extractDefinesRegex: /(?![""''])(PRJ(?:[\.][a-zA-Z]+)*)(?:\s*=)/g,
          extractRequiresRegex: /(?![""''])(PRJ(?:[\.][a-zA-Z]+)*)(?:\s*^=)/g
        }
        src : ...
   }
}
```
※ "や’を単体で残しておくとカラースキームがうまく反映されないため、Qiita用に意図的に"と'は重ねています。


しかし、この方法で行うと、自己参照が発生してしまい、循環参照が発生したため、
現状のプラグインのままでは解決できなくなってしまったのでプラグインを改造してみました。
https://qiita.com/taka-vagyok/items/f7193313b17afc066818

### パティーン2 (2018/9/4 時点の方法)

依存関係を解決するようなコメントを追加してみました。

* コードに依存関係を記述
    * // define("hogehoge") : 自分のクラス
    * // require("hogehoge") : 必要なクラス

サンプルは以下のように作っています。
そうするとあら不思議、file1より前にfile2を結合してくれるようになりました！

```js:Grantfile.js(一部)

  ....
  var pkg_dir = "___pkg___";
  var js_src = "assets";
  grunt.initConfig({
    pkg: grunt.file.readJSON("package.json"),

    file_dependencies: {
      assets: {
        options: {
          extractDefinesRegex: /(?:\/\/\s*(?:define)|(?:export))\s*\(\s*['"]([^'"]+)['"]/g,
          extractRequiresRegex: /(?:\/\/\s*(?:require)|(?:import))\s*\(\s*['"]([^'"]+)['"]/g
        },
        src: [js_src + "/**/*.js"]
      }
    },
    concat: {
      test: {
        options: {
          process: function(src, filepath) {
            return (
              "// Src: " +
              filepath +
              "\n" +
              // 依存関係用のコマンドの無効化。ガチのimportなどが使えなくなるが...
              src
                .replace(/(?:define|export)\s*\((.+)\)\s*;/g, "// Def: $1")
                .replace(/(?:import|require)\s*\((.+)\)\s*;/g, "// Req: $1")
            );
          }
        },
        src: ["<%= file_dependencies.assets.ordered_files %>"],
        dest: pkg_dir + "/main.js"
      }
    }
  });
  ....
```

■ ソースファイル

```js:assets/file1
// require("hoge");
... 処理 ...
```

```js:assets/file2
// define("hoge");
... 処理 ...
```

■ 完成品イメージ

```js:___pkg___/main.js
// Src : assets/file2
// require("hoge");
... 処理 ...
// Src : assets/file1
// define("hoge");
... 処理 ...
```

githubにお試しパッケージをおいといてみました。
https://github.com/taka-vagyok/test-grunt-file-dependency-concat

## おまけ: 奮闘記録

JSは、昔から依存関係について困っている人が多く、
解決のためのいろいろなフレームワークが用意されているようです。

* RequireJS+AMD
* Closure Tool

上記ツールは新たにフレームワークを導入したり、新たな記法をマスターしないといけないので、
引継ぎのことを考えると、覚えることが多くなり面倒になりそうだったため、JSの知識だけで対応できるもの
を基準に選定しました。

自分の対応しなければいけないプロジェクトは、ES5の制限化で動くといった制約もあり、
Import/Exportというキーワードは使いたくなかったため、いろいろ調べた結果、
grunt-file-dependencies というプラグインを見つけることができました。
（3年前につくられたプラグインですが、2018/9/6現在、日本語で記事にしているものはないようですね）

grunt-contrib-concatでuse strict を取り除くサンプルを見つけることができたので、
キーワードを埋め込んどいて、結合時にとりのぞいたらいいんじゃ？と考え今の形になりました。

いろいろと活用法がありそうなプラグインを見つけたので、ちょっとしたドキュメントを作るのにも
役にたちそうな気がしました。


... 今日はここまで。



## おまけ2: ぼやき

最近ではJSそのもので解決しようと、import/exportといった
キーワードを増やしたりと、本体側でも、いろいろ試行錯誤されているようです。
この記事は、JavaScriptのルールが制定されたら不要になるんだろうなぁ～。


# 改定履歴

2018/9/4 Init.
2018/9/6 表現みなおし。動的解決方法の他のパターンを追加。