はじめに
Backbone用のYoman Generatorを公開しました。
https://github.com/cgetc/generator-backbone-multipage
ポイントをざっと説明します。
複数ページでのrequire.js
- require.configのbaseUrlを設定しないと、JSの非同期読み込みの際のパスが変わる
- 共通で使用するファイル(common.js)を単一ファイルにまとめた
- require.configをrequire.jsに含めた(HTTPリクエストを減らすため)
- 各ページで使用するファイル(page/*.js)を単一ファイルにまとめた
※以前はrequire.jsをcommon.jsに含めていたが、そうすると、common.jsを読み込むまでブロッキングしてしまうため、require.jsとcommon.jsを切り離した。
開発環境と本番環境の切り替え
- require.jsに環境ごとの設定部分をGruntで結合して切り替える。
- 開発環境はminifyしないソースを直接参照する様になっている。
Gruntfile.js
function generate_develop_require_js () {
var options = _.pick(get_requirejs_options('develop'), ['paths', 'shim', 'urlArgs']);
options.baseUrl = '/' + jsDir;
delete options.paths.requirejs;
grunt.file.write(outJsDir + '/require.js', [
grunt.file.read('./node_modules/requirejs/require.js'),
'require.config(' + JSON.stringify(options) + ');'
].join('\n'));
}
- 本番環境はrequire.jsはminifyされたソースを参照する
Gruntfile.js
function generate_build_require_js () {
var options = _.pick(get_requirejs_options('build'), ['urlArgs']);
options.baseUrl = '/' + outJsDir;
grunt.file.write(jsDir + '/require.js', [
grunt.file.read('./node_modules/requirejs/require.js'),
'require.config(' + JSON.stringify(options) + ');'
].join('\n'));
}
- 本番環境はrequire.jsにmodulesオプションを指定して結合する
- 共通JSはincludeを指定し、各ページのJSはexclueを指定する
- このオプション指定でコード内でrequireされていなくても結合される
Gruntfile.js
grunt.config(['requirejs', 'build', 'options', 'modules'], generate_requirejs_build_modules());
grunt.task.run('requirejs:build');
function generate_requirejs_build_modules () {
var libs = _.keys(config.requirejs.paths),
modules = [{
name: 'common',
include: libs
}];
grunt.file.recurse(jsDir + '/page', function (abspath, rootdir, subdir, filename) {
var dir = subdir? subdir + '/' : '';
modules.push({
name: 'page/' + dir + filename.slice(0, -3),
exclude: libs
});
});
return modules;
}
HTMLからrequire.jsモジュールへの変数の渡し方
- 各ページのJSをFunctionのモジュールとして定義する
page/todo.js
define(['backbone', 'collection/Todos', 'view/todo/ListView'], function (Backbone, Todos, ListView) {
return function (todo_list) {
var todos = new Todos();
view = new ListView(todos);
todos.set(todo_list);
new Router();
Backbone.history.start();
};
});
- 各ページのJSをrequireし、引数としてJSONを渡す
inex.html
<script>
require(['page/todo'], function (load) {
load([
{id: 1, title: 'hoge', description: 'hogehogehoge'},
{id: 2, title: 'fuga', description: 'fugafugafuga'}
]);
});
</script>
テンプレート管理
- JSTテンプレートをgrunt-contrib-jstでJSにプリコンパイルする
- {namespace: false, amd: true}をオプション指定するとグローバル変数なしのAMD形式で出力される
- デフォルトではwith文を使うためtemplateSettings:variableオプション指定して抑制する
Gruntfile.js
grunt.initConfig({
jst: {
options: {
namespace: false,
amd: true,
templateSettings: {
variable: 'data'
}
}
}
});
tmpl/todo/ListView.ejs
<li>
<a href="#description/<%= data.id %>"><%= data.title %></a>
</li>
- JSファイルなのでrequire.jsで読み込める
ListView.js
define(['backbone', 'tmpl/todo/ListView'], function (Backbone, listTmpl) {
return Backbone.View.extend({
el: '#todo-list',
template: listTmpl,
});
});
雑感
- Backbone.jsは本来SPA(シングルページアプリケーション)を作成するものだが、規模が大きくなると分割した方がよいと考え、複数ページのベストプラクティスを模索した。
- require.jsの設定部分をGrunt.js内で書き出す以外に方法はないものか?(SPA前提であれば、data-mainで読み込めばいい)
- HTMLからrequire.jsモジュールへの変数の渡し方は他にないものか?(scriptタグ内にBackbone.jsのマウントポイントを置くよりはこちらの方が好み。)
- Backbone.jsにはtextプラグインがあるが、JSTをプリコンパイルした方が、パフォーマンス上優位なのは当然だが、テンプレートのフォルダもjsフォルダ配下に置かなくて済むのもメリット。
- テンプレートファイルの読み込みもrequire.jsで都度結合できるようになるメリットもある。