React Starter KitにみるWebフロントエンドに求められる機能と実装

初めまして、IT戦略3チームでUIUXデザインと社内プロジェクトのTech Leadを担当している高村といいます。この記事では、React Starter Kitという汎用的なWebプロジェクトテンプレートの実装を参考にしながら、改めて、なぜWebフロントエンドは複雑なのか、その解決方法は何かを振り返ってみたいと思います。この記事をきっかけとして、実際に現場でツール選定を行うフロントエンド開発者の方だけでなく、普段「フロントエンドには時間をかけたくない」と思っているサーバサイド開発者やWebディレクターの方たちに、「だからフロントエンドの課題は収束しにくいんだな」「フロントエンドといっても範囲は広いから、目的やユースケースを絞ってツールを選定しよう」と感じていただければ幸いです。

この記事はLINE Advent Calendar 2017の10日目の記事です。

Webフロントエンドの複雑さの背景

「最近のWebフロントエンドは流れが早過ぎて追いつけない」「学習コストを割いても無駄にならないか心配」「未成熟なライブラリばかりで手をつける気になれない」という声を聞いて久しいです。思えばjQuery + Backbone.jsが大規模開発の解として語られたのが5年前です。その後、Ember.js、Angular、React、VueとDOM操作ライブラリが次々と変遷しただけでなく、開発のベースとして重要なビルドツールも、Grant、Gulp、Browserify、Webpackと盛衰してきました。トランスパイルの視点ではCoffeeScript – ES2015 – Babel – TypeScriptとシンタックスの標準化 + 型の導入の流れがあっただけでなく、CSSの世界でもSass/LESSがデファクトスタンダードになったかと思うと、PostCSSをベースとした標準化の流れが生じた一方で、DOMライブラリのコンポーネント化にも追従したCSS Modulesの利用が増えてきているようです。通常変化することのないパッケージマネージャーも、Bowerからnpmにブラウザ向けパッケージが波及したかと思えば、現在はより安全に依存解決を行うYarnも広く利用されています。

なぜフロントエンドには多くのツールが登場するのでしょうか。短い解答はおそらく「Webブラウザの利用人口は多いので、新しい要望も多く集まるから」です。利用する人が多いと、課題や解決方法に気づく人も多くなります。ひらめきの数だけ、大小さまざまな実装が生まれるのでしょう。さらに理由を挙げるとしたら、ブラウザの元々の貧弱な機能を補完する目的が大きいと思います。ブラウザ実行環境ほど、作られた当初と今日とで違った使われ方をしている環境は少ないのではないでしょうか。Webの表現の幅が広がると、既存のツールでは想定していなかった要件が加わり、より汎用性の高い新たなツールが開発されるといった具合で、フロントエンドは進化を繰り返してきました。

ツールやライブラリの数が示すとおりWebフロントエンドが抱える要件は多いのですが、その時々で「より汎用性の高いツール(の組み合わせ)はこれ」という流れはあるように思います。今回はその組み合わせの1つの例として、React Starter Kitを取り上げます。

単機能モジュールを見通しよく組み合わせて作られたReact Starter Kit

初めにReact Starter Kit(以下RSK)について説明します。RSKはKriasoft社によって開発、メンテナンスされているオープンソースのWebアプリケーションプロジェクトテンプレートです。依存するモジュールを隠蔽せずに読みやすく組み合わせているだけでなく、豊富な機能をカバーする初期設定を含むため、開発の初速と拡張性の両方を高められるというメリットがあります。Node.jsサーバの実装にはORMライブラリであるSequelizeも含まれているため、これ1つでWebアプリケーションの開発を完結することも可能です。

RSKは、単機能のモジュールを組み合わせることで、今日当たり前のように要求される機能を数多く取り入れています。かつてのWeb開発では、その柔軟すぎる実行環境を制御するため、支配的なフレームワークの中で機能を拡張していくケースが多く見られました。現在では、単機能かつ交換可能な、役割を限定したモジュールの組み合わせで開発する機会がより増えています。その利点は、古くなったモジュールを新しいものに交換できるので、同じコードベースで2年、3年と開発を続けていけることです。RSKはモジュールの繋ぎ目を読みやすく残しているので、組み合わせの利点を享受できるプロジェクトだと思います。これに対しNext.jsは、サーバサイドレンダリング(Server Side Rendering。以下SSR)やコード分割(Code Splitting)を意識させない設計で、よりフレームワークの色が濃いプロジェクトと言えます。

