@babelでトランスパイルしているのにIE11でNodeList.forEach()が使えない。
なんで? ...そうか、ポリフィルが必要だった。
ポリフィルはブラウザの種類やバージョンによって追加されたり変更・削除されたコードを補完するものです。
トランスパイル(transpile)
プログラムを、べつのバージョンや、べつの言語のプログラムに変換すること。
一種のコンパイラ。トランスコンパイラともいう。
JavaScriptは最新の仕様で開発して、任意のバージョンにトランスパイルする手法が一般的になっている。
(新しい仕様のほうがシンプルな記述で読み書きしやすいため。)
@babelが有名。
トランスパイルは文法を変換するが新しいAPI(関数やオブジェクト)までは対応できない。
古いもので確実に動かすためにはポリフィルも必要。
ポリフィル(Polyfill)
プログラムのバージョン違いでAPIが不足するところを補うこと。
プログラム言語はバージョンが上がると新しいAPIを追加する。APIは関数やクラス・オブジェクトのこと。
古いバージョンで動かすときは新しいAPIと同じ機能がないと動かない。それを作るのがポリフィル。
ポリフィルは、新しいAPIを移植するのではなく、古いバージョンのプログラムで実装できるAPIに作り直して提供する。
jQueryのmigrateはjQueryのポリフィル・パッケージ。
トランスパイルで有名な@babelにもポリフィルの拡張機能がある。
Windows8のサポートはすでに終わってます。今サポートされているのはバージョン8.1と10のふたつ。
IEに対応しないといけないか? と言われれば、作業量を考えればほとんどないでしょう。
ただし、社内のイントラネットなど特定の環境では需要があり、Windows10ではEdgeが標準ですがIE11も残しています。
今回の話は、近い将来必ず不要になることに注意してください。
(Win10からIEが消えたら確実にいらない。)
ちなみに最新のEdgeの中身はChromeです。マイクロソフト、ついにやめたか...
forEach()は文法ではなくAPI
自分のサイトを見ている人の11%はIEを使ってます。そのうちの99%以上はIE11。
ということでJavaScriptをIE11でも動くようにしようと思ったのですがうまくいきません。
(IE11以外は対象外。)
SCRIPT438: オブジェクトは 'forEach' プロパティまたはメソッドをサポートしていません。
そう思って自分は悪くないっ!てしてましたが、そういうとき、悪いのはたいがい自分です。
ここでも100%自分がまちがってた。トランスパイルはしているけどポリフィルをしていません。
そりゃそうだ。forEach()はトランスパイルではムリだもん。
@babelのポリフィル
ということで、@babelのポリフィルを設定します。
パッケージ | バージョン |
---|---|
webpack webpack-cli | 4.43.0 3.3.11 |
@babel/core @babel/preset-env babel-loader | 7.10.2 7.10.2 8.1.0 |
まずは変更前の設定から。babel専用の.babelrc.jsを使っています。
webpack.config.jsに設定してもいいですが、.babelrc.jsは@babel専用の設定でwebpack以外でも使えるのでこちらのほうがいいです。
module.exports = {
presets: ['@babel/preset-env'],
plugins: [
'@wordpress/babel-plugin-import-jsx-pragma',
'@babel/transform-react-jsx',
],
};
module.exports = {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage",
},
],
],
plugins: [
"@wordpress/babel-plugin-import-jsx-pragma",
"@babel/transform-react-jsx",
],
};
@babel/preset-envのuseBuiltInsオプションを使います。
pluginsのオプションは無視してください。WordPressとreactで@babelを使うためのもので、ポリフィルとは関係ありません。
@babelには@babel/polyfillという、いかにもなパッケージがあります。
が、これはbabel7.4.0以上から非推奨です。preset-envを使いましょうになってます。
core-jsが必要
まだ作業は終わりません。いまwebpackを実行するとこんなエラーが出ます。
WARNING: We noticed you're using the `useBuiltIns` option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via the `corejs` option.
You should also be sure that the version you pass to the `corejs` option matches the version specified in your `package.json`'s `dependencies` section. If it doesn't, you need to run one of the following commands:
npm install --save core-js@2 npm install --save core-js@3
yarn add core-js@2 yarn add core-js@3
(省略)
ERROR in ./src/***/***.js
Module not found: Error: Can't resolve 'core-js/modules/es6.array.for-each' in '***\***\js'
resolve 'core-js/modules/es6.array.for-each' in '***\***\js'
Parsed request is a module
(省略)
警告: core-jsのバージョンを宣言せずにuseBuiltInsオプションを使っています。今のところ、バージョンが指定されていないときは2.xを想定しています。
このデフォルトバージョンは将来のBabelで変更される可能性が高いので、corejsオプションで明示的に指定することをおススメします。
(以下省略)
筆者訳
core-jsのインストールコマンドが表示されてるのでコピペで実行しましょう。
(もちろんバージョン3)
パッケージ | バージョン |
---|---|
core-js | 3.6.5 |
設定も修正します。
module.exports = {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage",
corejs: { version: 3, proposals: true },
},
],
],
plugins: [
"@wordpress/babel-plugin-import-jsx-pragma",
"@babel/transform-react-jsx",
],
};
設定の注意点
usageにはふたつの設定があるようで、
when using useBuiltIns: "usage" you have two different alternatives:
("usage" を使用する場合、次の2つの選択肢があります。)
set the shippedProposals option to true. This will enable polyfills and transforms for proposal which have already been shipped in browsers for a while.
(shippedProposals オプションを true に設定します。これにより、しばらくの間、ブラウザで既に出荷されているプロポーザル用のポリフィルやトランスフォームが有効になります。)
use corejs: { version: 3, proposals: true }. This will enable polyfilling of every proposal supported by core-js.
(これにより、core-jsでサポートされているすべてのプロポーザルのポリフィルが可能になります。)
筆者訳
shippedProposalはブラウザに影響されるようなので、corejsオプションだけを採用。
'entry' と 'usage'のちがい
useBuiltInsには'entry' も指定できますが、これは自分でcore-jsをimportをしないといけません。
import "core-js";
しかも、すべてのポリフィルをimportするので無駄なコードまでも入ります。
さらに、個別にimportしてコードを最小にできますが、importするパッケージを覚えないといけないのが面倒。
一方、'usage' は、importが不要です。
自動で使うパッケージだけを最小限でimportします。
それでもまだ動かない
これでいけるか? と思いましたがまだダメでした。設定が不十分です。
結果から言うとこの設定です。
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: {
ie: 11,
esmodules: true,
},
useBuiltIns: "usage",
corejs: { version: 3, proposals: true },
},
],
],
plugins: [
"@wordpress/babel-plugin-import-jsx-pragma",
"@babel/transform-react-jsx",
],
};
targetsのesmodulesがtrueじゃないとダメです。また、esmodulesを設定するとブラウザのバージョン指定(targets.browsers)が無視されるので、ieオプションが必要です。
(もちろん .browserslistrc も無視される。)
ターゲットブラウザはieだけじゃなく、その他のブラウザのオプションも用意されています。くわしくは、上のリンクを見てください。
結論! IE11は捨てる。
長々と説明してきてそれは無いわ~、ですが、結果的にIE11は捨てることにしました。
理由は、
- jsファイルのサイズが大きくなる。
- @babelのターゲット・ブラウザのバージョン指定が面倒。
- CSSでプレフィックス以外の修正が必要。
ほかのブラウザ、スマホ・タブレットなどの中小型デバイスに対応するのに加えIE11まで対応するのは、時間はかかるのに割に合いません。
IEの利用者は減っていくことは確実なので。Edgeで見てくれってことですね?
(スマホ・タブレットを最優先しているというのもある。)
今回の話をすべて否定はしません。IE11と他のブラウザの共存を諦めただけで。
もちろんですけど、お金と時間がふんだんにあるのならやります。