🍋

@babel/preset-envの導入に伴い@babel/polyfillを撲滅した話

2023/01/31に公開

https://zenn.dev/sa2knight/articles/67f6f5cc4ed5e26e391c

この記事がとても分かりやすかったのですが、@babel/preset-env では

  • 新しい構文への変換( arrow function など)
  • 新しい機能の追加( Promise Array.prototype.flatMap など )

の様な事を行ってくれます。本記事では後者について、@babel/polyfill の利用を辞めて @babel/preset-env でPolyfillを行わせる上で確認を行った事について書いて行きます。

背景

過去にbabelをバージョンアップする際に @babel/preset-env の導入は行っていたのですが、プロダクトが大きすぎてどのPolyfillを使いたいのかの調査が行えず、 Polyfillについては 元々導入していた @babel/polyfill を引き続き利用していました。

ただ、 @babel/polyfill はすでにdeprecatedです。
プロダクトの依存ライブラリのアップデートをする中で、重い腰をあげて @babel/preset-env に変える事にしたのでした。
https://babeljs.io/docs/en/babel-polyfill

本題

@babel/polyfill は、es6で期待される機能群を一通り組み込むライブラリであり、 @babel/preset-env は環境に応じて組み込む対象の機能を変更する事ができるライブラリです。

たとえば、

  • ソースコード上で利用されているもののみ追加
  • chrome、safariなど特定のブラウザの特定バージョン以降で必要なもののみ追加
    • シェア0.5%以上、最新の3バージョンなどの指定も可能。

などの様な事が行えます。この時、 @babel/polyfill を単純に @babel/preset-env に置き換えて良いのか。どういう機能差異があるのかがわからない点でした。

もちろんサポート対象のブラウザは定めているため、@babel/preset-envを用いてサポートブラウザでのみ動くようにしても良いのですが、弊プロダクトは現状サポートブラウザと実際利用されているブラウザに大きな差があります。また、比較的レガシーな環境下で利用され続けるケースも見られます。
さすがにIE11のサポートまでは考えていませんが、ある程度どういう影響があるか(全く問題が無いのか、ブラウザによっては動かない可能性があるのか)を前もって知っておく必要がありました。

という事で、両者の内容の違いについてみていく事にしました。

両者の違い

まぁやることは力技なのですが、それぞれがどういう機能を提供していくか確認します。

@babel/polyfill

利用しているbabel@7.4.5では、これだけのモジュールimportされています。

https://github.com/babel/babel/blob/v7.4.5/packages/babel-polyfill/src/noConflict.js

importされているモジュールをそれぞれを展開していくと・・・
たとえば require("core-js/es6"); はこんな風になっています。

require('../modules/es6.symbol');
require('../modules/es6.object.create');
require('../modules/es6.object.define-property');
require('../modules/es6.object.define-properties');
require('../modules/es6.object.get-own-property-descriptor');
// 超長いので省略
require('../modules/es6.reflect.own-keys');
require('../modules/es6.reflect.prevent-extensions');
require('../modules/es6.reflect.set');
require('../modules/es6.reflect.set-prototype-of');

このように、順次展開していくと @babel/polyfill が拡張してくる機能の一覧を確認できます。

@babel/preset-env

さて次に@babel/preset-env です。まず、対象となるブラウザの指定を行います。

.browserslistrc
> 0.5%
last 3 versions
Firefox ESR
not dead

これは、browserslistで確認すると、以下のようなブラウザが対象となります。
https://browsersl.ist/#q=>+0.5%25%2Clast+3+versions%2CFirefox+ESR%2Cnot+dead&region=JP

@babel/preset-env ではimport文を、対象のブラウザについて必要なモジュールのrequireに置き換える形となるため、以下の様なjsを作り、babelの変換をかけてみます。

index.js
import 'core-js/stable';
import 'regenerator-runtime/runtime';
babel index.js

すると、

index.js
require("core-js/modules/es.symbol");
require("core-js/modules/es.symbol.description");
require("core-js/modules/es.symbol.async-iterator");
require("core-js/modules/es.symbol.has-instance");
require("core-js/modules/es.symbol.is-concat-spreadable");
require("core-js/modules/es.symbol.iterator");
require("core-js/modules/es.symbol.match");
// 超長いので省略
require("core-js/modules/web.immediate");
require("core-js/modules/web.queue-microtask");
require("core-js/modules/web.timers");
require("core-js/modules/web.url");
require("core-js/modules/web.url.to-json");
require("core-js/modules/web.url-search-params");
require("regenerator-runtime/runtime");

と、指定したブラウザで必要とされる一覧を確認する事ができます。

結果

かなり力技ですが、あとは差分が無いか比較するのみです。
結論からいうと、今回のブラウザ指定であれば、元々あった実装は全て含める形になっているようでした。

唯一、 Reflect.enumerate というものが含まれていませんでしたが、、まぁ大丈夫でしょう。

Removed in ECMAScript 2016 (ES7).

となっていますし、モダンなブラウザでは基本的にサポートされていない実装の様です。

と、いうようにして、 @babel/polyfill から @babel/preset-env へ、ブラウザ依存でのダウングレードの可能性について、根拠を持って移行を進める事ができそうです。

OPENLOGI Tech Blog

Discussion