本記事では、今日フロントエンドに求められる要件を多く押さえた、読みやすいプロジェクトであるという理由でRSKを取り上げています。RSKはテンプレートに過ぎず、フロントエンドの最終解ではありません。本記事の目的はWebフロントエンドの複雑さとその解決法を概観することにあるので、今すぐ使えるフレームワークを探している方に直接はお役に立てない内容です。また、RSKはViewライブラリにReactを使っていますが、実際はVue.jsでもMithrill.jsでも、フロントエンドの課題解決という意味では極端な違いはないと思っています。個々の技術というよりは、フロントエンドの要望や要件を振り返って列挙しながら、RSKが提示する具体的な解決策をご紹介できればと思います。

ここから、フロントエンドに求められる具体的な要件を列挙していきたいと思います。

スピード

Webには絶えずスピードが求められます。一方、コンテンツの配信主はさまざまな目的から、より「リッチな」表現を追求します。コンテンツはFlashが担っていたような視覚効果かもしれませんし、エクセル機能を積んだ業務ツールかもしれませんし、オンラインゲームかもしれません。コンテンツが何であれ、スピードは常に求められます。媒体がWebブラウザである理由は、多くの場合「すぐに公開し、閲覧できるから」です。とりわけモバイルネットワーク環境の貧弱さを理由に、スピードはWebフロントエンドで常にシビアに扱われる課題であり続けてきました。

ページロードやランタイムの速度が遅延すればユーザー体験は悪化しますし、それに伴うビジネス的な指標にも確実に影響します。

引用元:『超速!Webページ速度改善ガイド 使いやすさは「速さ」から始まる』佐藤歩、泉水翔吾共著

問題は、リッチな表現とスピードは相性が悪いことです。ブラウザ上に画像を表示するにも、インタラクティブな表現をするにも、リッチな表現にはサーバからのさまざまなリソースのダウンロードが必ず要求されます。コンテンツや機能が増えればユーザーの待機時間が増すにもかかわらず、レイテンシは常に一定以下でなくてはならないというジレンマと、Webフロントエンドは向き合ってきました。

画面遷移と操作のレスポンスを高速に – ルーティングとテンプレートをクライアントサイドに持つことで

今日、画面の表示を変えるために毎回再読み込みをするような重たい動作は許容されません。ブラウザが一度評価した共通するリソースを失い再度読み込み直すので、無駄も多いです。

これを避けるために発展してきたのがAjaxとシングルページアプリケーション(Single Page Application。以下SPA)です。SPAでは一般的に、ユーザーの初期アクセス時にサイト内のHTMLテンプレートとルーティングルールをまとめてダウンロードします。その間ユーザーは「Loading…」を眺めることになるのですが、これによって画面操作のレスポンスを即座に表示でき、画面遷移する際などでも必要なデータの通信のみで済むようになります。画面の再読み込みよりはずっと軽快です。

RSKでも、クライアントサイドで実行されるUniversal RouterとReactがこれを可能にしています。しかしRSKはSPAとは明確に違う設計を採用しています。RSKが解決した要件が、初期表示のスピードです。

初期表示も高速に – コード分割

SPAの弱点は初期表示が遅いことです。ユーザーがアクセスしてからの、SPAの一般的な動作順序は以下のとおりです。

  1. ユーザーがサイトにアクセスする。
  2. ブラウザに「Loading…」と表示される。
  3. HTMLのロード後、サイト内の全てのHTMLテンプレートを含んだサイズの大きいJavaScriptのロードを開始する。
  4. JavaScriptのロードと評価が終わる。スクリプトがURLを参照する。
  5. URLに従ったJSONをロードする。
  6. 取得したJSONとURLを組み合わせてHTMLを生成し、画面に描画する。
  7. ユーザーがコンテンツを閲覧する。

3.の時点で読み込むJavaScriptが、7.ではまだ不要な情報を含む点に注意してください。この設計には、サイトの画面数が増えれば増えるほど初期表示が遅くなるという欠点があります。初期表示の速度も重視されるフロントエンドにおいて、不要なJavaScriptのロードは課題でした。

