amakanのフロントエンドを色々改善した

https://amakan.net/ のこの辺の改善の続き。


react_on_rails

amakan ではサーバサイドで React.js で記述したコードを使ってHTMLを生成していて、このために react_on_rails というライブラリを使っている。このライブラリの最新バージョンが v5 から v6 に上がっていたので、まず v6 を使うように変更を加えた。

v5 までは、サーバサイドとクライアントサイドで別々の Webpack の設定ファイルを用意するような設計になっていたが。しかし v6 では1つの設定ファイルに統合されるような設計になっており、設定ファイルの中身も変わっている。これに合わせて統合を行い、サーバ用とクライアント用でそれぞれ別に実行するようになっていたビルドプロセスも1つにまとめた。

結果的に、開発環境では以下のような2つのプロセスが実行されることになった。1つは Web サーバ用のプロセスで、もう1つが Webpack でのビルド用のプロセスである。

# Procfile.dev
compile: yarn run watch
serve: puma -C config/puma-development.rb

package.json の中身も一部抜粋しておく。watch は上述したように開発環境で使うスクリプトで、--watch オプション付きで Webpack が実行される。build は本番環境でデプロイ時に実行されるスクリプトで、NODE_ENV=production 付きで Webpack が実行される。

{
  "scripts": {
    "build": "NODE_ENV=production webpack",
    "watch": "webpack --watch"
  }
}

therubyracer

Sever-Side Rendering に使う v8 のエンジンを、therubyracer から mini_racer に変えた。mini_racer が react_on_rails に推奨されているというのと、therubyracer は Ruby 2.4 で動かすのにまだ問題があるという状態だったのが、移行のきっかけ。

Sprockets

Webpack の設定に変更を加えたついでに、Sprockets を使うのをやめることにした。具体的には、Sprockets が行っていた以下のような処理を Webpack に肩代わりさせることになる。

  1. SCSS で記述したコードを CSS に変換する
  2. 本番環境向けに CSSJavaScript を生成するときに manifest ファイルを利用する

これはこの辺りの Webpack のプラグインを利用すれば簡単に実現できる。

  • css-loader
  • extract-text-webpack-plugin
  • sass-loader
  • style-loader
  • webpack-manifest-plugin

この辺りの Webpack の知見は katatema.js の開発で得ておいて良かったと思う。

PostCSS

sprockets を捨てたことで CSS の変換に Node.js が使われるようになったため、PostCSS の関連ツールが使えるようになった。PostCSSを利用した代表的なツールの1つに autoprefixer があり、今回はこれも導入している。

SPA

amakan が Single-Page Application として振る舞うためにどういう実装をしているかについては、Ruby on Rails on React on SSR on SPA - ✘╹◡╹✘ という記事で書いた。しかし、一部の画面ではこの設計に対応できておらず、Ruby のテンプレートエンジンで描画される部分も幾らか残っている状態だったので、これを機にすべてのコードをこの設計に沿うようにコードを書き換えた。

結果的に、app/views 以下がほぼ Atom フィード生成用の ERB のファイルだけになり、Slim というテンプレートエンジンからも完全に脱却することになった。具体的には jquery-ujs を使っている処理などが移行できずに取り残されていたのだが、そこまで大した処理でもなかったので、React.js を使ったコードで再実装することで対応した。

SPA の記事内で a 要素 の代わりに Link コンポーネントを用意しているという話をしたが、それはこの jquery-ujs 相当の処理を Link 要素に実装するためだった。処理の内容は、GET以外のHTTPメソッドを利用してリクエストを送ったり、遷移前に window.confirm でポップアップを表示したりというもの。

head

JSON の生成は Ruby、HTML の生成は React」という基本方針を強固にするために、Ruby で HTML 生成に関与している領域を縮小した。具体的には、head 要素の内部も極力 React で生成するようにした。Ruby 側で利用している HTML テンプレートは、原文ママでこういう状態になった。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <% server_render_js("head = Helmet.rewind()") %>
    <%= server_render_js("head.title.toString()") %>
    <%= server_render_js("head.meta.toString()") %>
    <%= server_render_js("head.link.toString()") %>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag webpack_asset_url("client.css"), media: "all" %>
  </head>
  <body>
    <%= yield %>
    <%= javascript_include_tag webpack_asset_url("client.js") %>
  </body>
</html>

ファイルの命名規則と読み込み方法

JavaScript のコードで import 対象のファイルパスの付ける拡張子を省略するかしないかというのを、省略しない側に統一することにした。

  • ライブラリのディレクトリを指定するときは拡張子が付かない
  • アプリケーション内のファイルを読み込むときは拡張子を付ける
  • アプリケーション内では index.js を利用しない
import Helmet from "react-helmet";
import Layout from "../components/Layout.jsx";
import Link from "../components/Link.jsx";
import React from "react";
import Root from "../components/Root.jsx";

それと、ファイルの命名規則も統一することにした。

  • JSX が利用されているコードは拡張子を .jsx にする
  • Airbnb のスタイルに合わせてファイル名はキャメルケースにする
  • default export される値の名前を優先的にファイル名に付ける

ファイルシステム起因の問題を軽く見て、代わりに補完が効きやすくなるなど、編集環境で恩恵を受けやすいという利点に重きを置いた。ファイルシステムはともかくGitは大文字小文字を区別して管理しているので、Gitで管理しているならそこまで困ることはない。

その他