僕が考えた最強の「CoffeeScriptで書くBackbone.js」
最近業務でBackboneを使ってるので、その中で考えた最強の構成。
CakefileとソースサンプルはBitbucketに上げました。
Compile-coffee-scripts-in-(sub)-directories
https://bitbucket.org/takyam/compile-coffee-scripts-in-sub-directories
ソースファイルの構成
src ├── 001_init │ ├── 001_setup.coffee │ └── 002_helpers.coffee ├── 002_common │ ├── 001_init │ │ └── setup.coffee │ ├── 002_config │ ├── 003_events │ ├── 004_models │ │ ├── 001_post.coffee │ │ └── 002_comment.coffee │ ├── 005_collections │ │ ├── 001_posts.coffee │ │ └── 002_comments.coffee │ ├── 006_views │ │ ├── 001_post.coffee │ │ └── 002_comment.coffee │ ├── 007_routers │ └── main.coffee └── 003_top ├── 001_init │ └── setup.coffee ├── 002_config ├── 003_events ├── 004_models │ ├── 001_post.coffee │ └── 002_comment.coffee ├── 005_collections │ ├── 001_posts.coffee │ └── 002_comments.coffee ├── 006_views │ ├── 001_post.coffee │ └── 002_comment.coffee ├── 007_routers └── main.coffee
https://bitbucket.org/takyam/compile-coffee-scripts-in-sub-directories/src/master/src
まず、
- 001_init
- 002_common
- 003_top
のように、初期設定、共通処理、各ページ別みたいにディレクトリきります。
そして、それぞれの中に、
- 001_init # 初期処理
- 002_config # 設定系
- 003_events # Backbone.Event
- 004_models # Backbone.Model
- 005_collections #Backbone.Collection
- 006_views # Backbone.View
- 007_routes # Backbone.Router
- main.coffee # メインの実行処理(main:void的な)
を作ります。
この構成の場合のアプリの作り方
この構成のポイントとしては、001_init/setup.coffee のなかで、 Appオブジェクトを定義して、基本的にすべてのものをAppオブジェクト内に格納しちゃうって事でしょうか。
#001_init/001_setup.coffee App = helpers: {} pages: {}
こんな感じでAppオブジェクトを用意してあげて、
commonや各ページ用の処理は、それぞれの 001_init/setup.coffee のなかで、
それぞれ用のオブジェクトをAppに紐づけて定義してあげます。
例えば common(共通処理) の場合
# 002_common/001_init/setup.coffee App.common = config: {} events: {} models: {} collections: {} views: {} routers: {}
このように App.common オブジェクトを作成し、
App.common に関するものは、この下に追加できるようにします。
CoffeeScript の class名には、 .(ドット)でチェーンしたオブジェクトを指定できるので、 以下のようにclassを定義します。
# 002_common/006_views/001_post.coffee class App.common.views.post extends Backbone.View el: '.post' initialize: => @$save_btn = @$el.find('.save_btn') #適当な処理
ポイントは先頭の、
class App.common.views.post extends Backbone.View
です。
こうする事で、ネームスペースを汚さずにViewでもModelでも追加していく事ができます。
で、最後にrun()させると。
$ -> new App.common.views.post() new App.common.views.comment() log('common finished')
特定のページでのみ実行する処理
余談ですが、
# 001_init/002_helpers.coffee # URLと渡したパスが一致すればtrue、一致しなければfalseを返す # @args string|RegExp path # @return boolean App.helpers.match_url = (path) -> if typeof(path).toLowerCase() is 'string' if path is '/' path = new RegExp('^/$') else path = new RegExp('^/' + path.replace(/(^\/|\/$)/g, '') + '/?$') location.pathname.match(path) isnt null
こんな感じのメソッドを用意しておいて、
# 003_top/main.coffee if App.helpers.match_url('/') $ -> new App.pages.top.views.post() new App.pages.top.views.comment() log('top finished')
みたいな感じにすると、URLベース(ハッシュじゃなくてURL)で、
お手製ルーティングみたいな事ができるので良い感じです。
サブディレクトリのコンパイル
閑話休題。
そんなこんなで僕が考えた最強の「CoffeeScriptで書くBackbone.js」は出来るわけですが、
CoffeeScriptのコンパイラがコマンドラインレベルで、標準で用意しているのは、
特定ディレクトリ以下の *.coffee をコンパイルするだけです。
coffee -j production.js -cw src/*.coffee
こんな感じ。
ただ僕が考えた最強の(ryでは、サブディレクトリ以下もマージする必要があるため、
「coffeeコマンドで簡単にやるお(^ρ^)」はできません。
なので、簡単なCakefileを作りました。
fs = require('fs') coffee = require('coffee-script') source = '' get_text = (path) -> text = '' stat = fs.statSync(path) if stat.isDirectory() files = fs.readdirSync(path).sort() path_base = path.replace(/\/$/, '') + '/' for file in files text += "\n\n" + get_text(path_base + file) else if stat.isFile() text += "\n\n" + fs.readFileSync(path, 'utf-8') return text option '-s', '--src [DIR]', 'ソースとなるCoffeeScriptが格納されたディレクトリ' option '-o', '--output [FILE]', '出力先のJSファイル名' task 'compile', '対象のディレクトリをコンパイルします(default >> -s ./src -o production.js)', (options) -> src = options.src or './src' output = options.output or './production.js' src_full = __dirname + '/' + src output_full = __dirname + '/' + output fs.exists src_full, (exists) -> if !exists console.log 'Target isn\'t exists.' return false fs.stat src_full, (err, stats) -> if err isnt null console.log err return false if !stats.isDirectory() console.log 'Target isn\'t directory.' return false source = get_text(src_full) fs.open output_full, 'w', (err, fd)-> if err isnt null console.log err return false fs.write fd, coffee.compile(source)
https://bitbucket.org/takyam/compile-coffee-scripts-in-sub-directories/src/master/Cakefile
syncしまくりなうえにno commentでお恥ずかしいのですが、
動きゃいいんだよ動けば。
READMEにも書いてますが、npm install したうえで、
cake compile
すると、src/ 以下のすべてのCoffeScriptをマージした上でコンパイルして、./production.js として吐き出すようになっています。
※出力先とソースディレクトリはオプションで変えられます
全処理が1つの javascript に
こうする事で、1JSファイルで、全ページ共通で1つのJSを読み込むだけでよくなります。
これはメリデメある事だとは思いますが、多くの場合効果的ではないでしょうか?
さらに、全CoffeeScriptから、共通処理を App.common.hoge() のように呼び出す事ができるのは、結構便利なのではないかと考えています。
これまでの場合、hoge_fuga() 関数を作った場合、必ず window.hoge_fuga = hoge_fuga しなければならず、各Scriptでそれをやると、名前空間の重複などの確認を、全ソースを確認しないと正確には行えませんでした。
今回のような構成にする事で、共通処理も1つのfunction()内に閉じられるので、名前空間の重複などもそこまで気にする事なく行えて幸せかなぁと。
まとめ
というわけで、サブディレクトリ切りまくったほうが、超巨大なJS開発においては幸せだと思ったので、思いつきでサンプルアプリとCakefileを作ってみました。
※サンプルアプリはそれっぽく書いてるだけで動くかどうか知りません(^ρ^)
CoffeeScript+Backbone.jsの良い運用方法とかだれか教えてください><