babel-polyfill / babel-runtime の代わりにcore-jsを直接使うのはアリかナシか

  • 27
    Like
  • 3
    Comment

babel-polyfillとbabel-runtimeの使い分けに迷ったので調べた の続編・追加調査。

使い分け的なのわかったものの、未だにどうにも困りごとが多いので、更に深掘りしてみた。

それぞれのおさらい & 困りごと

polyfillのページで改めておさらいしつつ、それぞれの困りごとを書いてみる

  • babel-polyfill
    • core-jsregenerator-runtimeを読み込んでいる
    • polyfillとして、windowグローバルを拡張する仕組み
    • 困りごと
      • 複数ファイルからロードするとthrow errorされてしまうので、うっかりしてるとハマるので気を使う
      • 上記のような問題のため、ブラウザの場合は、別途dist/polyfill.jsなど、別管理してを読み込む
        • require('babel-polyfill')の様にコードベース上から読み込むことは推奨されてない。
        • これをやると二度読みでコードが完全に死んでしまうので大変危険
      • polyfillを別なscriptファイルとして読み込ませることになったりする。
  • babel-runtime / babel-plugin-transform-runtime
    • Polyfillが必要なコードを静的解析して、コードごと置き換える。
    • 静的変換なので、インスタンスメソッドのArray.prototype.includesとかは変換出来ない([1,2,3].inclues(3)みたいなコードのはずなので、そりゃ辛そう)。
    • 困りごと

じゃあcore-jsを読み込むのはどうなのか?

babel-polyfill / babel-runtime それぞれ内部的にcore-jsを利用している。

「じゃあcore-jsを直接使うのはどうなのだろう?」と考えた(regenerator-runtimeについては個人的にあまりお世話にならない都合で割愛させていただく。ほとんど扱いは一緒であろうと思われる)。

ここで改めてPolyfillのページを見てみると、こんな事が書いてある。

Note: Depending on what ES2015 methods you actually use, you may not need to use babel-polyfill or the runtime plugin. You may want to only load the specific polyfills you are using (like Object.assign) or just document that the environment the library is being loaded in should include certain polyfills.

要約すると、「必要なものだけ(例えばObject.assignなど)を選択してロードしても良い」

とのこと。つまり、「core-jsでpolyfillするのはアリかナシか?」という事については アリ と言えそうだ。

ちょっと待った。それでもbabel-polyfillがごにょごにょしてるのは気になる

自前でやるにしても、それはbabel-polyfillとどのぐらい差異が出るのかは気になる。

babel-polyfillのコードを読んでここから細かく検証してみたい(versionは6.19.0)

大まかにこんな具合になっている。

  1. globalの変数を利用して、複数読み込まれるのを制御(L3-6)
  2. core-jsregenerator-runtimeの読み込み(L8-9)
  3. core-jsに対するモンキーパッチ的な処理(L11-29)

ということで、1と3について見てみる。

babel-polyfillの読み込みを一回しか許可しない

なぜbabel-polyfillはなぜ1回しか読み込みを許していないのか?

この疑問を調べるのに、下記2つのissueが役に立った

https://github.com/babel/babel/issues/4019
https://github.com/babel/babel/pull/2050

この2つを総括すると、こんな具合が見て取れる

  • polyfillというのは得てして冪等ではない。複数回読み込む事で副作用が起きる懸念がある。
    • これがcore-jsのことを指しているのはpolyfill全体のことを言っているのかはちょっと掴めなかった。
  • polyfillが衝突することで、問題を起こしやすい
  • そのため、複数箇所で読むべきではない
  • とはいえ、「throw errorするのはやりすぎなのでもうちょっとスマートにやってほしい、ベストエフォートな振る舞いにしてほしい」という声が意見が散見される(個人的にもこれに同意)
  • libraryを作るなら、globalを汚染せぬよう、babel-transform-runtime使うべきという情報もあった。

ワークアラウンドとして、babelが利用している_babelPolyfillという変数をチェックするハックも紹介されている。

if (!window._babelPolyfill) {
  require('babel-polyfill')
}

ということで、babel-polyfillは polyfillの複数回読み込みによるバグを回避するために複数回読み込みを禁止している とわかった。

ただし、今回の調査では「どのようなバグが起きるのか?」という事までは調べられなかった(もし知っている方がおりましたら是非コメントいただければ幸いです)。
core-jsも覗いてみたが、「複数回読み込むな、読み込むとバグがある」という報告や「冪等である(冪等になった)」というchangelogは見当たらない。
実装を読み得る限りで読む分には、nativeを優先するようになっているようにも読めた。

この「一度のみ読み込ませる処理」というのは6to5時代から引き継がれているものだったりするので、実は今のcore-jsでは問題無い可能性(または少なくなっている可能性、かなりのエッジケースだったりベストエフォートとして片付けられる可能性)も考えられる

とはいえ、基本としては複数回読み込むことへの副作用は注意すべき観点だろう

core-jsに対するモンキーパッチ?

core-jsに対して色々モンキーパッチされている箇所は、文頭に
// Should be removed in the next major releaseとある。
つまり、babel 7系になった際にはこれら処理は消えるものと思われる(core-jsのnext-versionとも読めてしまうが、下記の文脈を考えるとbabelのmajor versionの事と推測できた)

では具体的に何が書かれているかというと、core-js 2.0.0へのアップデート時にremoveされたものを、後方互換措置として差し戻す処理をしている。

具体的には、下記のような差し戻し処理がされている。

  • core-js/からregexp/escapeの読み込み
    • RegExp.escape moved from the es7 to the non-standard core namespaceを差し戻す後方互換処理
    • import "core-js/fn/regexp/escape"しているだけ
  • padLeft, padRightの互換維持
    • renamed String#{padLeft, padRight} -> String#{padStart, padEnd}という変更を差し戻す後方互換処理。
    • padStart -> padLeft / padEnd -> padRightと扱えるようにしている。
    • padLeft / padRightというメソッドはTC39 Meetingでrenameが決定しているので、もしこれを利用しているなら速やかにpadStart / padEndに置き換えるべきだろう
  • Arrayのジェネリクスの再定義
    • removed Mozilla Array genericsを差し戻す後方互換処理
    • すごくコード的にわかりづらいが、Array.popみたいな呼び出しで、[1,2,3].popと同じメソッドが呼び出されるようになっている
    • この再現方法が結構難解だったので、だいたい何やっているかを自分向けに分解したgistも併せて貼っておく
    • core-jsとしては、もしこの機能がほしい場合はarray-genericsという別なshimを利用するようアナウンスされている

FYI: create-react-appも独自にpolyfllしてる。

偶然見つけたが、reactをさっくり作るためのツールであるcreate-react-appも、独自なpolyfillを作成している。
このコードはnpm run ejectするとコードベースに出力される。

https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/config/polyfills.js

下記がPolyfillされている

まとめ

  • core-jsなどで別途自己管理でpolyfillするのは割りとアリ
    • core-js自体は独立したpolyfillライブラリなので、スタンドアローンに十分使える
  • ただし、polyfillは冪等というわけではないので、そのあたりの管理はやったほうが良い
    • core-jsでpolyfillするにしても、複数回呼び出しは避けるのが賢明と言えそう
  • babel-polyfillが差し戻してるcore-jsから落とされた機能を利用する場合には、何らか処理が必要
    • とはいえ、おそらくどこかで消えるので、なるべく早めに根本解決したほうが良い。
  • ライブラリ提供を目的としてるならbabel-runtime一択。global汚染するpolyfillに出番は無い