dely Tech Blog

クラシル・TRILLを運営するdely株式会社の開発ブログです

jQueryへの依存を外す方法

こんにちは!dely でフロントエンドエンジニアをしている @all__user です。

この記事は dely Advent Calendar 2018 の8日目の記事です。

昨日は、iOSエンジニアのほりぐち( @takaoh717 )が「iOS版クラシルの開発からリリースまでの流れ」というタイトルで投稿しました。

tech.dely.jp

iOS 版 kurashiru の開発体制の遍歴がよく分かるような内容となっていますので、ぜひチェックしてみてください!

はじめに

ここ一年間で Web 版 kurashiru のフロントエンドは Rails から Vue の SPA へと少しづつ置き換えられてきました。
今回はその中でも jQuery への依存を外す際に行ったことにフォーカスを当ててまとめてみたいと思います。

目次

リプレース or 少しづつ置き換え

現在 jQuery を使った Web サイトを運用していて SPA への移行を考えている方の中には、リプレースするか少しづつ置き換えていくかで悩んでいる方もいるかもしれません。

最終的に kurashiru では少しづつ置き換える方法を取りました。
リプレースという選択肢もありましたが、割けるリソースの数、 SEO への影響などもろもろを考慮すると、エイヤっ!で移行するにはリスクが大きすぎると判断したためです。

稼働中の Web サイトを SPA へ書き換えるという作業は、いってみれば走っている車のタイアを交換するようなものです。人が住んでいるマンションを、人が住んでいる状態のまま建て替えるようなものと言ってもいいかもしれません。

jQuery を残したまま Vue に置き換えていくこともできますが、jQuery を使用している部分が無くなるまで jQuery と Vue が併存することになります。バンドルサイズはできるだけ小さく抑えたかったので、jQuery への依存を先に外すことにしました。

polyfill を入れる

Array.fromArray.prototype.findIndex などのメソッドは古いブラウザでサポートしていない場合があります。
どのブラウザでもこれらのメソッドを安全に呼び出せるように polyfill を導入しました。

これまではマルチブラウザ対応を jQuery がやってくれていましたが、依存を外していくにあたり別の方法でサポートする必要があります。

kurashiru では polyfill.io という CDN を使用して、User-Agent ごとに最適な polyfill を読み込むようにしています。

github.com

Sprockets 👉 Webpacker

まず最初に Sprockets のエントリーポイントをすべて Webpacker に移しました。

github.com

Webpacker はデフォルトで CoffeeScript に対応しているので、対象となる CoffeeScript ファイルを import したファイルを用意して Webpacker のエントリーポイントに移すだけです。

gem 👉 npm

gem の jquery-rails を削除し npm の jquery に置き換えます。

www.npmjs.com

CoffeeScript 👉 TypeScript

decafjs を使って CoffeeScript を JavaScript へ自動変換します。

www.npmjs.com

と、これで動いてくれれば良いのですが、まあまあ動かないところがあります。
ソースコードを読みながら地道に修正しつつ TypeScript に書き換えていきました。
decafjs はあくまでも TypeScript 版の下書きを作ってくれるツールくらいの感覚で使用しました。

jQuery 👉 VanillaJS

VanillaJS とは特定のフレームワークを使わずに DOM の標準 API のみを使って書くことを、バニラアイスのようなプレーンな状態ということに例えてよく使われている表現です。

この工程では特に、ここまでの過程で TypeScript に書き換えてきたことが効いてきます。
TypeScript で型をしっかり縛ることで typo や型エラーなどのケアレスミスで消耗すること無く、安心してリファクタリングを進められます。

$() 👉 Array.from(document.querySelectorAll())

jQuery のメソッドは基本的にセレクタにマッチした要素全てに対して操作を行いますが、標準 API では配列のループを回して一つ一つの要素に対して操作を行う必要があります。
.querySelectorAll() の戻り値は Array ではなく NodeList なので、Array.from() を使って Array に変換しておきます。

.each() 👉 .forEach()

.each() のコールバックに渡る引数は index, element の順ですが、.forEach() の場合は逆の element, index の順となります。

.width() 👉 .getBoundingClientRect().width

.getBoundingClientRect() は実行コストが少し気になりますが、.width().height() を再現する際にとても便利です。

.slideUp() 👉 ?

.slideUp() のように単純に標準 API へ置き換えることができないメソッドは、個別に機能を作って対応しました。
すでに Vue のアニメーションの仕組みにのっとって定義されたモーション用のクラス(.fade-enter, .fade-enter-active, .fade-enter-to みたいなやつ)があったので、それを使い、いい感じのタイミングで要素の classList に対してクラスの付け外しを行うことで再現しました。

各工程で動作確認をする

これまでの各工程の一つ一つはそれほど難しくないかもしれませんが、全ての工程を一気にやってしまうと、ちょっとした不具合があった際に、どこまでロールバックすれば良いかが分からなくなってしまいます。
できるだけ各工程の間に動作検証をいれるようにして作業を進めました。

挙動を変えないようにする

上記のように書き換えを行っていると、どうしても途中で挙動を変えたくなる箇所が出てきます。
あーこうした方がいいのにな、よしついでに直しちゃおう、っていうことが必ず出てきます。
でも、これをやってしまうと、何か不具合が起きた際に、書き換えにミスがあったのか、挙動を変えたことが原因なのかが分からなくなってしまいます。
気になった部分はコメントなどに残しておき、動作を確認できてから手を付けたほうが、着実に作業を進められます。

動作検証はビジュアルリグレッションテストで

動作確認を各工程で行う、と一口に言っても何をどこまで行えば良いのか、というのは非常に難しい問題です。
kurashiru ではビジュアルリグレッションテストという方法を使って動作検証を行いました。

ビジュアルリグレッションテストとは、コードに変更を加える前後でスクリーンショットを取り、その画像を比較することで動作検証を行うというテスト手法です。
これを各工程ごとに実施することで、書き換え前後で挙動が変わっていないことを確認しながら作業を進められます。

BackstopJS

kurashiru では BackstopJS というツールを使用してビジュアルリグレッションテストを行っています。

github.com

CI/CD などに組み込みやすいように Docker イメージが提供されていたり、コンフィグ類も過不足なく柔軟に設定できるためとてもオススメです。

意図的に 1px padding を変更した例
意図的に 1px padding を変更した例

差分が強調表示されるので分かりやすい
差分が強調表示されるので分かりやすい

一見まったく同じ見た目でも、きちんと差分を見つけ出すことができます。

まとめ

jQuery の依存を外すまでの工程を順を追ってご紹介してきました。

  1. polyfill の導入
  2. Sprockets から Webpacker への移行
  3. gem から npm への移行
  4. CoffeeScript から TypeScript への移行
  5. jQuery から VanillaJS への移行

細かい変換を繰り返しながら、各工程で動作確認をしていくことが重要です。

と、ここまで書いてきましたが、実際にはかけられる工数との兼ね合い、移行後のコードがその後どう使われるのかによっても違うので、ある程度エイヤっ!で書き換えることも正直たくさんありました。
そのあたりはよしなにやっていきましょう。

さいごに

SPA 化にあたっては、紆余曲折、チーム内でもさまざまな議論が行われました。
他にも Webpacker つらい、、、などご紹介したい話はたくさんありますが、それはまた別の機会にご紹介できればと思います!

明日はプロダクトデザイナーの @kassy が「ユーザーの声に振り回されないデザインの改善プロセス」というタイトルで投稿します。
こちらもぜひご覧ください!