babel-preset-env@2.0 を試した -トランスパイル環境をスケールさせていく -

こんにちは、制作部 フロントエンドエンジニアの武田です。
入社して5ヶ月経ちました。
ECMAScript の推し proposal は Cancellation API です。

今回は開発には切っても切れない Babel 、そのプリセットである babel-preset-env についてお話します。

このプリセットは Browserslist 1 の記述で compat table 2 を利用し、指定された環境にあったトランスパイルができるプリセットです。 使い方についての記事はすでに多くありますので今回は丁寧に触れません。

ピンと来ない方は、JavaScript をとりまくビルド環境のスケーリングに寄与しそう、と大きく理解していただいてよいです。

対象となりそうな人

  • Babel を使ったトランスパイル環境を作ったことがある人
  • babel-preset-env をすでに使っている人、知っている人
  • Babel 沼にハマったことがある人
  • ES2017 以降も Babel に付随するパッケージをアップデートしながらやっていきな人

書くこと

そんな便利なプリセットの alpha 版である @2.0.0 を試しに使ってみて

  • 2.0 で何が変わるか
  • 2.0 以降、babel-preset-env を使ったトランスパイルがどう変わりそうか
  • それらを踏まえて、今後の Babel の動向をほんのり少しだけ

について書いていきます。

この記事を書いている時点の2系は alpha 版となっておりそれらを利用して書いていますので、 プロダクション環境への導入は安定版となってから十分に吟味した上で利用ください。

サンプル

書かないこと

  • サンプルで使用しているコードそのものについて
  • バンドラーとして使用した webpack について

babel-preset-env v1 => v2 における大きな変更点

v2になって何が変更されるかというと、大きなところでは useBuiltIns 設定の値が変更されます。

{
  "presets": [
    ["env", {
      "targets": {
        "browsers": [
          "ios >= 9"
        ]
      },
      // v1 まで は true | false
      "useBuiltIns": true
      // v2 からは "usage" | "entry" | false
      "useBuiltIns": "usage"
    }]
  ]
}

useBuiltIns って何の設定ですか

A way to apply babel-preset-env for polyfills (via babel-polyfill).

です。要は babel-polyfill を引き込むかどうかの設定になります。
1系ではコード上に @import 'babel-polyfill' の記述が必要です。

1系で "useBuiltIns": true に設定しトランスパイルした、コンソールへのデバッグ表示が下記になります。

Using plugins: ブロックでこのブラウザターゲットに必要なものがわかります。
その後に Using polyfills: ブロックでさらに穴埋めする Polyfills 。なんですが…

…便利ですけどこんなに要らないです…。使ってないものの方が多い…。
これは babel-preset-env を使用していなくても抱える悩みですね。
@import 'babel-polyfill' の全マシで要らないものまで引き込みたくない気持ちです。

v2 は余計なもの引き込まない

"useBuiltIns": "usage" という v2 の新しい設定をしてトランスパイルしてみます。
2系ではこの設定を有効にした場合 @import 'babel-polyfill' の記述は必要ありません。

Using polyfills: の箇所がちょっと変わっていますね。
デバッグログだけだと少しわかりづらいですが、静的解析を行いコード上の使用に応じて穴埋めしています。
これで不要に Polyfill だけで肥大化することはなくなります。
ちなみに "useBuiltIns": "entry" の使用はエントリーファイルが1つの場合のみとして v1 時の true と同等の動きをします。

それでもハマりそうな Babel 沼

ただケースによっては問題が出てきます。3

Object.assign のような静的なメソッドは変換や解析がうまくいきますが、
String.prototype.includes
Array.prototype.includes
のようなインスタンス/プロトタイプメソッドが、コンテキスト上で arr.includes('foo') のように利用される場合、arr の型判定をした上で変換、ということができません。

これについてはすでに issue にあがっておりまだ落としてどころがないように見えます。4
型推論しようとすれば TypeScripts, Flow のようなアノテーションか型定義ファイルへのリファリングをしてから変換する必要があります。
現状どうするのかといえば、それ用に別の Polyfill ライブラリで埋めるか、明示的に @import 'core-js/modules/es7.array.includes' するかどちらかしかないでしょう。

v2 のその先、Babel の動向を少しだけ

v2 でこの大きな変更は v1 がリリースされてすぐに issue で議論され 5、4月段階でマージされたあとに v2.0.0-alpha.6 としてリリースされています。6
次のロードマップとしては transform-runtime を機能的にこちらに移す話も上がっています。7

日付としてはちょっと古いミーティングですが、下記のような記載も見受けられます。8

Move babel-preset-env into core (or a package (like babel) that includes it by default)

コアに入るような入らないような話もちらほら出ているということですね。

コアコミッターの Tweet を信じ、カミングスーンな安定版の 2.0 を待つことにします。

babel-preset-env v2 まとめ

  • v2 で Polyfill がスマートになる
  • ただ課題はまだありそうだから慎重にいったほうが良さそう
  • v2 以降の動向を watch しておくと良さそう

babel-preset-env が必ずしも銀の弾丸ではありませんが、ECMAScript の策定・採択とブラウザ実装に追従しながら、環境・コードともにスケールできると一番ステキですね。

本日は以上です。最後まで読んでいただき、ありがとうございました。

補足

  • Web プラットフォーム = ブラウザに限った話でしたが、babel-preset-env は Node.js のバージョン、Electron のバージョンにも有効です。
  • ES2015 〜 のスクリプトに関しては、UglifyJS が有効ではありません。
    harmony ブランチである uglify-es では有効ですが、Babel の推奨する babili を使用してminifyしています
  • リポジトリでは webpack を使用していますが、もちろんエントリーファイルが 1 ファイルであれば babel-cli も有効です。
  • レガシーな端末での動作を確実にするものではありません。トランスパイルによるファイルの肥大化・端末自身の処理速度によってはパフォーマンスが著しく落ちることもあります。

サンプルの検証について

サンプルで作ったものは下記端末、環境で確認しています。
※ 多端末までは確認していませんのでご注意ください。
※ development, recent(=last 2 version) 環境については特に検証の対象としていません。

環境指定端末OK
android>=2Android2.2 on IS05
android >= 4Android4.1.2 on Xperia UL SOL22
ie >= 10IE10 on Win7(x86) 9
ios >= 9iOS9 on iPhone5S
chrome >= 59Chrome60 on Mac Sierra

top