役割から紐解く!フロントエンド開発を支えるツール
フロントエンド開発の全体像を理解する
Soudai Sasada
昨今のモダンなWebフロントエンドアプリケーションを開発するにあたり切っても切れない関係にあるのがビルドツールです。が、フロントエンドはその巨大なエコシステムによる活発な新陳代謝により技術的なトレンドの移り替わりが非常に速く様々なツールが次々と現れては消えるため日常的にフロントエンドの開発をしているわけではない筆者にとってはキャッチアップしていくのは大変です。
といってもツールが変わって大変なのは(現在進行形で開発しているか否かでその差は大小あれど)みんな同じでそれでも新しいものが選ばれるのはそれ相応の理由があります。当たり前ですね。
ガッツリとリソースを確保してフロントエンド開発をするのがなかなか難しいという前提でそれでもついていくにはどうすれば良いか考えましたがビルドに関連するツールを調べるよりもそれらが必要とされる背景にどのような課題がありどういった役割を求められているかを紐解きそれによりまずは理解を深めることが先決ではないかと結論付けました。
そうすることでこれから新しいツールが出てきたときに素早くキャッチアップできるようになるのではないかという思惑ですね。
また、一度自転車に乗れるようになると乗れなかった頃の感覚を忘れてしまうように前提となる知識が乏しい状況は知識を得た後に再現することが難しく知識を得る前の「なにが分からないか」といった状況は作れないのではないかと思います。せっかくそうした貴重な感覚を持ち合わせている状況ですのでまだちゃんと学習できていない状態で記事にすることが自身の知識の体系化のみならずこれから学ぼうと思っている方のお役にも立てるのではないかと思い記事としてまとめることにしました。
なおツール間の比較であったり特定のツールの具体的な使い方はこの記事では言及しません。そうした情報を求めている方はそっ閉じ推奨です。
ツールを役割から整理してみる
では早速ですが本題に入りたいと思います。ただツールと一口に言っても様々なものがありますのでそれぞれがどういった問題を解決するものなのかという視点で整理してみようと思います。
ソースコードをチェックする
まずはソースコードのチェックをする Linter の紹介です。 Linter を使うとエディタと連携しソースコードが構文エラーを起こしていないかチェックしたりユーザーが独自に設定した記述ルールに違反していないかをチェックすることができます。
(後述の他のツールもいくつかはそうですが) Linter は JavaScript 特有のものではないため普段あまりフロントエンドを触らない方でも馴染みがあるんじゃないでしょうか。
なお Linter と似たような役割を持つものとして Language Server Protocol (LSP)というものもあります。こちらは言語とエディタの間で通信するためのプロトコルで言語が LSP に対応していればエディタはリアルタイムに言語に組み込まれた Language Server を呼び出し構文のチェックであったりコードの補完を行うことができます(筆者は Vim を使うときには ALE というプラグインを使っていますがなかなか便利です)。 LSP についてはこちらの記事がとても詳しく勉強になりました。
ソースコードを整形する
Linter はソースコードのチェックをし警告してくれますが修正まではしてくれません。これに対しソースコードをチェックし修正(整形 / Format)までしてくれるツールのことを Formatter と呼びます。
Formatter のよくある使い方としてはエディタの設定でファイルの保存時に走らせたり、 Git の hooks と組み合わせソースコードをコミットする前に走らせることでソースコードがバージョン管理システム上に記録(コミット)される前に整形した状態にできます。コミット前に整形しないとそのあとの修正に過去のコードの整形と新しい修正が混ざってしまい変更を追いづらくなるので、手を入れた部分をコミットする前に矯正してあると助かりますね。
余談ですが Git の hooks を簡便にするためのツールもあります。たとえば JavaScript だと huskey があり、これを使えばコードのチェックや整形に限らず Git の hooks に任意のタスクを組み合わせることを容易に実現できます。もちろん使わなくても hooks は使えますが管理や設定が複雑だとミスにつながるのでこうしたツールを使うことで楽になり結果的にミスの軽減に繋がったり設定するための時間を削減できるのであればそれはそれで良いですね :)
新しい機能を使えるようにする
JavaScript はブラウザ上で実行されますがブラウザの種類やバージョンによってサポートされている機能が違い各ブラウザ / バージョンごとの対応状況はMDN Web DocsやCan I Useでチェックできます。
サポート対象としているブラウザ全てで使いたい機能がサポートされていればその機能を使うために特別なことは必要ないですがそうではない場合に未対応のブラウザでも特定の機能を使えるようにするために Polyfill を使います。 Polyfill は新しい機能が実装されていないブラウザ上でも同じ振る舞いを実現するための関数やライブラリです。自身が使いたい機能がサポート対象としているブラウザで提供されていない場合に都度入れたり、後述の Transpiler を core-js のようなライブラリを組み合わせてまとめて入れます。
新しい構文を使えるようにする
JavaScript の仕様である ECMAScript では古い言語仕様を刷新し開発者にとって便利な機能を提供できるよう未来に向けた仕様策定が活発に行われており、こうした機能や構文も含めた新しい仕様を ESNext といったり、対して古くからあり現代の主要ブラウザであればどれもすでに実装済みのような古い仕様を ES5 といったりします(厳密には ES5 は ECMAScript の第5版で今ある「当たり前」な仕様がこのバージョンで整いました)。 ES5 の次のバージョンである ES6 は2015年に公開され以後は毎年メジャーバージョンを更新されることとなったため2016年には ES7 、2017年には ES8 と続いています。
ES6 以降に実装されている機能や構文はどれも非常に便利ですがブラウザ側が対応していないと動きません。一方でそうした新しい構文が開発時の生産性に主眼を置くものであればブラウザ上で動く際には必要なく古い構文でも問題ないわけです。であればブラウザで動かすときに変換してしまえば良いのではないかということでそれを実現するための Transpiler というツールが開発されました。
これは端的に言えばある言語を別の言語に書き換えてしまうツールでこれを使えば ES6 以降の JavaScript や TypeScript など別の言語を ES5 へ変換することができるため開発時に静的型付けや ES6 以降のモダンで便利な構文を使うことができ生産性を上げることが可能となります。
なお本来はサポートされていない構文や言語で開発しコードを動くように変換することで動かそうというアプローチは JavaScript だけに限った話ではありません。たとえば CSS では CSS Preprocessor というものを使うことで CSS とは別の便利な機能が提供された言語(SCSS / Sass)を使うことができたりベンダープレフィックス(と呼ばれる特定ブラウザで新しい機能を解釈させるためのプレフィックス)をコンパイル時に自動でつけることができます。 HTML で言えば Pug のようなテンプレートエンジンや JSX のような拡張言語がありますね。また、別言語を使って実装するというアプローチでいえば大きな括りでは ReactNative や Flutter でネイティブアプリを開発するのも同じ分類と言えるかもしれません。
対応ブラウザを設定する
古いブラウザをサポートするといってもどこまで古いブラウザをサポートすれば良いのでしょうか?その判断はユーザーの利用状況や提供しているサービスのポリシーによって違うかと思います。
直接利用するビルドツールというわけではないですが、この「どのブラウザをサポートするか」をサポートするためのツールもあり、これによりルールベースでサポート対象ブラウザを開発者が定義することができ、各種ツールはこの定義を元にトランスパイル時に参照したりといったことができます。筆者は Browserslist しか知らないのですが Browserslist では Google Analytics のレポートをもとに実際の利用ユーザーのシェアからサポート対象を決めるといったこともできるようです。「直接利用するビルドツールではない」としたのは Browserslist は Babel などのトランスパイラを介して(トランスパイル時の内部処理で)利用されるためです。
依存関係を管理する
JavaScript には主にサーバサイドでの利用を想定した Node.js という実装がありますが最近の JavaScript では Node.js で提供されているライブラリをブラウザ上でも使うことが多いです。背景にはNextやNuxtといったサーバサイドレンダリングをサポートするフレームワークの台頭のほか、ランタイムを問わず純粋に JavaScript として便利な機能を提供するものも多くあるためではないかと思います。こうした Node.js 製のライブラリを使いその依存関係を管理するためのツールが Package Manager です。
Package Manager を使えば依存しているライブラリやそれらがさらに依存しているライブラリを辿りどこでどのバージョンのものが使われるべきかといったことを管理します。これにより複数のライブラリがある特定のライブラリに依存している場合に重複してしまうような無駄を省くことができ、また、それがどのように行われたかを記録し再度依存関係を解決する際にその記録を元に再現することができるため、異なる環境でも同じ依存関係で動くことを保証してくれます。
また、 yarn というパッケージマネージャーでは依存関係の中でのバージョン指定を無視して「ある特定のライブラリを使用する場合は強制的に特定のバージョンで依存を解決する」といったこともできます。これはたとえばあるライブラリに脆弱性が見つかったような場合にそれが依存関係の中でも深い階層に位置するためにアップデートをすることができないといった状況で役に立ちます。
このあたりの実際の設定方法、またそれにより生じるリスクについては別の記事にまとめてみましたので、もしご興味がございましたらぜひご覧になってみてください :)
様々な処理を管理する
これまで紹介したような様々な処理を誰がいつどのように行うのでしょうか?
もちろん個々のツールを呼び出して自身で生成物を配置したりといったこともできますがこれだけツールが多いと現実的ではありません。
こうした処理を行えるものとして Task Runner というものがあります。 Task Runner ではこれまで紹介したような処理を組み合わせることができ、たとえばファイルの変更をリアルタイムに反映しブラウザをリロードする(いわゆるホットリロード)といったことも可能です。
生成物を最適化する
フロントエンドをサーバサイドと比較した際の大きな違いの一つにユーザーの利用環境があります。サーバであれば OS を統一したりCPUやメモリといったハードウェア環境を揃えることはそんなに難しくはないですがフロントエンドではそうはいきません。
ユーザーのネットワーク環境は不安定で、必ずしも潤沢なリソースがあるわけではなく、ブラウザ以外にも様々な処理が常に走っており限られたリソースを奪い合っています。
このような環境でサイトに訪れてくれたユーザーへ少しでも良い体験を提供するには生成物をより小さく(ミニファイ)しダウンロードに掛かる時間を短くしたり、ファイルをなるべくまとめる(バンドル)ことでブラウザがリクエストする回数を減らす必要が出てきます。こうした処理を行うものが Bundler です。
Bundler は本番用の生成物をミニファイしバンドルしてくれますが開発をサポートするためのホットリロード機能も提供されている場合が多くこれまで紹介してきた処理をプラグイン等で組み合わせることができるようになっているため開発するにあたり欠かせないツールとなっています。
最近では使われていないコードを削除したり(ツリーシェイキング)開発時にはあえてバンドルをせずにソース変更時のホットリロードを高速化したりと特徴的な機能を持つ新たなツールが登場しており盛んに改善が行われている領域です。
まとめ
ここまで出た役割とそれを解決するツールをマッピングしてみます。プラグイン機構を採用しているツールではここにマッピングした以外の役割をこなせるツールもあるためこの表は参考程度に留め詳しくはそれぞれがなにをできるのか調べることをお奨めします。
主な役割 | ツール |
---|---|
ソースコードのチェック | ESLint, LSP |
ソースコードの整形 | Prettier |
新しい機能や別言語で実装し変換 | babel, tsc |
依存関係の管理 | npm, yarn, pnpm |
タスクの管理 | Gulp, Grunt |
コードを本番用に最適化 | Browserify, webpack, Parcel, Rollup, Vite, Snowpack |
役割、ツール共にここに挙げたものが全てではなく他にもあると思います。新たな役割が必要になったり今あるツールとは別のアプローチでこれまで登場してきた問題を解決するツールが出てくるかもしれません。
たとえばこの記事で紹介した全ての問題をたった一つで解決するという目的を持った Rome というツールが開発されています。執筆時点では Node.js で書かれていたが様々な理由から Rust で書き直しているとのことでまだ正常に動くものはありませんがこれまで登場してきたような複数のツールをまとめ良い感じに設定するのはそれなりにコストも掛かるので個人的には期待しています。
全ての道はローマに通じるのでしょうか。ローマは一日にして成らずとも言いますので首を長くして待ちつつ新たに登場するビルドツールとそれが解決する課題はなんなのかを引き続きウォッチしていこうと思います。