RSKでは、Webpackのコード分割を使ってこの問題を解決しています。RSKは8つ画面を持っていますが、ビルドするとブラウザ用のJavaScriptを10個生成します。

サイトのトップ画面であるHomeにアクセスすると、その画面に必要な3つのJavaScriptのみを最初に読み込みます。別の画面のテンプレートなどを含まないので、効率的です。初期表示に必要なスクリプトだけを読み込む点は、2年、3年と開発を続けても変わりません。ユーザーがAbout画面に遷移するとき、クライアントサイドでのルーティングの直後に、初めてAboutのテンプレートをロードします。再びHomeに戻るときはテンプレートを既に持っているので、ユーザーは常に最小のリソースをロードしながら画面を遷移していくことができます。

ただクライアントサイドでレンダリングしている限りは、まだ最初の「Loading…」を消すことができません。

もっと初期表示を高速に – SSR

「Loading…」を無くし、コンテンツを素早く返却して表示したい理由はいくつかありますが、UXを担当している身としては、やはり冒頭にあげた表示遅延によるサイトからの離脱が気になります。

サーバが直接コンテンツを返却するには、さらなる設計が必要です。SPAの登場によりHTMLテンプレートをクライアントサイドに移したという元々の経緯があるので、今更同じHTMLテンプレート(と表示用にデータを変換する全ての関数)をサーバ側に再実装するわけにもいきません。

この要求を解決するのがSSRです。SSRは、ブラウザとサーバの両方で共通のロジックを使ってHTMLを生成する設計です。それが実現できれば良いので、例えばJavaで書かれたWebアプリケーションでもNashornを使ってSSRを実現することは可能です。ただ、表示に関するロジックは広範なことが多いので、サーバ側でNode.jsアプリケーションを動作させる優位性はあります。

RSKではNode.js + Universal Router + Reactを組み合わせて、これを実現しています。RSKは自身を(SPAと対比して)Isomorphic(同形的)であるとしていますIsomorphicな設計とは、ブラウザとサーバがJavaScriptによってソースコードを共有することで、フロントエンドのロジックを適切に抽象化する設計をいいます。RSKはこのIsomorphicな設計により、ルーティングとHTMLテンプレートを含む全てのロジックの共通化を実現しています。中でもルーティングが共通化されている点は美しく、各画面に必要なJavaScriptチャンクの決定も同一の箇所で管理されており無駄がありません。

サーバとブラウザが共通のルーティングルールに依存し、その取り扱いの差異のみを別ロジックにしている。

直接HTMLを返却すれば、ユーザーが初期表示を閲覧している間に、こっそりJavaScriptをロードできます。ロードが終わると、Reactは画面上のDOMをバリデーション無しで効率よく再利用します。このようにして、RSKは初期表示と画面操作の両方のスピードの要望を満たしています。

一方で、SSRは不要だとする考えもあります。実装を複雑にせずに要件を満たせるなら、それが一番です。例えばスタティックなページはそもそもSSRをする必要がありませんし、検索エンジンの結果から高速にページを表示する必要があるECサイトの商品ページなどは、SSRよりもAMP対応をすべきでしょう。LAN内で動作するWebツールならSSRが要件に数えられることはまず無いはずです。

Webは多様で、異なる文脈を混同して語られる機会がとても多いです。しかし、SSRにも速くてスケールするフレームワークが生まれてきています。特に、インタラクティブなWebアプリを作る場合に新規開発で使うWebフレームワークを探すなら、RSKのようなSSR設計を採用する価値は十分にあると思います。

React Routerではだめだったのか?

ここで少し話を変え、フロントエンドの要件の複雑さがツールを多様化してしまう事例を紹介したいと思います。Reactには元々、広く利用されているReact Routerがありますが、RSKの開発元であるKriasoftはUniversal Routerを自作して使っています。うまくモジュールを組み合わせているRSKが、わざわざルーティングライブラリを自作した理由は何だったのでしょうか。

