読者です 読者をやめる 読者になる 読者になる

CoffeeScriptファイルどうやって分けるか問題

追記

お恥ずかしい限りですが、この記事はWebpackやBrowserify等、複数のJavaScriptCoffeeScriptなどのファイルをまとめるツールが世にあることを知らない頃に書いた記事となっています。以前の内容も参考までに掲載しますが、多くのケースではこれらのツールを使って管理をする方が得策でしょう。


今開発しているアプリケーションのCoffeeScriptファイルが500行を越えてしまい、そろそろファイルを分割しておかなければならない気配がしたので、ここ数日でCoffeeScriptファイルをどうやって分割するか方針を考えていました。

ネット上を探すと、おおよそ3つの手法に分かれていたような気がします。

1. クラス名の先頭にwindow.を付けて、htmlからコンパイルしたjsファイルを全て読み込む

すぐに分かる CoffeeScript によるクラスの書き方 | Developers.IO

このサイトを参考にしました。

つまり、全てのクラスをwindow.Foowindow.Barという感じにwindowのプロパティにしておいて、html側で

<script src="js/foo.js"></script>
<script src="js/bar.js"></script>

とまとめて読み込む方法です。

利点
  • 一番楽そう
欠点
  • ファイルが増えたときにいちいちhtmlファイルに書き込まなければならない
  • htmlファイルの読み込み順を考えなければならない
  • クラスを入れ子にする場合、先に親クラスを定義する必要がある

2. require.jsを使う

jsファイルは別々にコンパイルしておくが、RequireJSというライブラリを用いてそれぞれのjsファイルを動的に読み込むという方法です。

利点
  • requireという関数を用いて、簡単に別のjsファイルを読み込むことができ、依存関係も分かりやすい
  • 不必要なjsファイルを読み込むことがない
欠点
  • ライブラリを用いるため、学習コストが上がる
  • jsファイルの読み込み順を指定できない
  • require.jsに関連する問題が発生した時に解決が大変そう

3. 複数のCoffeeScriptファイルをまとめて1つのjsファイルにコンパイルする

coffee --joinCakefileを用いて、複数のCoffeeScriptファイルを1つのjsファイルにコンパイルする方法です。ただしCoffeeScript 1.8からはcoffee --joinはdepricatedになったようです*1

利点
  • 1.の案からhtmlに書き込む手間がなくなる
  • window.は必要なくなる
欠点
  • Cakefileを書く手間
  • CoffeeScriptファイルの接続順を考えなければならない
  • クラスを入れ子にする場合、先に親クラスを定義する必要がある

3つの案のうち、最初は2.にしようと考えていましたが、開発するアプリケーションがシングルページのため、jsファイルを非同期で読み込むメリットが薄そうだったので、結局3.の方法を使うことにしました。

参考:RequireJSをプロジェクトで使ってみての所感 - ダーシマ・ヱンヂニヤリング

結局どうしたか

以下のようなCakefileを書いてcakeすることで複数のCoffeeScriptファイルをまとめることに成功しました。

jsDir以下のCoffeeScriptファイルをまとめてdestFileにコンパイルするやつ

長くなってしまいましたが、大まかに説明すると、jsDir以下のディレクトリのうち、ignoreDirsを除いたものからCoffeeScriptファイルを探して、それらのCoffeeScriptファイルをいい感じに接続したものをdestFile + '.coffee'に保存した後、そのファイルを同名のjsファイルにコンパイルするというのを行っています。

例えば、以下のようなファイル構成とします。

app
├── index.html
├── Cakefile
└── js
    ├── lib
    │   └── someLibrary.coffee
    ├── foo
    │   ├── bar.coffee
    │   └── hoge.fuga.coffee
    └── one.two
        └── threeFour.coffee

このとき、各ファイルの内容は

# js/foo/bar.coffee
class Foo.Bar# js/foo/hoge.fuga.coffee
class Foo.Hoge.Fuga# js/one.two/threeFour.coffee
class One.Two.ThreeFour

のようなクラスが書かれているものとします。

ここでcake buildを行うと、js/以下のうちlib/を除いたbar.coffeehoge.fuga.coffeethree.coffeeを連結したものがjs/main.coffeeに保存されます。

ただ、このままでは親クラスを定義しないでいきなり子クラスを書き始めることになるので、js/main.coffeeの先頭にはディレクトリ構成に応じて何行かコードが自動的に挿入されて名前空間の確保を行います。

上の例では、

Foo = {} unless Foo?
Foo.Bar = {} unless Foo.Bar?
Foo.Hoge = {} unless Foo.Hoge?
Foo.Hoge.Fuga = {} unless Foo.Hoge.Fuga?
One = {} unless One?
One.Two = {} unless One.Two?
One.Two.ThreeFour = {} unless One.Two.ThreeFour?

js/main.coffeeの先頭に入ります。これにより、各ファイルのクラスは問題なく宣言・参照することができます。

その後js/main.coffeejs/main.jsコンパイルされるので、html側からは

<script src="js/main.js"></script>

で読み込むことで1つにコンパイルされたjsファイルを使うことができます。

Cakefile参考資料

Cakefileについては、

CoffeeScript付属の軽量ビルドツールcakeを使ってみる - SELECT * FROM life;

が参考になりました。あと、Cakefileのテンプレートとして

GitHub - twilson63/cakefile-template: This is a cakefile template for coffeescript, docco and mocha

を使いました。