モダンフロントエンドの進む道

# まえがき

本記事は進化を続けるフロントエンド界隈の思想を大まかに紹介するものです。
「自分で問題を作って自分で解決する自作自演を繰り返している」というような批判をたまにうけるフロントエンド界隈ですが、
それぞれの技術はもちろん何らかの課題を解決するために生まれてきたものであり、
それらの課題と解決策を知り、フロントエンドがどのような方向に進もうとしているのかを認識しておくことは
流行のReactやVue.jsを活用していく上でも地味ながらも確かに役に立つことだと思います。

## 対象読者

ReactやVue.jsなどを使って現在流行のフロントエンド開発を始める方で
各ライブラリのGetting Startedを読んでHello World程度まで済ませたぐらいの方を想定しています。

この記事では抽象的な思想を中心に扱います。
なのでこの記事を読んであとにリポジトリに即一行コードが増えるような実践的な記事ではありませんし、
すでにバシバシSPA開発している本職のフロントエンドエンジニアの方にはすでにおなじみの内容が中心となります。
どちらかというと初学者の方が、今後知識を深めていくにあたって「〇〇するには△△をしてはどうだろうか？」というような議論をする際の土台になれるような内容を目指しています。
また、具体的な技術詳細を追えるように[Google Web Fundamentals](https://developers.google.com/web/fundamentals/?hl=ja)に用意されているページへのリンクを適宜用意しています（余談ですが、なにか調べたいときはまずWeb FundamentalsもしくはMDNを調べるのが鉄則です）

※ ポエムタグをつけているように、筆者の私見が混ざっている箇所が多いですのでそれが不快な方はスルーすることをおすすめします

## モダンフロントエンドとは何か

まずモダンフロントエンドの定義とは何でしょうか？
VueやReactを使ってSPAを使いWebpackでバンドルをすればモダン、というのはちょっと個別の技術によりかかりすぎているような気がします
この記事では思い切って **Webの進化する方向に寄り添い続ける開発そのもの**　をモダンフロントエンドと呼びたいと思います。
そしてそのモダンフロントエンドはなんのために生まれて何を解決しようとしているのかを説明していきます。

# モダンフロントエンドの技術が解決する課題

フロントエンド領域に限らず技術発展は主に2つの側面を持っています
１つはUI/UX(=User Experience)です。その技術を活用することでエンドユーザーの体験をいかに向上させられるかが焦点になります。
もう1つはDX(=Developer Experience)です。その技術を活用することで開発者の生産性があがり、バグが減り、運用負荷が下がる・・・といった点が焦点になります。
この2つの観点からフロントエンドの技術を整理してみようと思います。

##　フロントエンドにおけるUXでの課題

まずはユーザー体験の向上に関するものを考えます。
大きなテーマを個別に見ていきます。

### ページスピードの高速化

長年のWebサイトの規模の拡大により、サイトのサイズは大きく、ページは重くなっていきました。この点はつねにWebの課題でした。
Webの体験の質とはページスピードによって大きく決定するといっても過言ではないでしょう
[dev.to](https://dev.to)の登場が一部の界隈で大きな衝撃とともに受け入れられたのは記憶に新しいです。
特にGoogleなどの検索エンジンからの流入をターゲットとするサイトではページスピードは死活問題にもなりえます。
スピードのチューニングとしておおまかな要素を上げると以下のようなものがあります

- サーバーサイドロジックのチューニング
    - SQLチューニングなどのI/O最適化
        - (実際にはここが最重要だったりすることは多い。)
- 配信経路の最適化
    - AkamaiやImgIXなどのCDNから画像や静的アセット(.jsや.css)を配信する
    - ブラウザのキャッシュコントロールを丁寧に行い、余計なネットワークアクセスを抑える [^1]
    - Service Workerを利用して、資材そのものをローカルにキャッシュする [^2]
- 資材のロードタイミングのコントロール
    - `<script async>`や `<script defer>`を利用して、JSの評価を遅延させる [^3]
    - `<link rel="preload">`などを用いた資材配信タイミングの最適化 [^4]
    - PRPLパターンのような設計の導入 [^5]
- 資材そのものの最適化
    - JS/CSSのminify
    - WebpackのSplitChunkなどを利用して、サイト全体の共通アセットだけを切り出す [^9]
    - ESModulesを利用した配信ファイルの最適化 [^6]
- 各種制約の中で開発する代わりにベストプラクティスを提供するフレームワークの活用
    - AMP
- JSで描画されるHTMLをサーバー側で前もって描画するSSR( Server Side Rendering)の実施

しかし、これらは闇雲に対応すればいいというものではなく自分のサイトのどこがスピードのボトルネックになっているかをまずきっちり測定してあげる必要があります
Chrome dev tools[^7]やLighthouse[^8]はまさにそのために存在していて、
ページスピードチューニングはChrome dev toolsとにらめっこする作業といっても過言ではありません
SpeedCurve[^10]やCalibre[^11]のように特定のサイトのパフォーマンスを継続して収集し続けてメトリクスをとれるようなSaaSもあります

ページスピード改善は非常に奥が深いテーマですので本格的に改善を始める際は以下のような本を手元に置くことをおすすめします

- [Webフロントエンド ハイパフォーマンス チューニング ](https://www.amazon.co.jp/dp/4774189677)
- [超速! Webページ速度改善ガイド ](https://www.amazon.co.jp//dp/477419400X)

### 定型的なアクションの簡易化

多くのページはログインや決済などの定型的な機能によって構成されているものの、その実装やインターフェイスはバラバラです。
ユーザーは本質的に同じ操作をするために、違うWebサイトを訪問するたびに悩んだり手間のかかる入力を繰り返してきました。
それを解決するための手段として以前からautocompleteによる補完[^14]はありましたが、
そもそも定型的な機能をブラウザで提供してしまえばいいのではないか、という思想が登場してきました。
ブラウザ側の機能として持つことで、ユーザーが「使い慣れた」「結果の予測できる」インターフェイスで「登録済みのデータ」を、「サイトを問わず」に提供することができます。
すでに「Login with 〇〇(サービス名)」でログインしたりアカウントを作成できるサービスは珍しくないですが、その機能をブラウザにそもそも搭載してしまえばいいのではないかという発想です。

直近で最もホットなものとしてはカートや決済などのインターフェイスを統一できるPayment Request API[^12]が大きい変化ではないでしょうか。
Paryment Request APIを使ってサイトを実装することで、ユーザーは初めて訪れたサイトでも、氏名やカード情報などを1から入力しなおすことなく以前と同じようなフォームから商品の購入が可能になります。

(余談)
ただし、ブラウザをプラットフォームにして、サイトを超えて共通の体験を提供したいという思想はセキュリティや個人情報、独占などの問題といつも近いところにあります。
最近ではchromeがgoogleアカウントでのログインを強制して大きな批判を浴びました。[^13]
が、ブラウザの役割を充実(少なくともネイティブアプリ並みかそれ以上に)させていこうという流れはしばらく変わることはないだろうと思います。

## フロントエンドにおけるDXでの課題

###　クロスブラウザ対応

フロントエンドの特殊性の1つはその実行環境の多様性にあります。
現在でも使用されるHTML/CSS/JavaScriptは少なくとも20年前には誕生していましたし、それを実行するブラウザの数は大量に存在します。
そして、ブラウザの仕様や実装はバラバラでときには特定のブラウザの特定のバージョンに対処するためだけのhackを本番環境のコードに仕込む必要があります。
それらの個別のhackやベンダープレフィックスをエンジニアやデザイナが完全に認識してワークアラウンドを提供することは難しく、それを補うためのツールが発展してきました。
要は、**細かいこと気にせずに最新の便利なコードを書いて楽をしたい、予期せぬバグを減らしたい**ということです。

ブラウザの差異吸収は、かつてはjQueryの大きな役割のひとつでしたがモダンフロントエンドの文脈ではブラウザにpolyfill[^17]をあてることでそれを実現しようとします。
具体的には以下のようなことを実施します

- 対応ブラウザの基準を揃える
    - browsesrlist[^20]で対応ブラウザの範囲をコードで管理する
- CSSのクロスブラウザ対応
    - autoprefixer[^15]によるベンダープレフィックスの自動付与
    - 将来的にCSSの仕様として検討されているコードを先行して使うためのpostcss-preset-env[^16]
- JSのクロスブラウザ対応
    - babel-preset-env[^19]で必要なpolyfillを実行する
- そもそものサイトの設計をグレイスフルデグラデーションやプログレッシブエンハンスメント[^18]の考え方で作る

一般的にこれらのプロセスはgulpなどのタスクランナーやWebpackなどのバンドラーを通じて適用します。

(余談)
polyfillにも限界はあり、例えばVueの次期バージョンである3.0ではProxyというJavaScriptの新機能のpolyfillがIE11では作れないために、後方互換用のビルドを用意する計画をしています(当然互換ビルドのほうが機能が落ちる)

###　JS/CSSの記述力の強化

上述の通り、JS/CSSは20年以上前に誕生し、少しずつ拡張を加えていくことで現在まで使われ続けています。
しかしW3C[^21]やWHATWG[^22] 、TC39で新たな仕様が決定されたところでそれがブラウザに実装されるまでは時間がかかり、
また古いブラウザに対応するために結局クロスブラウザ対応が必要になります。
それであれば、クロスブラウザ対応を実現する仕組みを使って、将来のブラウザに搭載される予定の機能を先に使ってもいいんじゃないか、という話になります。
babel[^24]は「6to5」という名前で登場しました。まさに最新のJavaScriptの文法(ES6)で書かれたコードを古いブラウザ互換の形式(ES5)で出力する機能を提供します。
babelがJS(latest) -> JS(legacy)の変換をするのに対して、出力がJS(legacy)であれば、入力はなんでもいいのではないかという考え方も登場しました。
それらは少し前はAltJSとカテゴライズされ、複数の「JSを出力する言語」が登場しました。
数年前はCoffeeScriptが人気を集めましたが、現在のその代表格はTypeScript[^25]です。JavaScriptのスーパーセットとして登場したTypeScriptは静的な型付けとモダンな文法で多くの人気を集めています。その他の競合だったAltJSは現在かなり支持を失ってしまったといっていいと思います。
(少し毛色の違うAltJSとしてElm[^26]などは一定の強い支持を得ています)
CSSに関していえば、LessやSass[^27]などのCSSプリプロセッサ(AltCSS)が登場し、貧弱なCSSの記述力を補ってきました。
その一方でJSにおけるBabelのように、「将来正式なCSSとして採用される予定の機能を使う」ことが重要だと考える人達はpostcss/cssnext[^28](現在はpostcss-preset-envに移行)を利用しています。
現時点ではSassのシェアが最も高いのではないかと思いますが、postcssはそのシェアを伸ばしていますので今後どうなるかはまだわかりません。
postcss-preset-envはCSSの正式な流れを先取りするだけなのでライブラリが陳腐化しにくいというメリットは持っています。


### よりスケーラブルなWebアプリケーションの構築

スケーラブルという単語が具体的に何を意味するかは議論の余地がありますが、
少なくともモダンフロントエンドが目指しているのは、Webアプリを個別の閉じた部品に分解して管理することを可能にする世界です。

今までのWebの世界ではJSもCSSもグローバルな名前空間を容易に汚染でき、逆にグローバルな名前空間を活用してコードを描くのが当たり前でした。
その世界ではコードの修正の影響範囲がどこに及ぶのかわからず、HTML上でロードしたcssやjsファイルのコードが予期せぬ干渉をおこしたり、暗黙の依存関係を生み、メンテナンスの困難な世界ができることは珍しくありませんでした。

####  JavaScriptのモジュール化

こうした状況を解決するために、モダンフロントエンドではNode.jsの力を借りてjavascriptのモジュール化を実現しました。
モジュール化することにより依存関係はより明瞭になり、グローバル汚染をさけることが可能になりました。
モジュール化されたjavascriptはそのままではブラウザが解釈・実行できないので、
エンジニアの書いたコードは、リリースする前にwebpack[^30]やbrowserifyなどのモジュールバンドラーによって依存関係を解決し、
ブラウザが実行できる形になったjsファイルとしてデプロイを行います。
またNode.jsの力を借りることで、パッケージマネージャであるnpm/yarn[^31]が使用可能になり、ライブラリの管理が飛躍的に楽になりました。
外部ライブラリを使う際にはHTMLファイルに直接`<script>` を書いて読み込むのではなく、
package.jsonに記述して、モジュールとして読み込むようになりました。

将来的にはESModules[^32]の機能を利用して、ブラウザ上でも依存関係の解決ができるようになりモジュールバンドラーが不要な未来が来る可能性はありますが、現時点では対応ブラウザも少なく課題も多いため当面モジュールバンドラーの役割はなくならないことが予想されます

#### Web Components[^36]の世界

JavaScriptのモジュール化は可能になりましたが、JavaScriptはWebの構成技術の1つでしかありません。
そこで、HTML/CSS/JSをまとめてモジュール化するための思想が今後の主流の流れになろうとしています。
React[^33]やVue.js[^34]、polymer[^35]といったUIライブラリはそれを可能にします。
(ReactやVueはWebComponentsを実現するためのライブラリ**ではない**ですが、思想の流れとしては似ている点が多いのでここに書いてます)

これらのライブラリは誤解を恐れずに言うと、**HTMLを独自に拡張できる**(もしくは擬似的に可能にする)技術です。
既存のHTMLタグを組み合わせて、それにJSで振る舞いを与え、CSSでスタイルを当てたものを1つのHTMLタグとして再利用することができます。
例えば多くのサイトでdatepickerが独自に実装されていますが、`<date-picker>`というタグを書くだけでそれが実装できる世界です。
そしてその`<date-picker>`のスタイルや振る舞いはその周辺のHTMLやJSに一切影響を与えません（カプセル化）。

カプセル化のイメージはVueのSFC(Single File Component)[^37]のコードを見るのがイメージが湧きやすいです。
このようにHTML/JS/CSSを一箇所に固めてカプセル化を実現しています。
このカプセル化の実現方法は各ライブラリによって様々ですが、WebComponentsではShadowDOM[^39]によって実現されています。
Vue.jsやReactではShadowDOMを使用していないので擬似的なカプセル化になっていますが、それは
CSS Modules[^40]やCSSinJS[^41]、Styled Component[^42]によって実現されています。

（余談）
CSSのattributeそのものを拡張する技術としてはHoudini[^43]があります

#### Web Assembly[^38]の世界

WebAssembly(WASM)はWebアプリ上でネイティブコードを実行するための仕組みです。
Webアプリが担う領域が広がるにつれ、今までネイティブ環境でなければ需要を満たせなかったゲームや画像編集などのヘビーな処理を実行するために開発されています。
それらの処理をブラウザ上で実行することで、クロスプラットフォームを実現し、HTML/CSSという枯れたUI技術が活用できるという大きなメリットを持っています
先日開催されたChrome dev summitで公開されたSquooshなどのサイトを見るとなんとなくやりたいことが見えるかもしれません

## まとめ

風呂敷を広げすぎて畳みきれず長くなってしまった割に、あれもないこれもないと自分の実力不足が感じられる記事にはなってしまったのですが、
ここまでをかいつまでまとめると、モダンフロントエンドは

- ユーザーにとってはネイティブアプリのように高速で使いやすい
- エンジニアにとっては細かい悩みから開放され大胆にコードを書いていける

ように進化しています。
エンジニアやデザイナにとっては考えることが増えた分、最初の一歩が大変になってしまっていることは否定のしようがありませんが、
逆にその最初の一歩を踏み込んでしまえばとても楽しい世界だと思っています。

長文にお付き合いいただいてありがとうございました。

## 脚注

[^1]: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=ja
[^2]: https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker
[^3]: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/loading-third-party-javascript/#use_async_or_defer
[^4]: https://developers.google.com/web/updates/2016/03/link-rel-preload?hl=ja
[^5]: https://developers.google.com/web/fundamentals/performance/prpl-pattern/?hl=ja
[^6]: https://developers.google.com/web/fundamentals/primers/modules
[^7]: https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/?hl=ja
[^8]: https://developers.google.com/web/tools/chrome-devtools/speed/get-started?hl=ja
[^9]: https://qiita.com/soarflat/items/1b5aa7163c087a91877d
[^10]: https://speedcurve.com/
[^11]: https://calibreapp.com/
[^12]: https://developers.google.com/web/updates/2016/07/payment-request?hl=ja
[^13]: http://www.itmedia.co.jp/news/articles/1809/26/news132.html
[^14]: https://developers.google.com/web/fundamentals/design-and-ux/input/forms/?hl=ja#name_autocomplete
[^15]: https://github.com/postcss/autoprefixer
[^16]: https://github.com/csstools/postcss-preset-env
[^17]: https://developer.mozilla.org/ja/docs/Glossary/Polyfill
[^18]: https://developer.mozilla.org/ja/docs/Glossary/Progressive_Enhancement
[^19]: https://github.com/babel/babel-preset-env
[^20]: https://github.com/browserslist/browserslist
[^21]: https://www.w3.org/
[^22]: https://whatwg.org/
[^23]: https://github.com/tc39
[^24]: https://babeljs.io/
[^25]: https://www.typescriptlang.org/
[^26]: https://elm-lang.org/
[^27]: https://sass-lang.com/
[^28]: https://github.com/MoOx/postcss-cssnext
[^29]: https://qiita.com/unecochan/items/ac206365745257d68926#ie11-%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88--ie11-support
[^30]: https://webpack.js.org/
[^31]: https://yarnpkg.com/lang/ja/
[^32]: https://developers.google.com/web/fundamentals/primers/modules
[^33]: https://reactjs.org/
[^34]: https://jp.vuejs.org/index.html
[^35]: https://www.polymer-project.org/
[^36]: https://qiita.com/jtakiguchi/items/b1315f53b3726ff11b61
[^37]: https://jp.vuejs.org/v2/guide/single-file-components.html
[^38]: https://dev.to/nabbisen/webassembly--3385
[^39]: https://www.html5rocks.com/ja/tutorials/webcomponents/shadowdom-201/
[^40]: https://github.com/css-modules/css-modules
[^41]: https://github.com/MicheleBertoli/css-in-js
[^42]: https://github.com/styled-components/styled-components
[^43]: https://developers.google.com/web/updates/2016/05/houdini?hl=ja
[^44]: https://squoosh.app/