Concatだけでビルドを済ませてた例(Backbone.jsとAngularJS)

Concatでどこまで戦えるのか

(^ω^) 全然いけますよ

依存管理をサボってconcat

以下、「依存管理に労力を割きたくない」という理由で依存管理を省略した場合に、concatだけで破綻無くビルドするためにやっていたパターンの紹介。いけますと言った手前はあるが、最終的には現場によってケースバイケースということで、どうかひとつご容赦願いたい。

Case 1: Backbone.js

Backbone.jsの場合、extends に代表されるクラスベースのオブジェクト指向モデルに多少の制約が必要になる。デジャヴ感あるなーと思ったら、このケースについては 最近のオレオレconcatパターンとか で既出だった。

そして思い返すと個性的な設計上の制約がついてしまってる感 ^q^

ディレクトリ構成

  • app/
    • js/
      • collections/
      • components/
        • foo/
          • bar.js
          • baz.js
        • _base.js
        • qux.js
      • layouts/
      • mixins/
      • models/
      • utils/
      • views/
      • config.js
      • main.js
    • hbs/
    • sass/

_base.js としておくことで、glob的なアレで先頭にきてくれることを見越している。ここだけ結合順を意識したファイル運用だが、他には特に意識することはない。

models, views のディレクトリ下にも components の下と同じようなノリでJSファイルが配置されている。このときは、Backbone.js, Handlebars, Sass+Compass という感じのライブラリスタックだった。

concatの指定

[
  'src/app/config.js',
  'src/app/utils/**/*.js',
  'src/app/mixins/**/*.js',
  'src/app/models/**/*.js',
  'src/app/collections/**/*.js',
  'src/app/components/**/*.js',
  'src/app/views/**/*.js',
  'src/app/layouts/**/*.js',
  'src/app/main.js'
]

オブジェクトの種類によっては、先に来てもらわないと困るものもあるので、大きい分類のみ順序をつけている。新しいファイルを追加するたびに、わざわざこのリストを編集することはありえない。Concatでも何とかなる = 結合順の設定がノーメンテで運用できる、ということにしたい。

設計上の制約

単純concatの場合、JSの生オブジェクトで名前空間を作って直接代入する感じだとネストしたときにしんどい。あと継承をバンバン入れると面倒さが増す。

ので、単に次のようなルールを設けるようにしていた。

  • BaseやAbstract以外はextendsしない
  • 共通機能を足すときはextendsの代わりにmixinする
  • 名前空間の深いネストを作らない(思ったより困らない)

ネーミングルールとしてはやや奇抜だが、_base.js とか _abstract.js にするとglob結合時に普通は先頭に持ってこれる。そこでBaseないしAbstractなクラスを定義して、他のViewはそれをextendsしている。実装を付け加えたいのであれば、mixinを利用する。

この2点を守れば、そこそこの規模でも案外なんとかなる。なった。

脱線する。継承だけで全てを表現しようとすると極端な話、クラスがきれいなツリー構造を保つ必要がある。これを保つために、無理な継承関係が生まれてしまうくらいであれば、いわゆる横断的関心事などをmixinに追い出すほうがラクだった。(オブジェクトのライフサイクル中にhookポイントも用意すると、より扱いやすい)

Case 2: AngularJS

AngularJSであればconcat複雑化の敵であるextendsの概念も無理に持ち込まなければ存在しないし、モジュール内部の評価もスクルリプト実行時に即時では行われない。そもそもDI自体がランタイムの依存管理みたいなものだ。各セクションで angular.module() さえ優先的に済ませれば良い。

ディレクトリ構成

  • app/
    • components/
      • foo/
        • _index.js
        • foo-directive.js
        • foo-directive.html
        • foo-service.js
    • sections/
      • bar/
        • _index.html
        • _index.js
        • bar-controller.js
        • bar-hoge-directive.js
        • bar-hoge-directive.html
      • baz/
        • _index.html
        • _index.js
        • baz-controller.js
        • baz-fuga-directive.js
        • baz-fuga-directive.html
    • stylesheets/
    • main.js
    • main.scss

Angular Best Practice for App Structure (Public) を参考にしている。stylesheets の中身も sectionscomponents に分散させたかった感。AngularJS, Sass(libass) + grunt-sprite-smithを使ってる。

ここでも _index.js と形を変えて starts with underscore なファイル命名が登場する。この _index.js は下記のような感じで記述している。

'use strict';

angular.module('aho.sections.article', [
  'aho.base',
  'aho.components.sns',
  'actinium.providers.storage'
])
.constant('AHO', {
  DOMAIN: 'aho.mu'
})
.config(['ajaxProvider', 'localStorageProvider'], function() {
  ajaxProvider.settings.baseUrl = config.baseApi + '/' + config.clientId;
  localStorageProvider.setIdentifier('aho');
})
.run();

module名と依存moduleを定義して、ついでに constantconfig, run あたりだけ実行する。

グループ単位で先頭にこれがくれば、続くのは angular.module('aho.sections.article').controller()angular.module('aho.sections.article').directive() だけだ。順序関係が必要なものではない。

concatの指定

[
  'app/main.js',
  'app/components/**/*.js',
  'app/sections/**/*.js',
  'temp/templates.js' // grunt-angular-templates で生成
]

大ざっぱな指定でOK。

設計上の制約

なにかあったっけ・・・。

依存管理は必須ではない、はず

こんな感じで、ここ1年ぐらいBackbone.jsとAngularJSを使って、それぞれ中規模アプリケーションを書いてきたが、上記に挙げたようなconcatパターンで問題なかった。その前はBackbone.js + RequireJSを使っていたが、(他の諸氏が散々述べているような理由で)途中で面倒になった。

自分が書いたこともないような大規模アプリや、今回はそもそも面倒だから端折られている「依存関係の管理」が必要になる要件では、ビルドプロセスに作用する何らかが必要になるとは思う。あと、1ファイルにまとめるとデカすぎるときに、依存管理というか非常に賢い分割ビルドがあると嬉しいかもしれない。

色々書いたけど、多くの中規模以下案件なら concat で十分でしょうと思う次第。無理はしなくていい。

現場からは以上です。


余談。社内の中〜大規模くらいが想定されるプロジェクトで、webpackを使うと聞いたので続報を楽しみにしている。

余談2。「いつまで█████で消耗してるの?」を使おうかと思ったけどやめた。