requireJS
js
ファイルの管理は requireJS
が有名だ。
requireJS
というのはかいつまんで言うと、
<html>
<head></head>
<body>
<script src="dondon.js"></script>
<script src="dondoko.js"></script>
<script src="dondokodon.js"></script>
<script src="dokodondon.js"></script>
<script src="dontakata.js"></script>
<script src="dokatantan.js"></script>
<script src="dokadoka.js"></script>
<script src="dokashaba.js"></script>
<script src="shabadosu.js"></script>
<script src="shabadobi.js"></script>
<script src="shabadobia.js"></script>
<div>some contents</div>
</body>
</html>
こいつを
<html>
<head></head>
<body>
<script src="scripts/require.js" data-main="scripts/main.js"></script>
<div>some contents</div>
</body>
</html>
require.config({
shim: {
dondokodon: ["dondon", "dondoko"],
dondokodon: ["dondoko"],
dokatantan: ["dondokodon", "dokodondon", "dontakata"],
dokadoka: ["dokatantan"],
shabadobi: ["dondon"],
shabadobia: ["dokatantan", "dokashaba", "shabadosu", "shabadobi"]
},
deps: ["shabadobia"]
});
こうしてくれる便利なモジュールだ。
両者の違いは、
- 前者:
- ファイルリスト(= 「上にあるほど優先度高いよ」)によって管理している
- 後者:
- ファイル(= 「このファイルを読み込む前に」)と、
- ファイルリスト(= 「このファイル群が読み込まれている必要がありますよ」)の組によって管理している
「依存性」というのはまさにこの「ファイルとファイルリストの組」のことなので、後者のほうがより本質的な表現と言える。
(余談だがファイルに限らず、依存関係とは $X \times [X]$ の部分集合のことである、と定義できる)
requireJS には ファイル管理と同時にモジュールを管理する方法も提供されているが、angularJS の Dependency Injection の精神とバッティングしているので、angularJS と requireJS を併用する際には使用しない(その辺の考察はこちらを参考にした: https://github.com/CaryLandholt/AngularFun#commentary )
商用 WEB アプリケーション開発の際の問題点
さて、開発の際には requireJS はとても便利なソリューションだが、実際に商用の WEB アプリケーションを作る上では通信コストを考慮しなければならない。モジュールごとにファイルを細かく分割しておきたいが、公開の際には、
- ファイル結合
- minify
- gzip 圧縮
して提供したい。
ファイル結合さえ出来てしまえば minify, gzip 圧縮は grunt のタスクとしてそういうのがあるので、あんまし考えることは無い。
問題は、
- 開発ビルド時は「ファイルとファイルリストの組」をもとに main.js を生成
- 商用ビルド時は「ファイルとファイルリストの組」をもとに「結合する順序」を決定
これを、grunt で勝手にやってくれないものだろうか?
grunt-file-dependency
ざっと探した(あんまり探してない)感じだとよさげなのがなかったので作った↓
こいつを Gruntfile.js に取り込むと、次の2つのタスクが使えるようになる。
- makeMainJs
- 「ファイルとファイルリストの組」から、main.js を生成する
- 実際には Gruntfile.js で指定した オブジェクト を、JSON.stringify して require.config にぶち込んだものを出力しているので、shim プロパティに「ファイルとファイルリストの組」を渡した上で、必要に応じて paths, deps などもGruntfile.js から渡す。
- makePriority
- 「ファイルとファイルリストの組」から、整列されたファイルリスト(配列)を出力する
- こいつを、concat タスクの src として渡し、同タスクを実行すれば、concat されて出力される。
Gruntfile.js の例
(function() {
var reqConfig = {
baseUrl: "scripts/",
paths: {
a: "hoge/fuga"
b: "fizz/buzz"
// ...
},
shim: {
a: ["b", "c", "d"]
b: ["c", "e", "f"]
e: ["g", "h"]
i: ["a", "b", "e", "j"]
},
deps: ["i"]
};
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-file-dependnecy');
grunt.initConfig({
copy: {
dev: {
expand: true,
cwd: "src/scripts/",
src: "**/*.js",
dest: "build/dev/scripts/"
}
},
makeMainJs: {
dev: {
dest: "build/dev/scripts/main.js",
main: reqConfig
}
},
makePriority: {
dist: {
options: {
baseUrl: "src/scripts",
alias: reqConfig.paths
},
deps: reqConfig.shim,
done: function(priority) {
grunt.config.set("concat.dist.src", priority);
}
}
},
concat: {
dist: {
dest: "vendor/dist/scripts/scripts.js"
}
}
});
grunt.registerTask("build:dev", ["copy:dev", "makeMainJs:dev"]);
grunt.registerTask("build:dist", ["makePriority:dist", "concat:dist"]);
};
})();
なお readme.md はそのうち書きます。