React RouterはGitHubでスターが2.6万個以上付いている、Reactの非公式ルーティングライブラリです。「Declarative routing for React」と銘打って、JSXシンタックスで宣言的にルーティングを表現できます。登場した当初は作ったルーティングの見通しが良く、またNested Routesという機能がとても新鮮で、「新たなWebのスタンダードだ」と筆者の目には映ったものです。

render((
  <Router>
    <Route path="/" component={App}>
      <Route path="about" component={About} />
      <Route path="inbox" component={Inbox} />
    </Route>
  </Router>
), document.body)

https://github.com/ReactTraining/react-router/blob/v1.0.3/docs/Introduction.md#without-react-router

Nested Routesはある要件を解決します。インタラクティブなサイトで「戻る」ボタンを押すのを躊躇したことはないでしょうか。1画面の操作が長引くとき、ブラウザの履歴に画面の状態、つまりURLを登録するかどうかは、サイトのJavaScriptに依存します。「戻る」ボタンによって「1つ前のタブ」が開くか、画面全体が1つ前のものに戻ってしまうかは実装次第なので、途中までの操作が無駄になってしまうことがあるのです。また、操作が無駄になるようなサイトはたいてい「画面の右のタブが選択されたURL」を持っていないので、他の人にURLを渡して図示する際などに手間が生じてしまいます。React RouterのAPIは、フレームワークのように画面の詳細にまでURLを持つことを開発者に要求するので、上記の問題が起こりにくくなります。

あるタブが選択されたURLが存在しない場合がある。

しかしReact Routerには不都合な点もありました。React Routerは宣言的であるが故に、コード分割と相性が良くありませんでした。エントリーポイントの分割に別の工夫が必要なので、React Routerはv1時点からSSRに対応していたものの、実質的にSPAプロジェクト向きのツールでした。またバージョンアップを経て、コンテンツの状態はURLだけでなくデバイスのサイズによっても決定されることが分かり、要件が追加されました。さらにReact Nativeという、ブラウザとは本来無縁のランタイムでも動く要件も追加され、ソースコードはより抽象的になっています。いくつかの要件追加の末にv4がリリースされましたが、これには賛否両論あるようです。ルーティングルールがUIコンポーネントに混ざり込んだ「Dynamic Routing」は、確かに本来のコンセプトである「Declarative」と矛盾しているようにも見えます。

これに対し、Universal RouterはNested Routesをばっさり切り捨てて、よりシンプルなアプローチで作られています。Universal Router作者であるKonstantin Tarkus氏は次のように書いています。

In the majority of use cases I’m familiar with, there is no need in using nested routes as it’s done in RR. Using nested routes makes things more complicated than they should be and leads to an overly complex hariy routing implementation that is harder to maintain.

僕の知っているほとんどのケースでは、Nested Routesは必要ない。あるべき姿よりずっと複雑になってしまうし、メンテナンスできないくらいぐちゃぐちゃで複雑な実装になってしまう。

By the way, if someone knows why so many folks prefer using JSX for something not related to UI rendering, please leave a comment.

ていうかさ。なんでこんなに大勢の人が、UIと無縁のロジックをJSXで書きたがるんだ。誰か教えてくれよ。

引用元:『You might not need React Router』Konstantin Tarkus著

こうしてRSKのルーティングライブラリは自作されました。React Routerが解決する課題は多く、今後も広く利用されると思いますが、マーケティング的に成功したライブラリが貪欲に要件を飲み込んだ結果、姿を歪めてしまっている印象は否めません。要件の多いフロントエンドでは、単一のライブラリだけでは課題を完結できず、結果的にエコシステムを不安定にしてしまう例として、紹介しました。

ここまでで、スピードという要件と実装を紹介しました。フロントエンドに対する要望の多さが設計の見直しを要求する様子がお分かりいただけたでしょうか。次は、リッチな表現を実現するためにブラウザ実行環境の貧弱さを埋めるツール群を紹介します。

貧弱な実行環境でアプリケーションを動かすために

そもそもブラウザは、Google Spreadsheetsやオンラインゲームを作るプラットフォームとして開発されたものではありません。それにも関わらず、後方互換性を保ちながら機能を拡張するために、Webフロントエンドは知恵を絞ってきました。

