2014/03/23 @koba04
browserifyはbrowser環境でもnodeのようにrequire('xxx')というスタイルで依存しているライブラリを読み込むことが出来るようになるもので、最近盛り上がってますね。
(Backboneなど色々なプロジェクトでbrowserifyについて議論されていたり)
ここでは基本的な使い方は省略して、Backbone + Marionetteなサンプルプロジェクトをbrowserify対応してみたのでその構成についてを書きたいと思います。
(まだ全然理解出来てないので、もっといい方法があれば教えて欲しいです)
サンプルプロジェクト過ぎると役に立たないと思うので、テストも書きつつwebアプリっぽくLast.FMのAPI使ってアーティストの人気の曲一覧を表示するようなアプリにしてみました。
(testling対応はIssueにしているのでそのうちやります...https://ci.testling.com/)
browserify:app:files:"public/js/app.js":["coffee/**/*.coffee","template/**/*.hbs"]options:ignore:["coffee/vendor.coffee"]extensions:[".coffee",".hbs"]transform:["coffeeify","hbsfy"]aliasMappings:[{cwd:'coffee'dest:'myapp'src:['**/*.coffee']}{cwd:'template'dest:'template'src:['**/*.hbs']}]external:["jquery""underscore""backbone""backbone.marionette""handlebars"]alias:["hbsfy/runtime:handlebars"]vendor:files:"public/js/vendor.js":["coffee/vendor.coffee"]options:transform:["coffeeify"]alias:["jquery""underscore""backbone""backbone.marionette"]spec:files:"specs/spec.js":["specs/**/*.coffee"]options:"<%= browserify.app.options %>"
coffeescriptのcompileやhandlebarsのprecompileは、coffeeifyとhbsfyというtransformを使っています。
coffeescriptやhandlebarsのgrunt pluginを別途使用する必要がなくていいですね。
ライブラリ(vendor)とアプリ(app)のjsを分けているのは、vendor.jsはほとんど変更されることがないので毎回ビルドに含まれるのは無駄なためです。
vendor.jsのaliasで指定して、app.jsのexternalでそれを指定することでapp.js側にライブラリが含まれないようになります。
hbsfy/runtimeもそうしたかったのですが、どうしてもapp.js内で展開されてしまったのでapp.js内で指定しています...
browserifyはそのファイルからの相対パスを指定する必要があるので階層が深くなってくると階層を意識するのが面倒になります。
そこで、aliasmappingsを使ってどこからでも同じパス指定(require 'myapp/collections/users')のように指定出来るようにしています。
# coffee/view/items/hoge.coffee# beforeusers = require '../../collections/users'# after (anywhere!)users = require 'myapp/collections/users'
テスト用のspec.jsにアプリのjsも含まれてしまっているので、ホントはspec.jsにはテストだけが含まれてapp.jsを別に読み込むようにしたいのですがその方法がわからず・・・
'use strict'Backbone= require'backbone'ArtistSearchView= require'myapp/views/items/artist_search'TracksView= require'myapp/views/collections/tracks'Artist= require'myapp/models/artist'tracks= require'myapp/collections/tracks'template= require'template/layouts/top'module.exports=classextends Backbone.Marionette.Layouttemplate: templateregions:artistSearch:".js-artist-search"topTracks:".js-top-tracks"onRender:->@artistSearch.shownewArtistSearchViewmodel:newArtist@listenTo tracks,'reset',@showTracksshowTracks:->@topTracks.shownewTracksViewcollection: tracks
describe"views/layouts/top",-> expect= require'expect.js' sinon= require'sinon' Backbone= require'backbone' TopView= require'myapp/views/layouts/top' ArtistSearchView= require'myapp/views/items/artist_search' TracksView= require'myapp/views/collections/tracks' Artist= require'myapp/models/artist' tracks= require'myapp/collections/tracks' template= require'template/layouts/top' view=null beforeEach-> view=newTopView it"extends Marionette.Layout",->expect(view).to.be.a Backbone.Marionette.Layout it"template is layouts/top",->expect(view.template).to.be template describe"onRender",-> beforeEach-> sinon.spy view,"showTracks" view.onRender() it"artistSearch region has artist_search view",->expect(view.artistSearch.currentView).to.be.a ArtistSearchView it"artist_search view has models/artist",->expect(view.artistSearch.currentView.model).to.be.a Artist it"listenTo tracks's reset event, trigger showTracks",-> tracks.reset[]expect(view.showTracks.calledOnce).to.be.ok() describe"showTracks",-> beforeEach-> view.showTracks() it"topTracks region has tracks view",->expect(view.topTracks.currentView).to.be.a TracksView it"tracks view has collections/tracks",->expect(view.topTracks.currentView.collection).to.be tracks
まだまだ情報が少ない気がしますが、依存関係を意識せずrequireでライブラリを使えるのはわかりやすくてよさそうに感じました(実装を理解するともっと便利に使えそう)。
npmで提供されているライブラリだけ使うのであればbowerを使わなくてよくなるのもいいなと思いました。
repositoryはこちら