- 新しいRailsフロントエンド開発(1)Asset PipelineからWebpackへ(翻訳)
- 新しいRailsフロントエンド開発(2)コンポーネントベースでアプリを書く(翻訳)
- 新しいRailsフロントエンド開発(3)Webpackの詳細、ActionCableの実装とHerokuへのデプロイ(翻訳)
概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: Evil Front Part 1: Modern Front-end in Rails
- 原文公開日: 2017/12/05
- 著者: Andy Barnov、Alexey Plutalov
- サイト: Evil Martians
新しいRailsフロントエンド開発(1)Asset PipelineからWebpackへ(翻訳)
前書き
本記事は、フロントエンドのフレームワークに依存しないRailsプレゼンテーションロジックを現代的かつモジュール単位かつコンポーネントベースで扱う方法を独断に基いて解説するガイドです。3部構成のチュートリアルで、例を元に最新のフロントエンド技術の最小限に学習し、Railsフロントエンド周りをすべて理解しましょう。
混乱しがちな部分
今新たに実地のRailsフルスタック開発者になるということは、混乱の渦中で頑張るということです。Asset Pipeline/Sprockets/CoffeeScript/Sassを用いてフロントエンドを扱う「昔ながらのRails」wayは、2017年ともなると色褪せて見えます。Rails 3.1時代に選択された技術の多くは、現代の想定に応えられていません。昔ながらの手法にこだわり続ければ、この5年間にフロントエンド界隈で起こったあらゆる新技術を見送ることになってしまいます。全てを統べるJavaScriptパッケージマネージャnpmの台頭、頼もしいJavaScript文法であるES6の隆盛、連勝を重ねるBabelトランスパイラやビルドツール、かつてないほど成長を続ける CSSプリプロセッサPostCSS。フロントエンドのコードを「ページ」から「コンポーネント」にパラダイム変換したReactやVueなどのフロントエンドフレームワークが驚異的な成功を収めていることは申し上げるまでもありません。
これほどまでに複雑になったフロントエンド技術を1人の開発者の頭に押し込めようとすれば(キャリアの浅い人はなおさら)、認知機能が悲鳴を上げていわゆるJavaScript疲労になるのがオチです。
しかし、確立されたワークフローに疑問を感じるのは、時代に取り残された気持ちになるからとか、フロントエンド屋との技術トークがどんどん通じなくなるからとか、将来の見通しに不安を感じるからという理由だけではありません。つまるところ、プログラマーとは理性的な人間なのです。
Asset Pipelineの何が問題だったのか
昔ながらの方法が今も使えることについては触れないことにしましょう。今でも、Rails標準のフロントエンド向けセットアップ(とCoffeeScript)を使って成果を出せます。ビューテンプレート、スクリプト、スタイルシートは従来同様Asset Pipelineで結合/最小化されて配信されます。本番環境では2つの(少なくとも人間には)読めないファイルの形を取るので、重要性は変わりません。
しかし、開発者は普通次のような点に心を配っています。
- 分離され、再利用可能かつテスト可能な、理解しやすいコードであること
- コードの変更->結果の表示を短いサイクルで繰り返せること
- 依存関係の管理が面倒でないこと
- ツールがちゃんとメンテされていること
言うまでもなく、「昔ながらの」Railsは私たちのコードに何らかの構造を与えます。ビューテンプレート、JavaScript、スタイルシート、画像ごとに個別のフォルダが用意されます。しかしフロントエンドが複雑になればなるほど、これらを追っているうちに認知能力が低下(訳注: 参考)してしまいます。
「昔ながらの」フルスタックRails wayをおそろかにすれば、たちまちCSSやJSは死んだコードのグローバルなゴミ捨て場と化してしまうでしょう。
乗り換えを検討する理由の中には「スピード」もあります。Sprocketが遅いという問題については山ほどドキュメントがありますし、Herokuにいたっては、Asset Pipelineのパフォーマンス最適化方法に特化したガイドまで公開しています。そこではRailsアプリのデプロイで最も時間がかかるのがアセットの扱いであることを認めています。曰く「平均すると、依存関係をbundle install
でインストールするときよりも20倍以上遅い」。
開発中にCSSを1行変更してページを再読み込みするときも、結果が表示されるまでに多少待たされます。この待ち時間はすぐに長くなります。
依存関係についてはどうでしょうか。Asset Pipelineを常に最新に保つのは大仕事です。プロジェクトにJavaScriptライブラリを1つ追加する場合、CDNから読み込んだコードをコピペしてapp/assets
やlib/assets
やvendor/assets
に置くか、誰かがgem化してくれるまでぼんやり待つ方法があります。その間にも、JavaScriptコミュニティは同じことをnpm install
コマンド、今ならyarn add
コマンド一発で管理しています。アップデートも同様です。YarnはJavaScriptをBundlerのように便利に扱うことができます。
最後は、Asset Pipelineを支えるビルドツールであるSprocketsです。Sprocketsの最近のメンテ状況ははかばかしくありません。
風向きが変わった
2017年、DHHとRailsコミュニティはフロント周りの変更に手を付け始めました。Rails 5.1ではwebpacker gemによるWebpack統合、Yarnを介したnode_modules
、すぐに使えるBabel/React/Vue/PostCSSが(その気になればElmも)導入されました。
しかしAsset PipelineとCoffeeScriptは今も彼らがメンテしています。rails new
だけでプロジェクトを開始すると、昔ながらのRailsになります。JavaScript関連のトピックをググるときには、相変わらずコード例を脳内トランスパイルして隅々まで理解しなければなりません。
くよくよすることはありません。今日のRailsアプリではあらゆる現代的な手法を利用可能です。本シリーズで私たちと一緒に基礎を固めましょう。Rails/JavaScript/CSSの基本的な知識が多少あれば十分始められます。本シリーズでは設定やツールを最小限に抑えるため、最新のRails 5.1以降の機能も積極的に使います。
本チュートリアルのシリーズでは、Evil Martiansで培われた現代的で練り上げられたフロントエンド構築ベストプラクティスの一部を皆さまにご紹介いたします。
心の壁
Reactは私たちにコンポーネントで思考するよう指導します。その他の現代的なフロントエンドフレームワークもこれに準じています。モジュラリティは、BEMをはじめとする主なCSS方法論を支える哲学です。モジュラリティのコンセプトは「UIを構成するあらゆる論理的な部品は自己完結(self-contained)すべきである」というシンプルなものです。
Railsでは、ビューを論理的な部品に分割する方法が組み込まれています(ビューのパーシャル)。しかしパーシャルがJavaScriptに依存すると、おそらく他の現代的なコンポーネントと同様にapp/assets/javascripts
の下にある深いフォルダにアクセスしなければならなくなります。
パーシャルを使うときに一切合財をまとめることができれば、各パーシャルのスクリプトやスタイルシートを1箇所にまとめられるのではないでしょうか?
これからご紹介するアプローチは、React/Vue/Elmのアーキテクチャに依存していません。そしてそのように作られています。自分が使うツールの利用法を気兼ねなく学ぶことができますが、急いで学ぶ必要はありません。既にRailsで使えるツールを現代的なフロントセットの思考様式に徐々に合わせていけばよいのです。
Sass vs. PostCSS
SassはRailsに愛されています。しかし私たちの心はPostCSSに傾きつつあります。何より、PostCSSはRailsでCSSを処理するRuby組み込みのSassより36.4倍高速です。PostCSSは100%純粋なJavaScriptで書かれており、多くのプラグインを使って簡単に拡張/カスタマイズできます。cssnextというプラグインは、ブラウザでサポートされていない機能のポリフィルをすぐ使うことができますが、必要がなければ使わなくてよいのです。理由があれば、好みのプリプロセッサの上でPostCSSを使うこともできます。
私たちは何を作っているのか?
そろそろ実際に手を動かさないといけませんね。フロントエンドの新しいアプローチをご紹介するために、最小限の認証とActionCableを用いた何の変哲もない標準的なチャットアプリを作ることにします。アプリ名はevil_chat
としました。このサンプルアプリは複雑ではありませんが、それでも「フルスタック」の経験に十分なぐらいに洗練されています。
私たちのプロジェクトでは、Asset PipelineやデフォルトのRailsジェネレータが生成する大量の.scss
ファイルや.coffee
ファイルたちとおさらばすることにします。テンプレートエンジンは従来どおりERBを使い、好みに応じてSlimやHamlを使える余地を残しています。
後でまたこのフォルダ構造を振り返ります。アプリのトップレベルにあるfrontend
フォルダの中ですべてのことを行います。app/assets
は完全に置き換えられます。
一発ですべて理解できなくても問題ありません。ひとつずつ手順を追って進めましょう。
プロジェクトの開始方法
rails new
だけでは不要なものを切り落とせないので、次の新しいマジックコマンドを使います(アプリ名はevil_chat
とします)。
$ rails new evil_chat --skip-coffee --skip-sprockets --skip-turbolinks --webpack --database=postgresql -T
ご覧のとおり、CoffeeScriptやSprockets関連の機能は不要になります。本チュートリアルではテスティングまではカバーしないので、-T
オプションでテストファイル作成をスキップしています。作成後Herokuにすぐデプロイできるよう、--database=postgresql
でPostgreSQLを指定しています。
一番肝心なのは--webpack
オプションです。これを指定することで、Webpackですべてのアセットをバンドルするwebpacker gemがRailsで使われるようになります。
node_modules
フォルダにはJS依存ファイルがすべて含まれます(誤って余分なファイルを大量にリポジトリにコミットしないよう、.gitignore
にも追記されます)。package.json
はすべての依存関係を宣言します。yarn.lock
も同様なので、npm install
ではなく(より機能豊富な)yarn add
でパッケージを追加できます。.babelrc
ファイルは、ES6を、マーケットシェア1%以上のあらゆるブラウザに準拠するJavaScriptコードに変換します。.postcssrc.yml
はpostcss-smart-importプラグインやpostcss-cssnextプラグインで設定済みです。これらのプラグインのおかげで、cssnextに記載されている全機能を利用できます。
まだ何か忘れているような気がします。特に、Autoprefixerなどのツールのbrowserslistグローバル設定は、今後コードを正しくクロスブラウザ互換処理するうえで必要です。ありがたいことに、プロジェクトのルートディレクトリに以下のファイルを作成すれば簡単に修正できます。
$ touch .browserslistrc
おかげで要求される知識が随分少なくなりました(まったくというわけにはいきませんが)。
それではこのファイルを開いて> 1%
という1行を追加しましょう。これさえ知っておけばブラウザの互換性を保てます。
最初の段階で正しくやっておきたいことがもうひとつあります。Railsジェネレータのデフォルトの振る舞いの再設定です。既にネタバレしていますが、app/assets
には今後何も置く必要がないので、以下の手順でこのフォルダを削除します。application.rb
を開いて以下の行を追記します。
# config/application.rb
config.generators do |g|
g.test_framework false
g.stylesheets false
g.javascripts false
g.helper false
g.channel assets: false
end
Asset Pipelineに引導を渡すときが来ました。app/assets
フォルダを削除します。
しかし置き換え方法はどうすればよいのでしょうか。次の手順を実行します。
rails new
に--webpack
オプションを追加したことでapp/javascript
というフォルダが作成されています。このフォルダをプロジェクトのルートに移動してフォルダ名をfrontend
に変更します(名前は好みで構いませんが、frontendが一番わかりやすいと思います)。移動の際フォルダの中身は変更しないようにします。frontend/packs
の中にあるapplication.js
は、このアプリのWebpack「エントリ」ポイントとして使われます。-
application.html.erb
を開き、javascript_include_tag "application"
をjavascript_pack_tag "application"
に置き換えます。メソッド名の単語が1つ変わることですべての違いが生じます。include_tag
は、SproketsでコンパイルされたアプリレベルのJavaScriptファイルに参照を1つ挿入する昔ながらの方法ですが、pack_tag
は先のエントリポイント(つまりfrontend/packs/application.js
)から生成されたWebpackバンドルが使える新しい方法です。この段階で、<head>
にあるpack_tag
を<body>
の末尾、つまりyield
ステートメントの直前に移動します。 -
stylesheet_link_tag 'application', media: 'all'
をstylesheet_pack_tag 'application'
に置き換えます。WebpackとES6のimport
ステートメントの助けを借りて、今後はCSSをコンポーネント単位で使うことにします。これにより、CSSもすべてWebpackで管理されます。 -
次に、バンドルするファイルを探索する場所をWebpackerに指定する必要があります(デフォルトのフォルダをリネームしたので)。Webpacker 3.0によると、Railsの
config
フォルダ内にあるwebpacker.yml
で設定できます。このファイルの最初の数行に、以下の要領でアプリのフォルダ構造の変更を反映します。
default: &default
source_path: frontend
source_entry_path: packs
public_output_path: packs
cache_path: tmp/cache/webpacker
- ERBのパーシャルも
frontend
フォルダ配下に置かれるので、application_controller.rb
で次のように指定しないとコントローラから見つけられません。
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
# 以下を書くだけでよい
prepend_view_path Rails.root.join("frontend")
end
- Webpackerが3.0になったことで、開発中にオンデマンドでアセットをコンパイルするための別プロセスが不要になりました。しかしJSコードやCSSコードの変更時にページを自動更新するには、
rails s
するときに従来同様webpacker-dev-server
を実行しておく必要があります。そのためのProcfileが必要なので作成しましょう。
$ touch Procfile
このファイルに以下の設定を書きます。
server: bin/rails server
assets: bin/webpack-dev-server
このProcfileを置くことで、Foremanなどのツールを使ってすべてのプロセスをコマンド一発で起動できるようになりますが、Evil MartiansのHivemindをぜひおすすめいたします。Hivemindの兄貴分であるOvermindは実行中のプロセスを一時停止せずにpry
でデバッグできるツールですので、こちらもご覧ください。
訳注: 同社は昔のSFになぞらえた命名を好んでいるようです。Hivemindは集合精神、Overmindは「幼年期の終わり」に登場する神っぽい宇宙規模の集合知性を指します。
スモークテスト
それでは新しいセットアップが正しいかどうかテストしましょう。application.js
(packs
の下にあります)にシンプルなDOM操作コードを少し足し、Webpackerでちゃんと動くようにします。最初に、基本となるコントローラとデフォルトルーティングをそれぞれ生成する必要があります。
$ rails g controller pages home
# config/routes.rb
Rails.application.routes.draw do
root to: "pages#home"
end
views/pages/home.html.erb
の中身は完全にからっぽにしてください。次にapplication.js
の中身もからっぽにし、以下のコードに置き換えます。
// frontend/packs/application.js
import "./application.css";
document.body.insertAdjacentHTML("afterbegin", "Webpacker works!");
同じフォルダにapplication.css
ファイルを作成し、CSSも効いていることを確認できるようにします(CSSはPostCSSで処理されます)。
/* frontend/packs/application.css */
html, body {
background: lightyellow;
}
いよいよアプリの初立ち上げです。Hivemindをインストール済みであることが前提です。インストールしない場合はforeman
などのプロセス管理ツールをお使いください(私たちとしてはぜひHivemindの素晴らしさをご検討いただければと思います)。
$ hivemind
ではhttp://localhost:5000
を開きましょう(Hivemindは$PORT
に5000を設定するので、Railsも同じ環境変数で実行ポートを決定します)。次のように表示されるはずです。
ここでWebpackのクールな点を1つご紹介します。application.js
ファイルで"Webpacker works!"
の部分を変更して保存すると、ブラウザの[更新]ボタンを押さなくても画面が自動更新されます。
実際のコードを書き始める前に、コーディングスタイルを定めましょう。
ところでJSのlintはどうする?
Prettierには著名なエディタがすべて統合されているので、ボタンひとつでコードを整形できます。ESLintにもあらゆる主要なエディタ向けのプラグインがあるので、即座に結果をビジュアル表示できます。
JavaScriptの文法は年単位で更新を繰り返すので、コーディングスタイルは多岐にわたり、書き始める前からスタイルが混乱しがちです。たとえばセミコロン使う派と使わない派の争いは終わりそうにありません。StandardやPrettierのように、独自の色を出すコードフォーマッタもあります。私たちはPrettierにしました(Prettierはデフォルトでセミコロンを使いますが、いつでもオフにできます)。
lintはESLintである程度自動化するので、コーディングスタイルは常にチェックされます。また、メンテしやすいJSコードを書くベストプラクティスを豊富に収録しているAirbnbのJavaScriptスタイルガイドにも頼ることにします。
訳注: AirbnbのJavaScriptスタイルガイド日本語版は以下をどうぞ
現時点ではwebpack-dev-server
しか含まれていないpackage.json
にdevDependencies
を少々追加します。JavaScriptのlintに必要な部分をカバーすると、以下のような感じになるはずです。
{
"name": "evil_chat_codealong",
"private": true,
"dependencies": {
"@rails/webpacker": "^3.0.1"
},
"devDependencies": {
"webpack-dev-server": "^2.9.1",
"babel-eslint": "^8.0.1",
"eslint": "^4.8.0",
"eslint-config-airbnb-base": "^12.0.1",
"eslint-config-prettier": "^2.6.0",
"eslint-import-resolver-webpack": "^0.8.3",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-prettier": "^2.3.1",
"lint-staged": "^4.2.3",
"pre-commit": "^1.2.2",
"prettier": "^1.7.3"
}
}
lint-staged
やpre-commit
は、後でgit add
やgit commit
などにフックするときに便利です。これは、残念なコードを誤ってリポジトリに登録しないようにするためのものです。
最後の仕上げに、ルートフォルダに.eslintrc
ファイルが必要です。適用するルールをESLintに指示するのに使います。
$ touch .eslintrc
.eslintrc
に以下を書きます。
{
"extends": ["eslint-config-airbnb-base", "prettier"],
"plugins": ["prettier"],
"env": {
"browser": true
},
"rules": {
"prettier/prettier": "error"
},
"parser": "babel-eslint",
"settings": {
"import/resolver": {
"webpack": {
"config": {
"resolve": {
"modules": ["frontend", "node_modules"]
}
}
}
}
}
}
"extends"
キー以下の要素の順序は重要です。ここで、最初にAirbnbのルールを適用し、Prettierのフォーマットガイドとコンフリクトするものがあれば常に最新のものを使うようESLintに指示しています。eslint-import-resolver-webpack
への依存を指定する"import/resolver"
キーも必要です。これによって、JSファイル内のimport
で指定したライブラリが、Webpackが管理するフォルダ(このアプリの場合はfrontend
フォルダ)内に実際に存在するようになります。
CSSはどうする?
CSSにも何かlintツールが必要です。評判のよいツールであるnormalize.cssをnormalizeに使うことにします。CSSのエラーやスタイル違反の検出用にstylelintも使います。package.json
に開発用の依存ライブラリを2つ追加しましょう。
"devDependencies": {
...
"stylelint": "^8.1.1",
"stylelint-config-standard": "^17.0.0"
}
プロジェクトのルートフォルダに.stylelintrc
を置いてlinterに指示する必要もあります。
$ touch .stylelintrc
ファイルの中身は次のとおりです。
{
"extends": "stylelint-config-standard"
}
package.json
(今度はdevDevdependencies
ではありません!)の"dependencies"
キーの配下に次のようにnormalize.css
を追加します。
"dependencies": {
"@rails/webpacker": "^3.0.1",
"normalize.css": "^7.0.0"
},
...
次はgit hooksをいくつか導入し、git commit
時に自動チェックが走るようにしましょう。package.json
に"scripts"
を追加します。
...
"scripts": {
"lint-staged": "$(yarn bin)/lint-staged"
},
"lint-staged": {
"config/webpack/**/*.js": [
"prettier --write",
"eslint",
"git add"
],
"frontend/**/*.js": [
"prettier --write",
"eslint",
"git add"
],
"frontend/**/*.css": [
"prettier --write",
"stylelint --fix",
"git add"
]
},
"pre-commit": [
"lint-staged"
],
...
これで、コミット時にstagedファイルのエラーがすべてチェックされ、自動整形されます。
最終的なpackage.json
はgistにあるような形になるはずです。ターミナルでyarn
を実行して依存ライブラリをインストールします。
自動lintを早く使ってみたくてウズウズしているかと思います。frontend/packs/application.js
を開いてセミコロンを削除してから、git add . && git commit -m "testing JS linting"
を実行すると、ただちにセミコロンが復元されます。だらしないコーディングスタイルともこれでお別れです。
最初のコンポーネント(React未使用)
本ガイドのPart 2で扱う内容の一部を軽くご紹介します。最初のコンポーネントを作ってみましょう。
最初にapplication.css
を削除します。これはスモークテスト以外では不要です。同様にapplication.js
のコードもすべて削除します。ここからは、application.js
にはimport
ステートメントだけを書きます。このエントリポイントにすべてが集約されます。アプリ全体で使うCSSやJavaScriptの置き場所が必要なので、作ってみましょう。このフォルダの名前はinit
にしました。
$ mkdir frontend/init
$ touch frontend/init/index.js
$ touch frontend/init/index.css
続いてエントリポイント内部の新しいフォルダの登録が必要です。packs/application.js
に以下を追加します。
// frontend/packs/application.js
import "init";
新しいファイルに入れるコードも必要です。以下はinit/index.js
です。
// frontend/init/index.js
import "./index.css";
以下はinit/index.css
です。
/* frontend/init/index.css */
@import "normalize.css/normalize.css";
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
}
ここでは、アプリのすべてのフォントに一般的なスタイルをいくつか適用しています。init
フォルダはバンドル時に最初にチェックされる場所なので、ここにnormalize.css
をインクルードするのが自然です。同じフォルダを使って、後でポリフィルやエラー監視など、私たちのコンポーネントに直接関係のない機能をセットアップして必要が生じたら可能な限り読み込むこともできます。
init
は特殊ケースです。このコンポーネントについてはどうでしょうか。
各コンポーネントは、3つのファイル(ERBパーシャルとそれ用のJS/CSSファイル)を含む1つのフォルダで構成されます。
私たちのコンポーネントはすべて、frontend
配下のcomponents
フォルダ内に置かれます。ここに最初のコンポーネントを作成しましょう。レイアウトのテンプレートとみなしてこのコンポーネントをpage
と呼ぶことにします。
$ mkdir -p frontend/components/page
$ touch frontend/components/page/{_page.html.erb, page.css, page.js}
私たちがこのコンポーネントのJSファイルをindex.js
と呼んでいないことにご注意ください。この名前はinit
フォルダで予約されているからです。後でエディタでタブをいくつも開いたときに見つけやすいよう、JSファイルにはコンポーネントと同じ名前を付けることにしています。他のチュートリアルではindex.js
を使っていることが多いので、この手法はあまり見かけないかもしれませんが、後でコードを書くときに時間を大きく節約できます。
まだ私たちのコンポーネントに関連するJSロジックがないので、page.js
にはCSSファイルをimport
する行しかありません。
// frontend/components/page/page.js
import "./page.css";
page.css
の方にはコンポーネントに関連するスタイルがあります。
/* frontend/components/page/page.css */
.page {
height: 100vh;
width: 700px;
margin: 0 auto;
overflow: hidden;
}
最後の_page.html.erb
にはマークアップが含まれています。ERBや、コンポーネント同士をネストするのに使うyield
ステートメントもすべて使える点にご注目ください。
<!-- frontend/components/page/_page.html.erb -->
<div class="page">
<%= yield %>
</div>
application.js
にimport "components/page/page";
を追加して、新しいコンポーネントを参照できるようにするのをお忘れなく。
それではhome.html.erb
ビューにERBコードを書いてみましょう。
<!-- app/views/pages/home.html.erb -->
<%= render "components/page/page" do %>
<p>Hello from our first component!</p>
<% end %>
最初のコンポーネントが実際に動作することを確認しましょう。サーバーを再び起動してページを更新します。うまく行けば、以下のように表示されるでしょう。
おめでとうございます。チュートリアルPart 1はこれでおしまいです。ぜひPart 2もご覧いただき、アプリの体裁を整えてチャット関連の機能に必要なコンポーネントを導入するところまでやってみてください。少ないコード量でコンポーネントをレンダリングするヘルパーや、コンポーネント作成を自動化するジェネレータも追加します。
スタートアップをワープ速度で成長させられる地球外エンジニアよ!Evil Martiansのフォームにて待つ。