2010年に書かれた『Closure: The Definitive Guide』は、JavaScriptの重要性が増していることを指摘するだけでなく、Node.jsの普及とIsomorphic JavaScript設計やそれにまつわる未来のツールの登場を言い当てているようです。

As web browsers improve and become available on more devices, more applications are being ported from desktop applications to web applications. With the introduction of HTML5, many of these applications will be able to work offline for the first time. In order to create a superior user experience, much of the logic that was previously done on the server will also have to be available on the client. Developers who have written their server logic in Java, Python, or Ruby will have to figure out how to port that server logic to JavaScript.(…)

ブラウザが改良されさまざまなデバイスで使えるようになり、多くのデスクトップアプリケーションがWebアプリケーションに移植されています。HTML5の導入によって、それらはいずれオフラインでも動作するようになるでしょう。より質の高いユーザー体験のため、元々サーバサイドでやっていた処理をクライアントサイドで行わなければならなくなるでしょう。Java、Python、Rubyでサーバロジックを書いていた開発者が、どうやってそれをJavaScriptに移植すれば良いか、学ばなくてはならなくなるでしょう。(…)

Because of the emerging support for offline web applications, it is compelling to write both the client and the server in the same programming language to avoid the perils associated with maintaining parallel implementations of the same logic. Because it is extremely unlikely that all of the major browser vendors will adopt widespread support for a new programming language, that will continue to force the client side of a web application to be written in JavaScript, which in turn will pressure developers to write their servers in JavaScript as well. This means that the size of the average JavaScript codebase is likely to increase dramatically in the coming years, so JavaScript developers will need better tools in order to manage this increased complexity.

オフラインWebアプリケーションをサポートすることを考えると、クライアントとサーバを同じ言語で書く必要性が出てくるのは必至でしょう。同じロジックの実装の二重管理を避けねばならないからです。主要なブラウザベンダーが新たなプログラミング言語をサポートすることはほぼ考えらず、クライアントサイドは今後もずっとJavaScriptで開発せねばなりません。同時に、そのことはサーバもJavaScriptで書くように要求するでしょう。これは今後数年でメンテナンスすべきJavaScriptが劇的に増大することを意味しています。JavaScript開発者たちには、この増大する複雑性を管理するより良いツールが必要です。

引用元:『Closure: The Definitive Guide: Google Tools to Add Power to Your JavaScript』Michael Bolin著

幾度もの世代交代の末に、この複雑性を管理するツールがRSKにまとめられました。次に、ツールが解決しなければならなかった課題を紹介します。

スコープの導入 – Webpack、NPM Modules、React Component、CSS Modules

まず妨げとなったのが、ブラウザ実行環境のグローバルスコープです。JavaScriptはもちろんのこと、CSSは特にメンテナンス性が低く、フロントエンドを悩ませ続けました。BEM(Block, Element and Modifier)などの命名規則でスコープをもたらす動向もありましたが、完全な導入は難しく、必要性と用法を理解していた開発者は多くなかったでしょう。

この点をWebpackのバンドル機能が解決します。もともとブラウザ向けライブラリのモジュール化はBowerが後押ししていましたが、Node.js向けモジュールとのすみ分けが面倒になり、npm + CommonJS(require())で依存管理されることになりました。バンドラーはBrowserifyがありましたが、依存解決すべきはJavaScriptだけではないことが分かり、Webpackが利用されることになりました。

UIコンポーネント(HTML + JavaScript + CSS)の単位でCSSが書けるようになったのも、Webpackのおかげです。これにより、CSSを直すたびにどこかが壊れる、ということは非常に少なくなりました。RSKは初期設定でこれを行えるようになっています。

低レベルなDOM APIとの決別 – Virtual DOM

そもそもブラウザの表示を変えるには、DOM(Document Object Model)APIを通して行いますが、複数の表示要素を効率的に書き換える方法がなく、パフォーマンスのボトルネックになりがちでした。必要なDOMだけを変更する高速なコードを書こうとすると、メンテナンス性が下がったり、状態がJavaScript上で管理し切れずDOMに散らばったりして、バグの温床になっていました。

これを解決するのが、Vue.jsやReactが実装しているVirtual DOMです。Virutal DOMは画面の状態をJavaScript上のオブジェクトだけで管理でき、確実に表示に反映できます。表示を変える時はデータを変更することで、ライブラリが必要な要素だけを効率的に操作します。開発者が直接DOMに触る機会が大幅に減り、問題が起こりにくくなりました。

