Qiita株式会社のカレンダーの2日目は @kyntk が担当します。
今年はIEのサポート終了がありましたね。
今まではIEのサポートをするためにJavaScriptやCSSで使えない記法があったり、ポリフィルを入れていたりしていましたが、モダンブラウザだけを対応するようになるとこれらの設定が不要になりました。
ES2015以前の環境をサポートするために@babel/preset-envがよく使われます。
今回は、preset-envの設定を変更するにあたって、うっかりサポート対象環境で動かないバグを生んだり、逆にあまり無駄なコードを削れなかったりしないように、内部でどのような処理が行われているのか調べてみました。実際にbabelのソースコードを読みながらまとめています。
調査時点のbabelのバージョンは「7.20.6」です
分かったこと
はじめに結論ですが、Babelを使ってJavaScriptがどのように変換されるかを知るためにこのあたりが重要になりそうです。
- まずは自分のアプリケーションがサポートするターゲット環境を決める or 把握する
- browserslistを使っている場合、
npx browserslist
などで一覧を取得できます - ブラウザのstatsデータをアップデートするためにもcaniuse-liteのDBも更新する
- browserslistを使っている場合、
- すべてのターゲットをサポートするようにpluginが追加されるので、どのプラグインが追加されるかを把握する
- リストはこのあたりで管理している (これだけ見ても分からない...)
- https://github.com/babel/babel/tree/v7.20.6/packages/babel-compat-data/data
- core-jsのポリフィルの読み込みもターゲットを参照していて、
useBuiltIns
で設定ができる
前提: Babelの役割
ドキュメントには以下のようにあります。
- Transform syntax
- Polyfill features that are missing in your target environment (through a third-party polyfill such as core-js)
- Source code transformations (codemods)
- And more! (check out these videos for inspiration)
BabelはJavaScriptを別のJavaScriptに変換をしたり、ポリフィルを追加したりします。
JavaScriptを変換する理由としては、実行時には各ブラウザの環境で動作するコードにしたいものの、実装自体は別のモダンな書き方を使いたいといったものが挙げられます。
一方ポリフィルは、新しい記法を古い記法に変換するのではなく、新しい記法のまま使えるようにJavaScriptの関数の定義などをしているものです。
これを行うために様々なpluginが用意されており、必要なpluginを選択することでどのように変換するかを決めることができます。
pluginについて
pluginのリストはこちらです。
すべてのpluginがコードの変換をするというわけではなく、JavaScriptのparseをするためのpluginなどもあるようです。
また個人的なイメージとして、今まではBabelは「ES2015に対応していない環境のためのツール」という印象を持っていました。しかし、TC39 Proposalsのpluginもあり、モダンブラウザにもまだ実装されていないものを先取りして使うこともできるようです。
presetについて
presetはpluginのセットです。例えばpreset-envを使うと、pluginを1つ1つ選択しなくてもES2015未対応の環境でも動作するコードにすることができます。
また、core-jsのようなBabel以外のポリフィルを、必要なものだけを選択して追加できる設定もあります。
presetだけでなくpluginも同時に設定することができ、その場合はpluginのほうが先に実行されます。
@babel/preset-env について
preset-envは、ターゲットブラウザの設定をすることで、その環境で動作するように必要なプラグインとポリフィルを選択してくれます。
例えばtargetsの中に「IE 11」が入っていると、IE 11で動作するJavaScriptを生成することができます。
targetsの設定方法
babelの設定としてtargetsがありますが、targetsを直接指定せずに、.browserslistrc
を置いておくと、そちらを参照します。
babel-helper-compilation-targets
targetsには> 0.25%
のような記述もすることができますが、具体的にどのブラウザのどのバージョンをサポートするかをbabel-helper-compilation-targets
で取得しています。
browserslistから対応しているバージョンを取得して、各ブラウザその一番低いバージョン1つをピックアップしたリストが作られます。
ブラウザごとのstatsデータなどはcaniuse-liteのdbを参照しているので、これをアップデートしておかないと最新ブラウザなどが反映されないので注意しましょう。
pluginの選択
targetsのリストができたので、必要なpluginを選択します。
このファイルには、各pluginごとにその機能が実装されているブラウザの最低バージョンが記載されています。
targetとなるブラウザのうち1つでもこのバージョンを下回っているときにはpluginとして選択されます。
この処理自体はbabel-helper-compilation-targets
で行っているようです。
bugfixesについて
たとえば、特定のブラウザの実装にバグがあったときでも、その記法が機能するようにコードの変換をするのですが、その際、元のコードよりもかなり多くのコードが生成されることがありました。
それも、バグがあるブラウザだけでなく、バグのないブラウザでも読み込まれるファイルサイズが大きくなることに繋がります。
これを防ぐために、bugfixes: true
の場合はできる限りモダンで壊れないシンタックスに変換するようにします。
具体例はこちらにまとまっています。
ポリフィルの選択
pluginの他に、core-jsによるポリフィルの読み込み設定ができます。targetsに合わせて必要なポリフィルだけを読み込むコードに書き換えてくれます。
例えば、useBuiltIns: 'entry'
にすると、
import "core-js";
targetsに合わせて以下のように必要なものだけをimportするように変換してくれます。
これにより1つ1つ管理しなくても、不要なポリフィルを読み込まずに済み、バンドルサイズが大きくなるのを防ぎます。
import "core-js/modules/es6.array.copy-within.js";
import "core-js/modules/es6.array.fill.js";
import "core-js/modules/es6.array.filter.js";
import "core-js/modules/es6.array.find.js";
import "core-js/modules/es6.array.find-index.js";
import "core-js/modules/es7.array.flat-map.js";
import "core-js/modules/es6.array.from.js";
import "core-js/modules/es7.array.includes.js";
import "core-js/modules/es6.array.iterator.js";
import "core-js/modules/es6.array.map.js";
import "core-js/modules/es6.array.of.js";
import "core-js/modules/es6.array.slice.js";
import "core-js/modules/es6.array.species.js";
import "core-js/modules/es6.date.to-primitive.js";
import "core-js/modules/es6.function.has-instance.js";
// ...
core-jsについて
ポリフィルがたくさん実装されているライブラリです。
ES2015へのポリフィルだけでなく、ES2023などProposalのものも含まれています。
まとめ
- まずは自分のアプリケーションがサポートするターゲット環境を決める or 把握する
- browserslistを使っている場合、
npx browserslist
などで一覧を取得できます - ブラウザのstatsデータをアップデートするためにもcaniuse-liteのDBも更新する
- browserslistを使っている場合、
- すべてのターゲットをサポートするようにpluginが追加されるので、どのプラグインが追加されるかを把握する
- リストはこのあたりで管理している (これだけ見ても分からない...)
- https://github.com/babel/babel/tree/v7.20.6/packages/babel-compat-data/data
- core-jsのポリフィルの読み込みもターゲットを参照していて、
useBuiltIns
で設定ができる
このあたりの設定を理解していないと、うっかりサポート対象環境で動かないバグにつながったり、逆に安全に倒そうと思うばかりにあまり無駄なコードを削れなかったりというのがあるので、知れてよかったです。
Qiita株式会社のカレンダー3日目は @degudegu2510 と@masato930 が担当します!
ぜひ、Qiita株式会社のカレンダーを購読設定して、明日の記事もご覧いただけると嬉しいです。