こんにちは!エンジニアの美馬です!Vite 使ってみました!
はじめに
新規 PJ では迷わずモダンな環境を選択します。しかし現実には既存のコードを流用・修正していかなくてはならないこともあると思います。
標準に追従しモダンな周辺ツールの恩恵を受けられるようにし、スムーズに開発を継続させるべく、フロントエンドの改善施策を行いました。
やったこと
- ES5→ESNext の書き換えを行った
- Vite の Backend Integration を使用した
- 周辺ツールを整えた
- Formatter - Prettier
- Linter - ESLint
- E2E Test - TestCafe
- Type Annotation - JSDoc
やっていないこと
- TypeScript の導入
- 宣言的 UI の導入
- デザインの刷新
開発の課題
対象のサービスは、マイクロサービスとしてメインのモノリスから切り出され、8 人のチームで開発していました。切り出したは良いものの、新規コンテンツページの作成等もりもりとサービスの開発は進むのに対し、開発環境のメンテナンスは特にされていない状況でした。
周辺環境の課題
開発ツールがそろっておらず、 効率の良い開発が行えるとは言いがたい状況でした。
ビルドツールがない
トランスパイラはなく、生の ES5 が書かれていました。 バンドルツールは YUI Compressor が使用されており、bundle, minify はされていました。 JS の依存は HTML テンプレートにて管理されておりわかりにくい状態で、 ライブラリは npm で管理されておらず生ファイルが存在する状態。ライブラリのアップデートも難しい状態でした。
自動テストが十分でない
主要な機能を確認する E2E テストはあったのですが、JS エラーを網羅できるようなテストはなく、 ほかの部分ではほぼ手動でテストが行われていました。
不十分な静的解析
一応 Linter は JSLint が導入されていましたが、十分な静的解析は行えていない状況でした。(後述) ESModule での記述も静的に解析したかったこともあり、ESLint に乗り換える必要がありました。 (JSLint もこの施策中に大きめアップデートが来ていました。ESLint に乗り換えましたが。)
レガシー JS そのものの課題
古くなった JS を書き続けるということは、JS の進化の恩恵を放棄し続けるということです。 ES2015 以前の JS では ↓ のような問題を抱えていました。
ESModules がなく JS の依存が JS 内で完結しない
<script src="jquery.js"></script> <script src="common.js"></script> <script src="app.js"></script>
依存は HTML テンプレートに直接書いて管理されます。 import, export の構文は使えず、どんどんグローバルは汚染されていき、 読み込み順序、読み忘れにより度々エラーが起き、JS を書いた後 HTML を開きエラーを確認する必要があります。
見慣れないプロトタイプチェーン
someClass.prototype.say = function () { console.log("hello"); };
本サービスではこれを避けようとして独自のファクトリ関数のようなものによって擬似 Class が実装されていました。その結果、十分な静的解析ができない状態になっていました。
これらの影響により、ちょっと jQuery を推奨バージョンまでアップデートしようにも影響範囲は分かりづらく、独自に実装された内容が現在では不要であるはずの学習コストを生んでいることなど良い環境とは言えない状況でした。
Road to ESNext
効率の良い開発が行えていないと判断され、JS 改善 PJ が立ち上がり、ESNext への書き換えが始まりました。これは急に取り組み始められたものではありませんでした。
ES5 でそのまま運用されている背景からも分かるように、 これまでは事業としての開発に振り切られていた状態でした。
しかし昨年ごろから徐々に開発環境改善の動きがあり、改善の意識が高めてきた先の本施策でした。 次のような段階を踏んだ上で書き換えに至りました。
フロントエンドテスト環境改善の動きが出始める
まずはテスト不足からアプローチされました。TestCafe での E2E テストが整えられ、主要な機能をテストできるようになりました。
TestCafe が CI で動き始める
毎朝とデプロイ時に E2E が動くようになりました。CodeBuild で動いています。 E2Eテストをクラウド移行したお話 - LIFULL Creators Blog
TestCafe で全ページの JS Error を拾うようになる
全ページで初回ロード時の JS エラーを拾うようにしました。
リポジトリ内の使用されていないコードをひたすら grep して削除
ESNext 化する前に不要なコードは削除しておきました。 幸いリポジトリに配置されていた jQuery プラグインや自作ライブラリの多くは使われていないもしくは限定的な使われ方をしていたのでたくさん削除できました。
ビルドツール導入が検討される
トランスパイラのみ導入すること、新環境を作成すること、等を検討する中で ノーバンドルツールである Snowpack, Vite のうち Internet Explorer 対応が容易かつ勢いが感じられた Vite に注目、採用することを決めました。
ESLint, Prettier を設定
既存の JS の ESNext 化の際、ツールで一括置換しようにもユニットテストはないので手動確認が必要になってしまいます。 また、Vite を採用するにあたって ESModules に書き換える必要があります。 全部書き換えることに決めたので、最大限にツールを活かして書き換えを行うべく周辺ツールを整えました。
ESNext 化
書き換え対象は 3 万行ほどありましたが、盤石な体制を整えて レビュー 1 人書き換え 2 人の計 3 人での手作業で JS を書き換えつつテストを作成するパワープレーを実行しました。
導入したツール
Vite
Rails や Laravel とも組み合わせることができます。
JSDoc
もともと JSDoc を書く文化はあったのでそのまま採用しました。今回 TypeScript は導入していませんが型をざっくり守っています。tsc の checkJs によって型チェックを行うこともできます。
ESLint
- eslint-plugin-jsdoc
- JSDoc で不正な記述があった場合に検挙します。
- eslint-plugin-import
- 存在しないパスやモジュールを指定した時に注意してくれたりします。
- eslint-import-resolver-alias
- import エイリアスを設定しているので対応します。
- eslint-plugin-prettier
- Prettier のエラーを ESLint のエラーとして表示します。
Prettier
TestCafe
E2E テストを行います。クロスブラウザに強く、セットアップが容易です。
Cross-Browser End-to-End Testing Framework | TestСafe
Vite で嬉しかったこと
爆速ビルド
従来のビルドツールを導入すると分単位で待ち時間が生じてしまいます。
しかし esbuild のような高速ビルドツールやこの Vite などのノーバンドルツールによりその待ち時間は大幅に短縮可能になりました。
Vite では開発環境向けには Native ESM を活かし JS をビルドするので待ち時間がほぼありません。 3 秒で開発開始可能です。
簡素な Config
postCSS, esbuild, Rollup と内包しているので Vite さえ面倒をみていればビルド環境が整えられます。 設定ファイルが簡素で、package.json もシンプルであることも良いところではないかと思います。
vite.config.js
import legacy from "@vitejs/plugin-legacy"; import glob from "glob"; export default defineConfig({ plugins: [ legacyPlugin({ targets: ["ie >= 11"], }), ], resolve: { alias: { pc: "/assets/js/pc", sp: "/assets/js/sp", common: "/assets/js/common", }, }, build: { manifest: true, rollupOptions: { input: glob.sync("**/js/**/entry.js"), }, outDir: "out", assetsDir: "js", }, server: { port: 3000, hmr: { protocol: "ws", host: "localhost", }, }, });
package.json
"dependencies": { "@vitejs/plugin-legacy": "^1.5.0", "vite": "^2.4.4" }
レガシーブラウザ対応
SPA ではなく MPA でしたが容易に導入することができ、 レガシーブラウザ対応もプラグインを設定して簡単に行えました。
ライブリロードがあることも格段に開発体験を向上させてくれます。 (HMR まであるが JQuery を使用しているので今は活かされていない)
書き換えで苦労した点
Vite Backend Integration
ガイドにあるように簡単にエントリーポイントのみ指定することで動作しますが、Native ESM による読み込みが行われるため RTT が生じます。回避するには、Vite から出力された manifest.json から依存ファイルを辿り、preload directives やタグを SSR しておく必要があります。
manifest.json
{ "assets/main.js": { "file": "main.5a48225c.js", "src": "assets/main.js", "isEntry": true, "imports": ["_moduleA.9fb6069d.js"] }, "_moduleA.9fb6069d.js": { "file": "assets/moduleA.9fb6069d.js", "imports": ["_moduleB.ead1d8b3.js"] }, "_moduleB.ead1d8b3.js": { "file": "assets/moduleB.ead1d8b3.js" } }
HTML(SSR)
<script type="module" src="assets/moduleB.ead1d8b3.js"></script> <script type="module" src="assets/moduleA.9fb6069d.js"></script> <script type="module" src="assets/main.js"></script>
module/nomodule による問題
type=module では常に use strict モードとなるので注意が必要です。
また、トップレベルの this は global ではなく undefined を返すようになります。
ECMAScript® 2022 Language Specification
既存のライブラリで最上位にて this を参照していたものを window に変える必要がありました。
jQuery
全書き換えのついでに 1 系から 3 系へのアップデートを行いました。
VSCode で型を参照しつつ非推奨の構文を取り除くことができ便利でした。
成果
7.1 万行あったJSから不要なJSを削除して 2.9 万行、ESNextに書き換えて最終的には 1.2 万行にまで減りました。
静的解析が強力になったのでレビューコストが削減されました。
パッケージ管理ができるようになったので外部ライブラリを導入しやすく、メンテナンス可能になりました。
おわりに
捨てるでも新しい何かをつくるでもなく、標準に寄せ、負債を縮小するという選択をとりました。
進化し続ける技術に追従しつつ、ユーザーにもエンジニアにもうれしい開発をしていけたらなと思います。
PR
マンションを売る時も LIFULL HOME'S へ!