壊れやすいJavaScriptの静的解析 – ESLint、stylelint、Flow、husky

JavaScriptという言語自体、そのままでは複雑なアプリを作るのには向きません。シンタックスが柔軟すぎて、複数人で開発をすると読めないコードを簡単に書けてしまうからです。

ESLintは細かな点までシンタックスを強制できるJavaScriptのリンターです。ESLintの設定項目は多岐に渡り、どの書き方を許容するか、しないかを細かく設定できます。しかしひとつひとつを調べつつ、ゼロから設定ファイルを作るのは骨の折れる作業です。RSKでは初期設定で程よいシンタックスを定義しており、かつ複数人での開発に耐えうるやや厳しめの設定になっています。また、同様のチェックをCSSに対して行うstylelintも設定済みです。

Flowは強力な静的解析ツールです。TypeScriptと同様に、ECMAScript標準に型情報を追加した構文です。問題が起こりそうな箇所から必要に応じて導入できます。元々は壊れやすいJavaScriptですが、Flowの型推論が広く導入されれば、サーバサイド開発の面でもRubyやPHPなどの言語に対して優位性を示すことができるでしょう。

husky多くのプロジェクトに使われているGit Hookとnpm scriptsを繋げるツールで、インストールすると.git/hooksにファイルを配置し、package.jsonで定義したリントを実行してくれます。RSKは初期状態で、lint-stagedとの連携により、ローカルでのコミット時にJavaScript、JSON、CSSなどのファイルに対してリントと自動修正を実行します。

多様な環境で動かすために

ブラウザ実行環境は多様です。かつてはInternet Explorerのバグと非効率性がブラウザの表現力を制限し、格差を生んでいました。現在では差異の種類が変化し、W3C、WHATWGとTC39が策定した新機能を、各ベンダーが競って導入することで、実装機能のばらつきが生まれています。普及したブラウザのバージョンを、フロントエンドエンジニアは制御できません。サポートするバージョンと切り捨てるバージョンを決めなければならないこと、その決定が開発に大きく影響することは今も変わっていません。

StatCounterを元にしたCan I Useのブラウザ利用統計は、現在でも多くのバージョンの利用が混在することを物語っています。これらのデータを元に、ターゲットのブラウザの制約を理解したうえで、意図したコンテンツをユーザーに届けることは、Webフロントエンドが達成すべき重要な要件のひとつです。

古い方でなく、新しい方に合わせて作る – Polyfill、Transform

しかし、開発者がJavaScript実行環境の制約に縛られながら、汎用的な機能をリリースすることはできません。策定済みの機能で、古いブラウザでは使えない関数などを、実行時に実装してしまうのがPolyfill、シンタックスを変換するのがTransformです。例えば、IE11はwindow.fetchを実装していません。fetchは柔軟なクロスオリジン共有指定やヘッダ指定が可能なXMLHttpRequestに変わるブラウザ環境の新しい非同期通信機能です。fetch関数はPolyfillとして制約付きで、XMLHttpRequestを使った実装があり、これを利用してIE11をサポートできます。またES2017で追加されたasync/awaitでさえも、PolyfillとTransformによりES5環境での実行が可能です。

ブラウザバージョンのばらつきが半永久的に収束しないことを考えると、Polyfill、Transformの存在がWebの表現の幅に与える影響は大きいと思います。またそれらのおかげで、次世代の標準策定プロセスは「忘れた頃に使えるようになる標準」ではなく「すぐにでも実現可能な標準」の策定で活気あるものになっています。

サポートポリシーからサポートバージョンを決定する – babel-preset-env

PolyfillやTransformは素晴らしい機構なのですが、プロジェクトごとにターゲットとする実行環境でサポートする機能をひとつひとつ調べていては大変です。特にWeb開発の場合、サービスごとに「各ブラウザの最新2バージョンをサポートする」などというポリシーを設定することが多いですが、リリース後にバージョンが変わるとサポートする機能も変わるので、メンテナンス上の問題が起きてしまいます。不必要にPolyfillやTransformを利用すると、JavaScriptファイルのサイズが増えるうえ、実行時に無駄な処理が多く入りスピードに影響を及ぼす点も気になります。

