7 年半運用している Rails サービスに TypeScript + React を組み込んだ #Zaim
こんにちは、 Zaim でサーバーサイドを担当している naoki85 です。今回は Zaim で進めている Web 版 Zaim の React 対応について紹介します。
きっかけは jQuery の脆弱性
Zaim の Web 版の運用が始まったのは 2013 年の初頭。そこからデザインや UI などリニューアルを数回、繰り返してきましたが、フロントエンド周りの大幅な変更は、なかなか着手に踏み切ることができていませんでした。
ところが、どうしてもせざるを得ない事態が発生しました。昨年に問題となった jQuery の脆弱性です。Web 版の一部で利用している jQuery をバージョンアップしなくてはならなくなったため、まずはバージョンごとの差異を埋めてくれる jquery-migrate プラグインを入れることを検討しました。ただ、解決できない問題が一つだけ、残ってしまいました。
jquery-ui プラグインです。
当時はメンテナンスが止まってしまっており、Zaim が使う jQuery のバージョンでは jquery-migrate が効きませんでした。fork して対応することも考えましたが、jquery-ui を使っている部分を React コンポーネントに置き換えていく方が、今後のためには有効であると考え、踏み切りました。
最小工数で実行するため「デザイン変更はなし」
React に置き換えるといっても、デザインを見直すことから始めると多大なる時間が必要になってしまいます。そこで、UI は特に問題がない限り、既存に合わせる形で進めました。
もちろん、そもそも問題があった部分や変更の合意が取れた部分は、UI も同時に修正して進めています。
先月 6 月で、入力、編集、口座連携など家計簿としての機能の大きな部分は対応が終了しました。最小工数といいつつ着手をし始めてから 1 年が経過してしまったのは、修正範囲が大きかったことと、このリプレース専任のメンバーが不在だったことが影響しています。
Rails に React を組み込むにあたって、社内からの要望や実装をシンプルにするため、いくつか導入したことをご紹介します。
(1)SCSS の型の定義を出力
既存の CSS は、 CSS modules として取り込むようにしました。
また「元々の資産である SCSS のファイルを利用したい」「今までのように書きたい」というフロントエンドエンジニアからの要望があったため、以下のように、typed-scss-modules を使って SCSS のクラスの型定義(d.ts ファイル)を自動で生成するようにしました。
$ yarn run tsm 'app/javascript/packs/**/*.scss' -w
(2)Storybook の導入
コンポーネントの作成や編集は、Storybook にて修正するようにしました。これにより Rails を起動させることなく、コンポーネントが編集できるようになりました。
ただ、 rails/webpacker は Storybook をインストールしてくれるわけではないので、自分たちでインストールしました。Storybook の設定の一例を以下に記載します。
const { resolve } = require('path');
const pkgConfig = require('@rails/webpacker/package/config');
const environment = require('../config/webpack/environment')
module.exports = ({ config }) => {
// Here reusing webpacker's style rules. It needs setting hmr config true in
// webpacker.yml. Otherwise ExtractTextPlugin will be used and result in runtime
// errors in storybook.
const rules = [
environment.loaders.get('css'),
environment.loaders.get('sass'),
environment.loaders.get('moduleCss'),
environment.loaders.get('moduleSass'),
environment.loaders.get('typescript'),
environment.loaders.get('file'),
]
config.module.rules.push(...rules)
config.resolve.extensions.push('.ts', '.tsx', '.scss', '.css');
config.resolve.modules.unshift(resolve(pkgConfig.source_path))
return config
}
Storybook の設定ファイルから、 webpacker の設定ファイルを参照するようにしています。また、先ほど記載した CSS modules などもロードしています。
(3)アトミックデザインの導入
コンポーネントの設計にはアトミックデザインを参考にしました。
特に家計簿の入力や編集などは使い回しすることが多いので、アトミックデザインにして再利用できるコンポーネントを分かりやすく管理できるようにしました。
サーバーサイドとの通信は pages にて実施し、そこから値を渡しています。
(4)CI サーバーでコンパイル
今まで、この Rails プロジェクトは capistrano による配信をしていました。その precompile は各サーバーで実施していたものを、CI サーバー上に切り替えました。
大きな理由は、サーバー側の負荷を下げるためです。静的ファイルは別ストレージで配信しているため、サーバーで precompile しなくてはならない理由は多くありません。
また、Zaim では各コンテンツのコンテナ化を進めています。コンテナになったら、どちらにせよ、今までとは異なる場所でストレージにアップロードをすることになるでしょう。
CI サーバーでは、静的ファイルのコンパイル、およびストレージへアップロードするようにしています。その際には、データベースにアクセスしない、コンパイル用の RAILS_ENV を作成し、その環境変数を使用して CI サーバー上で precompile を実施します。
なおコンテナ化に関する取り組みとしては、過去このような記事も公開しています。
今後の課題
Zaim では Web 版以外でも、新規プロジェクトの多くは Go や Rails と組み合わせて React を採用しています。
最近はコンポーネントが増えてきたため、生成される JS ファイルも増えています。共通のモジュールは共有できるようにし、極力コンパクトにするノウハウを蓄積することが今後の目標の一つです。
React や Go 言語を使用して、既存のサービスをより良く改善していくエンジニアを募集しています!