Ruby on Railsと言えばCoffeeScript、CoffeeScriptと言えばRuby on Railsと言われたぐらい、Railsでは昔からCoffeeScriptを採用していました。Ruby使いにとってはCoffeeScriptの文法は馴染みやすいものであり、また、モジュールなども無かった当初はSprocketsという考えも悪くありませんでした。
時代は移り、sprocketsだけでは対応しきれなくなったRailsはyarnやwebpackにも対応し、多様なJavaScriptエコシステムにも対応しようとしています。そんな中、CoffeeScriptも2系となって、ES2015+に対応した今、Railsにおいて、2系のCoffeeScriptをどうやって使うかを見ていきます。
Ruby 2.4.2p198
Ruby on Rails 5.1.4
Railsアプリのセットアップ
まずは、普通にRailsのアプリを作ります。Ruby、Node.js、Yarn、Ruby on Railsは既にインストール済みで有ることの前提です。ただし、webpackをReactありで有効にします。
rails new coffee_app --webpack=react
rails g controller home index
config/routes.rbを次の用にしておきます。
Rails.application.routes.draw do
root to: 'home#index'
end
あとは、rails s
で立ち上げ、アクセスしたときに、home/indexが表示されれば準備は完了です。今回はCoffeeScriptの動作だけを見ていきますので、特にモデルを作ったりはしません。おもに、この作ったページでどうなるかを見ていきます。
Assets Pipeline
さて、普通にセットアップしたらapp/assets/javascript/home.coffeeが作られているはずです。Assets Pipelineによってこのhome.coffeeはJavaScriptにコンパイルされて読み込まれていることでしょう。では、このCoffeeScriptがv2に対応しているかというと、v1が使われてしまっています。
これは、一緒にインストールされているcoffee-script-sourceが内包しているCoffeeScriptがv1になっているからです。この部分を変えない限り、v2を使うことはできません。
CoffeeScriptを指定する。
まずはコンパイル用のCoffeeScript v2を手に入れなくてはなりません。しかも、一つのファイルとしてまとまっている物でないと使用できません。そこで、"text/coffeescript"
Script Tags - CoffeeScriptにあるブラウザ用のcoffeescript.jsを使用します。ダウンロードしてvendorディレクトリに入れておきます。
cd vendor
wget http://coffeescript.org/v2/browser-compiler/coffeescript.js
このファイルを見に行くようにするため、config/initializers/coffeescript.rbを下記のように作ります。
CoffeeScript::Source.path = ENV['COFFEESCRIPT_SOURCE_PATH'] ||
Rails.root.join('vendor', 'coffeescript.js').to_s
これでCoffeeScript v2が使われるようになります。早速、hello.coffeeを次のように書き換えて、コンパイル後のJavaScriptファイルを見てみます。
class Test
constructor: ->
console.log 'create Test'
hello: (s) ->
console.log "hello, #{s}!"
test = new Test
test.hello('world')
ブラウザから、JavaScriptのソースを見ると次のようになっています。
(function() {
var Test, test;
Test = class Test {
constructor() {
console.log('create Test');
}
hello(s) {
return console.log(`hello, ${s}!`);
}
};
test = new Test;
test.hello('world');
}).call(this);
ちゃんと、class構文を使っていますね。CoffeeScript v2化がうまくいきました。
トランスパイルはできない。
さて、この方法には重大な欠点があります。それは続けてBabelによるトランスパイルができないと言うことです。そのため、次のことが使えません。
- ES2015+に未対応のレガシーブラウザ(IE11等)では動作しないJavaScriptになってしまう。
-
import
やexport
はさらにモジュール対応のブラウザが必要であり、フィンガープリントを無効にする必要がある。 - JSXもJSXのままであるため、そのままでは動作しない。
Reactを使用せず、ブラウザが限られた内部向けのアプリでも無い限り使えるとは言えません。ここら辺がアセットパイプラインの限界と言ったところでしょう。
Webpack
最新のRailsはwebpackerを使ってwebpackと連携して使うことができます。--webpack
を指定してセットアップしたことで、既に使えるようになっているはずです。
まずは、home.coffeeをapp/javascript/packs/home.coffeeに移動しましょう。つぎに、app/views/home/index.html.erbで次のような行を追加します。
<%= javascript_pack_tag 'home' %>
うまくwebpackでCoffeeScriptが変換(pack)されて読み込みできるようになっているはずです。しかし、Rails 5.1.4ではこのとき使われるCoffeeScript v1です。同じく、v2に挙げなくてはなりません。
YarnでCoffeeScriptをアップデートする。
RailsではJavaScriptのパッケージをYarnで管理できるようになっています。次のコマンドでCoffeeScriptをアップデートできます。
yarn add coffeescript
package.jsonでcoffeescriptだけがアップデートされたことを確認できるはずです。rails webpacker:compile
でコンパイルして、作られたJavaScriptを確認してみましょう。
/******/ (function(modules) { // webpackBootstrap
// webpack初期処理
/***/ 27:
/*!******************************************!*\
!*** ./app/javascript/packs/home.coffee ***!
\******************************************/
/*! dynamic exports provided */
/*! all exports used */
/***/ (function(module, exports) {
eval("var Test, test;\n\nTest = class Test {\n constructor() {\n console.log('create Test');\n }\n\n hello(s) {\n return console.log(`hello, ${s}!`);\n }\n\n};\n\ntest = new Test;\n\ntest.hello('world');\n//# sourceURL=[module]\n//# sourceMappingURL=...\n//# sourceURL=webpack-internal:///27\n");
/***/ })
/******/ });
classはclass構文のママですので、うまくCoffeeScript v2でコンパイルされています。
さて、このままではアセットパイプラインと同じ問題が起きてしまいます。なんとかしてBabelによるトランスパイルを実行させなくてはなりません。
トランスパイル付きでCoffeeScriptを使う。
トランスパイル付きに対応したcoffee-loaderは0.9.0以上です。0.8.0が入ってしまっていますので、まずはcoffee-loaderをアップデートします。
yarn add coffee-loader
webpackのオプションをマージするためにwebpack-mergeを入れておきます。
yarn add webpack-merge
webpackerの設定はcofing/webpacker.ymlとconfig/webpack配下の各ファイルです。config/webpack/environment.jsを次のように書き換えます。
const { environment } = require('@rails/webpacker')
const merge = require('webpack-merge')
const transpileCoffeeLoaderOptions = {
transpile: {
presets: ['env']
}
}
const CoffeeLoader = environment.loaders.get('coffee')
CoffeeLoader.options = merge(CoffeeLoader.options, transpileCoffeeLoaderOptions)
module.exports = environment
JavaScriptのファイルを見るとclass構文では無く、ES5の文法で書いていることが確認できると思います。
参考: https://github.com/rails/webpacker/blob/master/docs/webpack.md
さらにReactを利用する。
envプリセットだけではReactのJSX記法に対応しません。しかし、--webpack=react
でセットアップしている場合は既にReactの環境は整っており、.babelrcが用意されています。この.babelrcを見に行くように書き換えます。
const { environment } = require('@rails/webpacker')
const merge = require('webpack-merge')
const transpileCoffeeLoaderOptions = {
transpile: {
extends: `${__dirname}/../../.babelrc`
}
}
const CoffeeLoader = environment.loaders.get('coffee')
CoffeeLoader.options = merge(CoffeeLoader.options, transpileCoffeeLoaderOptions)
module.exports = environment
それぞれ次のように書き換えます。
import React from 'react'
import ReactDOM from 'react-dom'
class Main extends React.Component
render: ->
<p>
CoffeeScript + React on Rails!
</p>
ReactDOM.render <Main />, document.getElementById('main')
<div id="main"></div>
<%= javascript_pack_tag 'home' %>
さっそくrails s
で立ち上げて、確認してみます。
うまくいきました。
まとめ
CoffeeScript v2がリリースされてから、周りのツールも対応が進んでいます。デフォルトではv1が使われる事がまだまだ多いと思いますが、少しバージョンアップすればv2の利用も十分可能です。
ただ、見て頂いたとおり、現実的にはwebpackでないとうまくいかないようです。ただ、Railsがwebpackをうまく取り込んでいるため、Railsでの作成が時代遅れになると言うことは無いと思います。CoffeeScriptもその中でいつまでも使えるようになるのでは無いのでしょうか。