これを解決するのがbabel-preset-envです。これは、前述したCan I Useの統計Kangax ECMAScript compatibility tableの提供するデータを元に、browserlistの提供するDSLによって、トランスパイラであるBabelが必要なPolyfill、Transformを決定してくれる機構です。babel-preset-envはブラウザだけでなくNode.jsのバージョンごとの機能に合わせても変換できるので、無駄がありません。babel-preset-envの登場により、Can I Useを調べて必要なBabelプラグインを細かく探し回る作業からフロントエンドは解放されました。別個に存在するさまざまなフロントエンド支援サービスやツールが組み合わさり、Web開発環境の底上げをしている例だと思います。

多様な実行環境での動作を担保する – Jest、Enzyme、Browsersync

最後はテストです。テストの対象レイヤー(単体テスト、APIテスト、E2Eテスト)と粒度の決定、カバレッジの目標値の決定の難しさは、バックエンドにも共通するところかと思います。フロントエンドでは、そこに実行環境の多様性(ブラウザベンダー、ウィンドウサイズ、端末)と、そもそもの変更頻度の高さという要素が加わります。テストツール(とその選定)は、これらの要素を考慮した「限りあるリソースで、最も効果があるテストはこれ」という決定を支えるものでなければなりません。

Seleniumによる統合テストや、TestemやKarmaなど各種ブラウザの実際の実行環境で単体テストをする部分を、RSKのデフォルトでは切り捨てています。そのため、画面のスナップショットは撮れませんし、ウィンドウサイズによる表示の分岐などは自動でテストできず、手動テストに回されます。プロジェクト次第では、追加のテストツールの導入が考えられます。

RSKの初期設定としては、Node.jsでの単体テスト実行環境を提供しています。Jestは関数の単体テストの他に、UIコンポーネントが生成するHTMLをスナップショットとしてソースに保存しておくことで、生成されるHTMLの意図しない変化を検知する仕組みを提供します。ただHTMLの変更頻度は高いので、しょっちゅう差分がエラーとして報告されてしまい、スナップショットでの検知が向かないケースもあります。Jestはテストランナー + 記述の枠組みを提供するツールですが、このままではDOM APIが無いので、UIテストに期待される肝心な部分を検査できません。

Enzymeが、その点をカバーします。EnzymeはReactコンポーネントに対して、Node.js上で実行できるDOM API(のようなもの)を提供します。例えば「Aというデータを与えると、aというclassNameを持つ」といった不変な部分だけをチェックできます。さらに「ボタンをクリックするとXが表示される」といったユーザーインタラクションもテストできます。JSDOMと組み合わせれば、例えばcomponentDidMountでwindow.documentなどのブラウザAPIとやり取りする箇所も、問題なくNode.jsで実行できます。

Enzymeのおかげでブラウザを起動しなくともUIの自動テストがある程度書けるのは、テスト実行時間と手軽さの面で良い点です。その一方で、多様な端末やウィンドウサイズに対する、レイアウティングなどの表示のテストが抜け落ちてしまいます。この点をカバーするのがBrowsersyncです。

RSKでyarn startを実行するとすぐに、Browsersyncが有効の状態でブラウザが起動します。異なるサイズのウィンドウ、別のブラウザ、別の端末でアプリを表示してみると、WebSocketで操作が同期され、スクロールや画面遷移による画面変化を短時間で確認できます。UIは人の目で確認しないと気づけない領域が多いので、結果的にはこの方法が有効であるケースが多いのではないでしょうか。

結び

やや偏りのある内容になってしまいましたが、Webフロントエンドが満たすべき要件が多くあることがお分かりいただけたでしょうか。この記事が、要件の複雑さと、どの要件を採用しないのかを検討するきっかけになれば幸いです。この記事で触れた内容の他にも、RSKは、GraphQLなど、フロントエンド開発への要望や負荷が生み出した工夫が多く盛り込まれている興味深いプロジェクトです。よろしければ手元でyarn startを実行して、初期動作を確認してみてください。

明日の記事は、sugyanさんによる「詰将棋LINE Botを作りました」です。お楽しみに。

Related Post