背景
自分が仕事で書く WEB アプリケーションは多くの場合が小粒で、何か書く場合には sinatra を使っています。
さらにテンプレートエンジンは slim で、ビューが必要な場合は twitter bootstrap を使って書きます。
で、新規で何か書き始める時に、それっぽいディレクトリ構成を作って、twitter bootstrap とか jquery とかをダウンロードして解凍してそれっぽいところに設置してー(もしくは既存プロジェクトをディレクトリごとコピーしてきて要らないファイル消してネームスペース変更してー)、とかっていうローテクな感じのことを毎回手動で行っていて、すっと実装に入れない!めんどくせー!ってなることが多いので、いったん整備してみました。
studio3104/ore-no-sinatra-skelton · GitHub
構成
javascript/css ライブラリ群は bower で管理して、自分で書く javascript/css は coffee/sass で assets 配下に記述し、grunt によって自動コンパイルされます。
いずれも public 配下に勝手に設置されます。
ore-no-sinatra-skelton
├── Gemfile
├── Gemfile.lock
├── Gruntfile.coffee
├── Procfile
├── assets
│ ├── application.coffee
│ └── application.scss
├── bower.json
├── config.ru
├── lib
│ ├── skelton
│ │ └── app.rb
│ └── skelton.rb
├── package.json
└── views
├── index.slim
└── layout.slim
とりあえず試すだけならこんな感じでやればすぐに sinatra まわりの実装に入れるでしょう
$ git clone https://github.com/studio3104/ore-no-sinatra-skelton.git $ cd ore-no-sinatra-skelton $ npm install -g grunt-cli $ npm install $ bundle $ bundle exec foreman start
これで、localhost:9292 でアプリケーションが起動します。
あとは lib, views, assets 配下のファイルを編集して開発していくだけです。
少し詳しく解説
bower
bower は javascript/css ライブラリ群 のパッケージマネージャで、bower.json に記述されたパッケージを Search Bower packages から取得してきて bower_components ディレクトリ配下にインストールして管理してくれます。
しかしただそれだけであって、例えば javascript のファイルを public/js 配下に、css のファイルを public/css に設置するなど、そういったことを柔軟にやってくれるわけではありません。
そういったことは、後述する grunt によって解決します。
bower.json 全体
dependencies に必要なライブラリ名とバージョンを記述します。
exportsOverride は、後述する grunt の grunt-bower-task から利用する場合に、dependencies に記述されたそれぞれのライブラリの中からどのファイルが必要なのか明示するために記述します。
{
"name": "ore no sinatra skelton",
"version": "0.0.1",
"dependencies": {
"vue": "latest",
"underscore": "latest",
"jquery": "latest",
"bootstrap": "latest",
"bootswatch": "latest",
"Font-Awesome": "latest"
},
"exportsOverride": {
"jquery": {
"js": "dist/*"
},
"underscore": {
"js": [ "underscore-min.js", "underscore-min.map" ]
},
"vue": {
"js": "dist/vue.min.js"
},
"bootstrap": {
"js": "dist/js/bootstrap.min.js",
"css": [ "dist/css/*.min.css", "dist/css/*.map" ],
"fonts": "dist/fonts/*"
},
"bootswatch": {
"css": "journal/bootstrap.min.css"
},
"Font-Awesome": {
"css": "css/font-awesome.min.css",
"fonts": "fonts/*"
}
}
}
bower 単体での利用
後述する grunt などと組み合わせてではなく、単体で利用する場合は、上記のような bower.json を用意するか、 bower init コマンドによって対話的に bower.json を作成した後、 bower install コマンドを実行します。
bower install [package name] コマンドを実行すると、[package name] のみインストールすることも出来ます。
また、 bower install [package name] --save コマンドを実行することで、[package name] の情報が bower.json に記述されます。
今回は単体利用の目的ではないので、詳しく知りたい場合は後述の参考リンクを参照してください。
grunt
grunt は javascript で振る舞いを記述するタスクランナーで、プラグインとの組み合わせによって、例えば指定したディレクトリ配下の css ファイルを minify したり結合したり、 例えば altJS で書かれたファイルをコンパイルして指定ディレクトリに設置したりなどが出来ます。
Gruntfile.coffee 全体
この Gruntfile.coffee を設置して grunt コマンドを実行すると以下の様になります。
(npm install -g grunt-cli と、リポジトリ内の package.json を設置しての npm install も必要です)
bower installを実行し、必要なファイルを適切にpublic配下に設置assets配下にあるassets配下を監視し、ファイルの変更を検知したらコンパイルして適切に配置
module.exports = (grunt) -> grunt.initConfig coffee: compile: options: bara: true files: [ expand: true cwd: 'assets' src: [ '**/*.coffee' ] dest: 'public/js/' ext: '.js' ] compass: dist: options: sassDir: 'assets' cssDir: 'public/css/' cssmin: my_target: files: [ expand: true, cwd: 'public/css', src: [ '*.css', '!*.min.css' ], dest: 'public/css/', ext: '.min.css' ] bower: install: options: targetDir: 'public' layout: (type, component, bower_path) -> path = if component == 'bootswatch' && type == 'css' "#{type}/#{component}" else if component == 'bootstrap' && type == 'fonts' "css/#{type}" else type path install: true cleanTargetDir: true cleanBowerDir: false esteWatch: options: dirs: [ 'assets' ] 'coffee': (path) -> [ 'coffee' ] 'scss': (path) -> [ 'compass', 'cssmin' ] grunt.loadNpmTasks 'grunt-bower-task' grunt.loadNpmTasks 'grunt-contrib-coffee' grunt.loadNpmTasks 'grunt-contrib-compass' grunt.loadNpmTasks 'grunt-contrib-cssmin' grunt.loadNpmTasks 'grunt-este-watch' grunt.registerTask 'make', [ 'bower', 'coffee', 'compass', 'cssmin' ] grunt.registerTask 'default', [ 'make', 'esteWatch' ]
grunt-bower-task
Gruntfile.coffee の bower の部分だけ抜粋。
bower: install: options: targetDir: 'public' layout: (type, component, bower_path) -> path = if component == 'bootswatch' && type == 'css' "#{type}/#{component}" else if component == 'bootstrap' && type == 'fonts' "css/#{type}" else type path install: true cleanTargetDir: true cleanBowerDir: false
targetDir
bower install したファイルをどこに設置するか指定。
layout
どのようなレイアウトで targetDir 配下に設置するか指定。
byType, byComponent から選ぶか、自前で定義する。
- byType で実行した場合のレイアウトの例
public
├── css
│ ├── Font-Awesome
│ │ └── font-awesome.min.css
│ ├── application.css
│ ├── bootstrap
│ │ ├── bootstrap-theme.css.map
│ │ ├── bootstrap-theme.min.css
│ │ ├── bootstrap.css.map
│ │ └── bootstrap.min.css
│ └── bootswatch
│ └── bootstrap.min.css
├── fonts
│ ├── Font-Awesome
│ │ ├── FontAwesome.otf
│ │ ├── fontawesome-webfont.eot
│ │ ├── fontawesome-webfont.svg
│ │ ├── fontawesome-webfont.ttf
│ │ └── fontawesome-webfont.woff
│ └── bootstrap
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ └── glyphicons-halflings-regular.woff
└── js
├── application.js
├── bootstrap
│ └── bootstrap.min.js
├── jquery
│ ├── jquery.js
│ ├── jquery.min.js
│ └── jquery.min.map
├── underscore
│ ├── underscore-min.js
│ └── underscore-min.map
└── vue
└── vue.min.js
- byComponent で実行した場合のレイアウトの例
public
├── Font-Awesome
│ ├── css
│ │ └── font-awesome.min.css
│ └── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ └── fontawesome-webfont.woff
├── bootstrap
│ ├── css
│ │ ├── bootstrap-theme.css.map
│ │ ├── bootstrap-theme.min.css
│ │ ├── bootstrap.css.map
│ │ └── bootstrap.min.css
│ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.svg
│ │ ├── glyphicons-halflings-regular.ttf
│ │ └── glyphicons-halflings-regular.woff
│ └── js
│ └── bootstrap.min.js
├── bootswatch
│ └── css
│ └── bootstrap.min.css
├── css
│ └── application.css
├── jquery
│ └── js
│ ├── jquery.js
│ ├── jquery.min.js
│ └── jquery.min.map
├── js
│ └── application.js
├── underscore
│ └── js
│ ├── underscore-min.js
│ └── underscore-min.map
└── vue
└── js
└── vue.min.js
- 自前で定義する場合
上述の2例では、vue/js/vue.min.js か js/vue/vue.min.js というようなスタイルになり、少々冗長な感じになってしまうので、自前で無名関数を定義してあげ、パスを返すようにしてあげれば、そのとおりの場所に設置してもらえる。
javascript ファイルは public/js 配下にフラットに並べたいし、css ファイルは public/css 配下にフラットに並べたいことが多いと思う。
例えば、このように定義してあげると、
layout: (type, component, bower_path) -> path = if component == 'bootswatch' && type == 'css' "#{type}/#{component}" else if component == 'bootstrap' && type == 'fonts' "css/#{type}" else type path
(bootswatch に含まれる css ファイルは bootstrap.min.css というファイル名で、bootstrap のそれとカブってしまっていて上書きされてしまうので、byType のレイアウトで設置)
(bootstrap の bootstrap.min.css は ../fonts/ でフォントファイルを参照しているので、public/css/fonts 配下に設置)
このようになる
public
├── css
│ ├── application.css
│ ├── bootstrap-theme.css.map
│ ├── bootstrap-theme.min.css
│ ├── bootstrap.css.map
│ ├── bootstrap.min.css
│ ├── bootswatch
│ │ └── bootstrap.min.css
│ ├── font-awesome.min.css
│ └── fonts
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ └── glyphicons-halflings-regular.woff
├── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ └── fontawesome-webfont.woff
└── js
├── application.js
├── bootstrap.min.js
├── jquery.js
├── jquery.min.js
├── jquery.min.map
├── underscore-min.js
├── underscore-min.map
└── vue.min.js
cleanTargetDir
targetDir を初期化してからタスクを実行するかどうか指定。
上記の例だと、public ディレクトリが実行前に削除され、再作成されます。
(これめっちゃコワイんだけど、targetDir に '.' を指定して cleanTargetDir を true にして実行するとプロジェクトがディレクトリごと消滅します。.git とかも全部消える。コワイ。)
cleanBowerDir
特に指定のない場合は bower install によって bower_components ディレクトリにインストールされますが、そこを実行時に初期化するかどうかを指定する。
foreman
grunt-este-watch を実行するようにした状態で grunt コマンドを実行すると、フォアグラウンドで grunt が起動しっぱなしになってしまうので、rack アプリケーションを起動するために別のコンソールを起動しなくてはならなくなってしまいます。
そうしなくてよいように、以下のような Procfile を用意して、bundle exec foreman start します。
application: bundle exec rackup grunt: grunt
これであとはもう lib, views, assets 配下のファイルを編集して開発していくだけ。
やろうとしてやめたこととか
最初は、 views/layout.slim に js/application.js と css/application.min.css だけ読み込ませるように記述して、requirejs や browserify などを利用して自前で書いた javascript と bower でインストールしたライブラリをいい感じにガッチャンコしたひとつのファイルを作成する、わざわざ scss ファイルをインストールして compass を利用して自前で書いた css と bower でインストールしたライブラリをいい感じにガッチャンコしたひとつのファイルを作成する、などということを試みました。
が、やめました。
意図しない挙動などのトラブルに対処するにあたり、ガッチャンコされて minify されてしまっていると、どのライブラリ起因の問題なのか切り分けるときに一個ずつ読み込ませないようにして探るとか出来なくてとても大変です。
使うライブラリを link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css" だったり script src="/js/vue.min.js" みたいにそれぞれ書けばいいじゃん。
javascript/css のファイルをテンプレートでそれぞれ一つずつ読み込ませたいというだけのために頑張ることじゃないよなぁ。ということで。
あと、「requirejs は オワコンなんで browserify 使いましょう」とか突然若者に言われたので、両方それなりに調べて使えるようにしたけど、いずれを使うにしても目的と天秤にかけたらとにかくコストが高すぎると感じた。
現在ナウいものを選択したとしても、来年にはそれがオワコン化してるかもしれないし、本業の領域ではない分野のトレンドを追いかけ続けてオワコンじゃないものに切り替えていくのは、やはり今回の目的からすると異常な高コストでしかなかった。
なぜ今 coffee script なのか
「coffee script はオワコン。本命 TypeScript, 対抗は Haxe, Dreamy だけどおもしろいのが Dart だ。」
みたいな意見を数人から頂戴した。
coffee script は利用者が減ってきていて、コミュニティも以前のような活発さがなくなっているとのことだが、それは現状自分にとってはあまりリスクではなく、解説記事やサンプルコードなど、そんなに古くないものがまだまだいくらでもインターネット上に存在しているので、困難に対処しやすいという観点でもまだまだメリットが多い。
それに自分としては「文字列内での変数展開、簡潔なイテレーション記法、省略記法が使える」など、「簡潔な記述が出来る」ということだけ求めているので、情報の多さ(特に母国語での)という観点から coffee script でとりあえず書いてみようということにした。
もしメンテを継続していくプロジェクトにおいて coffee script を使わなくなったとしても、ゴリゴリ javascript を書いてるわけではないので他の altjs で書き換えるのはさほどコストをかけずに出来るだろうし、変換されたナマの javascript をベースにして開発を継続すれば良いのかなと思っております。
参考
昨今の自分用Webアプリケーションひな形 - naoyaのはてなダイアリー
昨今のWebアプリケーションのひな形その2 - Grunt - naoyaのはてなダイアリー
Bower入門(基礎編) - from scratch
Bower入門(応用編) - from scratch
jade, sass/compass, coffeescript, bowerで静的Webサイトを作るGrunt.js秘伝のタレ - Qiita