JavaScriptにおける依存関係の解決とファイル分割
こんにちわ、ソニックムーブの青海です。
今回はHerlock自体とは少し話が逸れますが
JavaScriptにおける依存関係の解決とファイル分割のお話となります。
目次
前置き
JavaScriptにおける依存関係の解決方法は言語仕様としては何もありません。
基本的に軽いモノなら1ファイルに全て書くということになり、大きなモノなら複数ファイルに分けて、個別に読み込みますね。
問題点
問題点としては下記等が挙げられます
- 読み込み順に依存
- 定義の前後関係
- import等が無い
- namespaceが無い
では現実的に依存関係を解決する方法として何があるでしょう。
依存関係の解決方法
1.googleclosurelibrary等で採用されているコンパイル方式
2.requirejs等による動的解決
等が一般的な方法でしょうか。
コンパイル方式について
以前ソニックムーブ社内では、1の方法を採用していました。
Objectでnamespaceらしきものを定義して、ファイル毎に処理を定義。
結合して使用というものです。
Google Closure Libraryと似たような概念ですが
下記のようにコメントとして依存関係を記述するような感じです
1 |
[sourcecode lang="js"]//#include hoge.hage.Test[/sourcecode] |
良い点
- ファイル毎に依存性の解決が可能
- デプロイ時は結合するので通信回数を減らせる
- コンパイル前提なので環境毎に書き分け等が容易
- トリッキーな書き方になりにくいのでIDEとの相性は良い
問題点
この方法の問題点は、若干デバッグが手間だということでしょうか
- 毎回コンパイルが必要:これに関しては最近の流行のgrunt等で監視させれば解決出来るでしょう。
- エラー箇所の補足:ブラウザのコンソールでは結合後のファイルベースでのエラー行数を補足することになります。
※この辺もソースマップ等で解消は可能
requirejs等による動的解決
AMDモジュール方式で記述する最近流行のやり方ですね。
対応しているライブラリなども多いので、無難な方法だといえるでしょう。
requirejsだとデバッグ時は動的にscriptタグを書き出して依存性を解決可能な為
デバッグは上記の方法と比べ、幾分楽ではないでしょうか。
製品環境への書き出しは結合ツールが出回っているので結合&圧縮という方法が採れます。
良い点
- ファイル毎に依存性の解決が可能
- デプロイ時は結合する方式も選択可能
- 動的な解決が可能なので、毎回コンパイルしなくて良い
- 方法としてポピュラー
- 色々便利機能有り
良いこと尽くめのようですが問題点もあります。
問題点
- define、require等必然的にトリッキーな書き方になるのでStorm等IDEの補完との相性があまり良くない
- 依存モジュールが多くなると引数がやたら増えるか、requireをやたら羅列する必要がある
- そもそも書き方としてあまり直感的でない
一番上は、自分が知らないだけかもしれませんが、今のとこコメントの使い方も含め最適解は見つけれていない状況です。
良い方法を知っている方がおられたら、教えて下さい。
他の言語と比べるとやはりファイル分割のめんどくささは解決されないように思います。
疑似namespace方式との併用
妥協点として下記のような感じで疑似namespace方式と併用する方法等もアリかなと思います。
ルールとしてはディレクトリ構造=ネームスペース構造として
defineに書いてあげる事で依存関係を解決するメージです。
メリットとしては、IDEとの相性という点は、requireのみで解決するより良くなる点で、
デメリットは多少手間が増える点と、恐らく一般的で無い点でしょうか。
また、globalにnamespaceを展開することを前提にしているので、
その辺りはコメント等で補完してあげる方が良いですね。
namespace方式は圧縮ツールによっては効率が悪かったりするので、書き方に一工夫必要かもです。
参考コード
app/hello/package.js
1 2 3 4 5 6 7 8 9 10 11 |
[sourcecode lang="js"]define( [ "app/package" ], function(){ if(typeof window.app.hello === "undefined") { /** @namespace */ app.hello = {}; } } );[/sourcecode] |
app/hello/Hoge.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[sourcecode lang="js"]define(["app/hello/package"], function() { 'use strict'; /** * @exports * @constructor */ app.hello.Hoge = function() { this._initialize.apply(this, arguments); }; (/** @this app.hello.Hoge.prototype */function(){ this._initialize = function() { }; //any }).call(app.hello.Hoge.prototype); });[/sourcecode] |
使用
1 2 3 4 5 6 7 8 9 |
[sourcecode lang="js"]define(["app/hello/Hoge"], function() { 'use strict'; /** @type {app.hello.Hoge} */ var hoge = new app.hello.Hoge(); //any });[/sourcecode] |
まとめ
requirejsもコンパイル方式も、若干の難点はぬぐい切れない感じですが、小規模〜中規模な案件等はrequirejsを使うのが割と良いのではないでしょうか。
規模が大きくなると、IDEでコードを追い易いという点は大きなメリットになるかと思いますので、疑似namespace方式との併用等も考慮してみると良いかもしれませんね。
しかしながら、正直言語仕様にないものを無理矢理という部分はあるので、一定規模を超える案件ではTypescript等AltJsを利用した方が良いかもしれません。
今回は一般的なJavaScriptにおける依存関係の解決とファイル分割の話でした。
弊社開発中のHerlockでもJavaScriptによる開発という点で、少し考えてしまう領域です。
JavaScriptによる規模の大きな開発は、概念と記述方式を固めるのが大事になってきますね。
次回は、Herlockの話に戻りまして、今回取り上げたrequirejs等を含むライブラリの話をしたいと思います。
ではまた次回にご期待下さい。