blog.euxn.me

babel 環境における Polyfill のビルド最適化と async-await の扱い

2020-12-05 Sat.

リポジトリ

euxn23/babel-and-polyfill-sample

想定読者

  • Babel を使っているが、 Polyfill がいまいちわからない方
  • Polyfill は使えるが、より最適なビルドを生成したい方
  • @babel/polyfill が非推奨になったことを受け現在の推奨実装を知りたい方

Polyfill とは

例として、以下の Promise を使ったコードを、 @babel/preset-env を使用して IE11 向けにビルドします。

input
1const main = () => {
2 Promise.resolve().then(() => console.log('then'));
3}
4
5main();
output
1"use strict";
2
3var main = function main() {
4 Promise.resolve().then(function () {
5 return console.log('then');
6 });
7};
8
9main();

このように、 babel は文法である import (esmodules は文法に含まれる)や() => {} を es5 に transpile するのみで、 Promise 等の機能については関与しません。 (ここでは JavaScript のパースに関わるものを文法、実行時に必要な global な Object や、組み込みクラスのメソッドの有無についてを機能、とします) この不足している機能を補う代表的な手法として、 Polyfill が挙げられます。

Polyfill を導入する代表的な手段

2020 年末現在、有用な手段としては以下が挙げられるかと思います。

  • polyfill.io を html 側で読み込む
  • core-js を必要箇所に import する
  • @babel/plugin-transform-runtime
  • @babel/preset-env の useBuildIns を使用する

polyfill.io については、 Polyfill.io を使って JavaScript の Polyfill を適用する が非常にわかりやすく説明されているため、ここでは詳細を省略します。 ※ @babel/polyfill は既に非推奨となっており、 @babel/plugin-transform-runtime もしくは @babel/preset-envcore-js の併用を推奨しています。

core-js を使用する

polyfill 実装である core-js を必要に応じて import します。 たとえば、 Promise を使用する場合、以下のように記述します。

input
1import "core-js/modules/es.promise"
2
3const main = () => {
4 Promise.resolve().then(() => console.log('then'));
5}
6
7main();
output
1"use strict";
2
3require("core-js/modules/es.promise");
4
5var main = function main() {
6 Promise.resolve().then(function () {
7 return console.log('then');
8 });
9};
10
11main();

必要な Polyfill のみを指定して挿入できるためバンドルサイズは小さくなりますが、必要箇所それぞれに挿入することになるため手間がかかるのが難点です。 また、必要な Polyfill を正しく把握している必要があり、 Polyfill の不足によるバグを引き起こす可能性もあるため、注意が必要です。 (下記の useBuildIns: 'usage' の場合では、同一のコードに対して core-js/modules/es.object.to-string が挿入されている) アプリケーションのルートで適用する場合、ビルドバージョンが新しくなってもキャッシュが効き得ることを鑑みると polyfill.io を使う方が手間は少なくなるかと思います。

@babel/plugin-transform-runtime

https://babeljs.io/docs/en/babel-plugin-transform-runtime @babel/runtime{,-corejs{2,3}} というヘルパーランタイムをビルドファイルに挿入します。 https://babeljs.io/docs/en/babel-runtime @babel/polyfill と違い、 core-js のバージョンを指定して Polyfill することができます。 以下の例では、 core-js@3 を Polyfill として使用しているため、 core-js@3 に加え、 @babel/runtime-corejs3 が依存に必要となります。

input
1const main = () => {
2 Promise.resolve().then(() => console.log('then'));
3}
4
5main();
.babelrc
1{
2 "presets": [
3 [
4 "@babel/preset-env",
5 {
6 "targets": {
7 "ie": 11
8 }
9 }
10 ]
11 ],
12 "plugins": [
13 [
14 "@babel/plugin-transform-runtime",
15 {
16 "corejs": 3
17 }
18 ]
19 ]
20}
output
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
4
5var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
6
7var main = function main() {
8 _promise.default.resolve().then(function () {
9 return console.log('then');
10 });
11};
12
13main();

このように、上記の core-js の挿入なく、自動で Promise を @babel/runtime のものに置き換えてビルドされます。 (_interopRequireDefault は、 commonjs と esmodules の default の仕様差異を吸収するヘルパーです)

async-await と regeneratorRuntime について

さて、ここまで 機能 について触れてきましたが、 async-await は文法です。 babel は async-await を非対応ブラウザ向けにどのようにビルドしているか見てみましょう。

input
1const main = async () => {
2 await Promise.resolve()
3 console.log('after await')
4}
5
6main();
output
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
4
5var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs3/regenerator"));
6
7var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
8
9var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/asyncToGenerator"));
10
11var main = /*#__PURE__*/function () {
12 var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
13 return _regenerator.default.wrap(function _callee$(_context) {
14 while (1) {
15 switch (_context.prev = _context.next) {
16 case 0:
17 _context.next = 2;
18 return _promise.default.resolve();
19
20 case 2:
21 console.log('after await');
22
23 case 3:
24 case "end":
25 return _context.stop();
26 }
27 }
28 }, _callee);
29 }));
30
31 return function main() {
32 return _ref.apply(this, arguments);
33 };
34}();
35
36main();

async-await は Promise に置換されるのではなく、 regenerator というヘルパーに置換されます。 この @babel/runtime-corejs3/regenerator は内部で regenerator-runtime というパッケージに依存しています。 つまり、 babel で async-await を非対応ブラウザ向けにビルドし使用するためには、他の Polyfill 手段を用いていようとも @babel/plugin-transform-runtime により regenerator に変換する必要があります。 async-await が動かない、 regeneratorRuntime が見つからないというエラーが出る、という際のトラブルシューティングとして、ビルドターゲットを node にすることで解決する、という情報も見つかりますが、これは async-await 対応環境向けにビルドしているのみで IE11 で動かなくなるので、正しい解決方法ではありません。 参考までに Chrome 86 向けのビルドでは以下のようになります。

.babelrc
1{
2 "presets": [
3 [
4 "@babel/preset-env",
5 {
6 "targets": {
7 "chrome": 86
8 }
9 }
10 ]
11],
12 "plugins": [
13 [
14 "@babel/plugin-transform-runtime",
15 {
16 "corejs": 3
17 }
18 ]
19]
20}
output
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
4
5var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
6
7const main = async () => {
8 await _promise.default.resolve();
9 console.log('after await');
10};
11
12main();

なお、デフォルトでは core-js の設定がオフになっているため一般的には core-js のバージョンを指定して使用しますが、別の方法で Polyfill を挿入している場合、 core-js をオフのままにすることで、 regenerator のみを挿入することができます。 この場合は @babel/runtimeregenerator-runtime が依存に必要になります。

output
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
6
7var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
8
9var main = /*#__PURE__*/function () {
10 var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
11 return _regenerator.default.wrap(function _callee$(_context) {
12 while (1) {
13 switch (_context.prev = _context.next) {
14 case 0:
15 _context.next = 2;
16 return Promise.resolve();
17
18 case 2:
19 console.log('after await');
20
21 case 3:
22 case "end":
23 return _context.stop();
24 }
25 }
26 }, _callee);
27 }));
28
29 return function main() {
30 return _ref.apply(this, arguments);
31 };
32}();
33
34main();

参考: TypeScript での async-await のコンパイル

tsc でコンパイルする場合、以下のようになります。

target が ES2015 以上の場合 -> Promise や Generator に依存したビルドファイルを生成する target が ES5 以下の場合 -> lib に ES2015.Promise ES2015.Generator を指定し、同等の動作をするヘルパー関数が挿入される

target が ES5 以下の場合、 lib が不足しているとコンパイルエラーとなります。

@babel/preset-env による polyfill

https://babeljs.io/docs/en/babel-preset-env

通常の用法の通りに @babel/preset-env を使用した場合、 targets を指定するのみでは polyfill は挿入されません。

input
1const main = () => {
2 Promise.resolve().then(() => console.log('then'));
3}
4
5main();
.babelrc
1{
2 "presets": [
3 [
4 "@babel/preset-env",
5 {
6 "targets": {
7 "ie": 11
8 }
9 }
10 ]
11 ]
12}
output
1"use strict";
2
3var main = function main() {
4 Promise.resolve().then(function () {
5 return console.log('then');
6 });
7};
8
9main();

polyfill を挿入するためには、 useBuiltIns というオプションを指定します。 https://babeljs.io/docs/en/babel-preset-env#usebuiltins useBuiltIns は preset-env の targets(browserslist との integration あり) に合わせて、変換時にそれぞれの処理を行います。

useBuiltIns: 'entry'

useBuiltIns に entry を指定する場合、 targets に合わせ、コード中の import 'core-js' を各機能ごとに展開し、 import します。 たとえば、以下のコードを IE11 向けと Chrome 86 向けにそれぞれビルドします。

input
1import "core-js"
2
3const main = () => {
4 Promise.resolve().then(() => console.log('then'));
5}
6
7main();
output(IE11)
1"use strict";
2
3require("core-js/modules/es.symbol");
4
5require("core-js/modules/es.symbol.description");
6
7require("core-js/modules/es.symbol.async-iterator");
8
9require("core-js/modules/es.symbol.has-instance");
10
11require("core-js/modules/es.symbol.is-concat-spreadable");
12
13require("core-js/modules/es.symbol.iterator");
14
15require("core-js/modules/es.symbol.match");
16
17require("core-js/modules/es.symbol.replace");
18
19require("core-js/modules/es.symbol.search");
20
21require("core-js/modules/es.symbol.species");
22
23require("core-js/modules/es.symbol.split");
24
25require("core-js/modules/es.symbol.to-primitive");
26
27require("core-js/modules/es.symbol.to-string-tag");
28
29require("core-js/modules/es.symbol.unscopables");
30
31require("core-js/modules/es.array.concat");
32
33require("core-js/modules/es.array.copy-within");
34
35require("core-js/modules/es.array.every");
36
37require("core-js/modules/es.array.fill");
38
39require("core-js/modules/es.array.filter");
40
41require("core-js/modules/es.array.find");
42
43require("core-js/modules/es.array.find-index");
44
45require("core-js/modules/es.array.flat");
46
47require("core-js/modules/es.array.flat-map");
48
49require("core-js/modules/es.array.for-each");
50
51require("core-js/modules/es.array.from");
52
53require("core-js/modules/es.array.includes");
54
55require("core-js/modules/es.array.index-of");
56
57require("core-js/modules/es.array.iterator");
58
59require("core-js/modules/es.array.join");
60
61require("core-js/modules/es.array.last-index-of");
62
63require("core-js/modules/es.array.map");
64
65require("core-js/modules/es.array.of");
66
67require("core-js/modules/es.array.reduce");
68
69require("core-js/modules/es.array.reduce-right");
70
71require("core-js/modules/es.array.slice");
72
73require("core-js/modules/es.array.some");
74
75require("core-js/modules/es.array.species");
76
77require("core-js/modules/es.array.splice");
78
79require("core-js/modules/es.array.unscopables.flat");
80
81require("core-js/modules/es.array.unscopables.flat-map");
82
83require("core-js/modules/es.array-buffer.constructor");
84
85require("core-js/modules/es.date.to-primitive");
86
87require("core-js/modules/es.function.has-instance");
88
89require("core-js/modules/es.function.name");
90
91require("core-js/modules/es.json.to-string-tag");
92
93require("core-js/modules/es.map");
94
95require("core-js/modules/es.math.acosh");
96
97require("core-js/modules/es.math.asinh");
98
99require("core-js/modules/es.math.atanh");
100
101require("core-js/modules/es.math.cbrt");
102
103require("core-js/modules/es.math.clz32");
104
105require("core-js/modules/es.math.cosh");
106
107require("core-js/modules/es.math.expm1");
108
109require("core-js/modules/es.math.fround");
110
111require("core-js/modules/es.math.hypot");
112
113require("core-js/modules/es.math.imul");
114
115require("core-js/modules/es.math.log10");
116
117require("core-js/modules/es.math.log1p");
118
119require("core-js/modules/es.math.log2");
120
121require("core-js/modules/es.math.sign");
122
123require("core-js/modules/es.math.sinh");
124
125require("core-js/modules/es.math.tanh");
126
127require("core-js/modules/es.math.to-string-tag");
128
129require("core-js/modules/es.math.trunc");
130
131require("core-js/modules/es.number.constructor");
132
133require("core-js/modules/es.number.epsilon");
134
135require("core-js/modules/es.number.is-finite");
136
137require("core-js/modules/es.number.is-integer");
138
139require("core-js/modules/es.number.is-nan");
140
141require("core-js/modules/es.number.is-safe-integer");
142
143require("core-js/modules/es.number.max-safe-integer");
144
145require("core-js/modules/es.number.min-safe-integer");
146
147require("core-js/modules/es.number.parse-float");
148
149require("core-js/modules/es.number.parse-int");
150
151require("core-js/modules/es.number.to-fixed");
152
153require("core-js/modules/es.object.assign");
154
155require("core-js/modules/es.object.define-getter");
156
157require("core-js/modules/es.object.define-setter");
158
159require("core-js/modules/es.object.entries");
160
161require("core-js/modules/es.object.freeze");
162
163require("core-js/modules/es.object.from-entries");
164
165require("core-js/modules/es.object.get-own-property-descriptor");
166
167require("core-js/modules/es.object.get-own-property-descriptors");
168
169require("core-js/modules/es.object.get-own-property-names");
170
171require("core-js/modules/es.object.get-prototype-of");
172
173require("core-js/modules/es.object.is");
174
175require("core-js/modules/es.object.is-extensible");
176
177require("core-js/modules/es.object.is-frozen");
178
179require("core-js/modules/es.object.is-sealed");
180
181require("core-js/modules/es.object.keys");
182
183require("core-js/modules/es.object.lookup-getter");
184
185require("core-js/modules/es.object.lookup-setter");
186
187require("core-js/modules/es.object.prevent-extensions");
188
189require("core-js/modules/es.object.seal");
190
191require("core-js/modules/es.object.to-string");
192
193require("core-js/modules/es.object.values");
194
195require("core-js/modules/es.promise");
196
197require("core-js/modules/es.promise.finally");
198
199require("core-js/modules/es.reflect.apply");
200
201require("core-js/modules/es.reflect.construct");
202
203require("core-js/modules/es.reflect.define-property");
204
205require("core-js/modules/es.reflect.delete-property");
206
207require("core-js/modules/es.reflect.get");
208
209require("core-js/modules/es.reflect.get-own-property-descriptor");
210
211require("core-js/modules/es.reflect.get-prototype-of");
212
213require("core-js/modules/es.reflect.has");
214
215require("core-js/modules/es.reflect.is-extensible");
216
217require("core-js/modules/es.reflect.own-keys");
218
219require("core-js/modules/es.reflect.prevent-extensions");
220
221require("core-js/modules/es.reflect.set");
222
223require("core-js/modules/es.reflect.set-prototype-of");
224
225require("core-js/modules/es.regexp.constructor");
226
227require("core-js/modules/es.regexp.exec");
228
229require("core-js/modules/es.regexp.flags");
230
231require("core-js/modules/es.regexp.to-string");
232
233require("core-js/modules/es.set");
234
235require("core-js/modules/es.string.code-point-at");
236
237require("core-js/modules/es.string.ends-with");
238
239require("core-js/modules/es.string.from-code-point");
240
241require("core-js/modules/es.string.includes");
242
243require("core-js/modules/es.string.iterator");
244
245require("core-js/modules/es.string.match");
246
247require("core-js/modules/es.string.pad-end");
248
249require("core-js/modules/es.string.pad-start");
250
251require("core-js/modules/es.string.raw");
252
253require("core-js/modules/es.string.repeat");
254
255require("core-js/modules/es.string.replace");
256
257require("core-js/modules/es.string.search");
258
259require("core-js/modules/es.string.split");
260
261require("core-js/modules/es.string.starts-with");
262
263require("core-js/modules/es.string.trim");
264
265require("core-js/modules/es.string.trim-end");
266
267require("core-js/modules/es.string.trim-start");
268
269require("core-js/modules/es.string.anchor");
270
271require("core-js/modules/es.string.big");
272
273require("core-js/modules/es.string.blink");
274
275require("core-js/modules/es.string.bold");
276
277require("core-js/modules/es.string.fixed");
278
279require("core-js/modules/es.string.fontcolor");
280
281require("core-js/modules/es.string.fontsize");
282
283require("core-js/modules/es.string.italics");
284
285require("core-js/modules/es.string.link");
286
287require("core-js/modules/es.string.small");
288
289require("core-js/modules/es.string.strike");
290
291require("core-js/modules/es.string.sub");
292
293require("core-js/modules/es.string.sup");
294
295require("core-js/modules/es.typed-array.float32-array");
296
297require("core-js/modules/es.typed-array.float64-array");
298
299require("core-js/modules/es.typed-array.int8-array");
300
301require("core-js/modules/es.typed-array.int16-array");
302
303require("core-js/modules/es.typed-array.int32-array");
304
305require("core-js/modules/es.typed-array.uint8-array");
306
307require("core-js/modules/es.typed-array.uint8-clamped-array");
308
309require("core-js/modules/es.typed-array.uint16-array");
310
311require("core-js/modules/es.typed-array.uint32-array");
312
313require("core-js/modules/es.typed-array.copy-within");
314
315require("core-js/modules/es.typed-array.every");
316
317require("core-js/modules/es.typed-array.fill");
318
319require("core-js/modules/es.typed-array.filter");
320
321require("core-js/modules/es.typed-array.find");
322
323require("core-js/modules/es.typed-array.find-index");
324
325require("core-js/modules/es.typed-array.for-each");
326
327require("core-js/modules/es.typed-array.from");
328
329require("core-js/modules/es.typed-array.includes");
330
331require("core-js/modules/es.typed-array.index-of");
332
333require("core-js/modules/es.typed-array.iterator");
334
335require("core-js/modules/es.typed-array.join");
336
337require("core-js/modules/es.typed-array.last-index-of");
338
339require("core-js/modules/es.typed-array.map");
340
341require("core-js/modules/es.typed-array.of");
342
343require("core-js/modules/es.typed-array.reduce");
344
345require("core-js/modules/es.typed-array.reduce-right");
346
347require("core-js/modules/es.typed-array.reverse");
348
349require("core-js/modules/es.typed-array.set");
350
351require("core-js/modules/es.typed-array.slice");
352
353require("core-js/modules/es.typed-array.some");
354
355require("core-js/modules/es.typed-array.sort");
356
357require("core-js/modules/es.typed-array.subarray");
358
359require("core-js/modules/es.typed-array.to-locale-string");
360
361require("core-js/modules/es.typed-array.to-string");
362
363require("core-js/modules/es.weak-map");
364
365require("core-js/modules/es.weak-set");
366
367require("core-js/modules/esnext.aggregate-error");
368
369require("core-js/modules/esnext.array.last-index");
370
371require("core-js/modules/esnext.array.last-item");
372
373require("core-js/modules/esnext.composite-key");
374
375require("core-js/modules/esnext.composite-symbol");
376
377require("core-js/modules/esnext.global-this");
378
379require("core-js/modules/esnext.map.delete-all");
380
381require("core-js/modules/esnext.map.every");
382
383require("core-js/modules/esnext.map.filter");
384
385require("core-js/modules/esnext.map.find");
386
387require("core-js/modules/esnext.map.find-key");
388
389require("core-js/modules/esnext.map.from");
390
391require("core-js/modules/esnext.map.group-by");
392
393require("core-js/modules/esnext.map.includes");
394
395require("core-js/modules/esnext.map.key-by");
396
397require("core-js/modules/esnext.map.key-of");
398
399require("core-js/modules/esnext.map.map-keys");
400
401require("core-js/modules/esnext.map.map-values");
402
403require("core-js/modules/esnext.map.merge");
404
405require("core-js/modules/esnext.map.of");
406
407require("core-js/modules/esnext.map.reduce");
408
409require("core-js/modules/esnext.map.some");
410
411require("core-js/modules/esnext.map.update");
412
413require("core-js/modules/esnext.math.clamp");
414
415require("core-js/modules/esnext.math.deg-per-rad");
416
417require("core-js/modules/esnext.math.degrees");
418
419require("core-js/modules/esnext.math.fscale");
420
421require("core-js/modules/esnext.math.iaddh");
422
423require("core-js/modules/esnext.math.imulh");
424
425require("core-js/modules/esnext.math.isubh");
426
427require("core-js/modules/esnext.math.rad-per-deg");
428
429require("core-js/modules/esnext.math.radians");
430
431require("core-js/modules/esnext.math.scale");
432
433require("core-js/modules/esnext.math.seeded-prng");
434
435require("core-js/modules/esnext.math.signbit");
436
437require("core-js/modules/esnext.math.umulh");
438
439require("core-js/modules/esnext.number.from-string");
440
441require("core-js/modules/esnext.observable");
442
443require("core-js/modules/esnext.promise.all-settled");
444
445require("core-js/modules/esnext.promise.any");
446
447require("core-js/modules/esnext.promise.try");
448
449require("core-js/modules/esnext.reflect.define-metadata");
450
451require("core-js/modules/esnext.reflect.delete-metadata");
452
453require("core-js/modules/esnext.reflect.get-metadata");
454
455require("core-js/modules/esnext.reflect.get-metadata-keys");
456
457require("core-js/modules/esnext.reflect.get-own-metadata");
458
459require("core-js/modules/esnext.reflect.get-own-metadata-keys");
460
461require("core-js/modules/esnext.reflect.has-metadata");
462
463require("core-js/modules/esnext.reflect.has-own-metadata");
464
465require("core-js/modules/esnext.reflect.metadata");
466
467require("core-js/modules/esnext.set.add-all");
468
469require("core-js/modules/esnext.set.delete-all");
470
471require("core-js/modules/esnext.set.difference");
472
473require("core-js/modules/esnext.set.every");
474
475require("core-js/modules/esnext.set.filter");
476
477require("core-js/modules/esnext.set.find");
478
479require("core-js/modules/esnext.set.from");
480
481require("core-js/modules/esnext.set.intersection");
482
483require("core-js/modules/esnext.set.is-disjoint-from");
484
485require("core-js/modules/esnext.set.is-subset-of");
486
487require("core-js/modules/esnext.set.is-superset-of");
488
489require("core-js/modules/esnext.set.join");
490
491require("core-js/modules/esnext.set.map");
492
493require("core-js/modules/esnext.set.of");
494
495require("core-js/modules/esnext.set.reduce");
496
497require("core-js/modules/esnext.set.some");
498
499require("core-js/modules/esnext.set.symmetric-difference");
500
501require("core-js/modules/esnext.set.union");
502
503require("core-js/modules/esnext.string.at");
504
505require("core-js/modules/esnext.string.code-points");
506
507require("core-js/modules/esnext.string.match-all");
508
509require("core-js/modules/esnext.string.replace-all");
510
511require("core-js/modules/esnext.symbol.dispose");
512
513require("core-js/modules/esnext.symbol.observable");
514
515require("core-js/modules/esnext.symbol.pattern-match");
516
517require("core-js/modules/esnext.weak-map.delete-all");
518
519require("core-js/modules/esnext.weak-map.from");
520
521require("core-js/modules/esnext.weak-map.of");
522
523require("core-js/modules/esnext.weak-set.add-all");
524
525require("core-js/modules/esnext.weak-set.delete-all");
526
527require("core-js/modules/esnext.weak-set.from");
528
529require("core-js/modules/esnext.weak-set.of");
530
531require("core-js/modules/web.dom-collections.for-each");
532
533require("core-js/modules/web.dom-collections.iterator");
534
535require("core-js/modules/web.queue-microtask");
536
537require("core-js/modules/web.url");
538
539require("core-js/modules/web.url.to-json");
540
541require("core-js/modules/web.url-search-params");
542
543var main = function main() {
544 Promise.resolve().then(function () {
545return console.log('then');
546 });
547};
548
549main();
output(Chrome86)
1"use strict";
2
3require("core-js/modules/esnext.array.last-index");
4
5require("core-js/modules/esnext.array.last-item");
6
7require("core-js/modules/esnext.composite-key");
8
9require("core-js/modules/esnext.composite-symbol");
10
11require("core-js/modules/esnext.map.delete-all");
12
13require("core-js/modules/esnext.map.every");
14
15require("core-js/modules/esnext.map.filter");
16
17require("core-js/modules/esnext.map.find");
18
19require("core-js/modules/esnext.map.find-key");
20
21require("core-js/modules/esnext.map.from");
22
23require("core-js/modules/esnext.map.group-by");
24
25require("core-js/modules/esnext.map.includes");
26
27require("core-js/modules/esnext.map.key-by");
28
29require("core-js/modules/esnext.map.key-of");
30
31require("core-js/modules/esnext.map.map-keys");
32
33require("core-js/modules/esnext.map.map-values");
34
35require("core-js/modules/esnext.map.merge");
36
37require("core-js/modules/esnext.map.of");
38
39require("core-js/modules/esnext.map.reduce");
40
41require("core-js/modules/esnext.map.some");
42
43require("core-js/modules/esnext.map.update");
44
45require("core-js/modules/esnext.math.clamp");
46
47require("core-js/modules/esnext.math.deg-per-rad");
48
49require("core-js/modules/esnext.math.degrees");
50
51require("core-js/modules/esnext.math.fscale");
52
53require("core-js/modules/esnext.math.iaddh");
54
55require("core-js/modules/esnext.math.imulh");
56
57require("core-js/modules/esnext.math.isubh");
58
59require("core-js/modules/esnext.math.rad-per-deg");
60
61require("core-js/modules/esnext.math.radians");
62
63require("core-js/modules/esnext.math.scale");
64
65require("core-js/modules/esnext.math.seeded-prng");
66
67require("core-js/modules/esnext.math.signbit");
68
69require("core-js/modules/esnext.math.umulh");
70
71require("core-js/modules/esnext.number.from-string");
72
73require("core-js/modules/esnext.observable");
74
75require("core-js/modules/esnext.promise.try");
76
77require("core-js/modules/esnext.reflect.define-metadata");
78
79require("core-js/modules/esnext.reflect.delete-metadata");
80
81require("core-js/modules/esnext.reflect.get-metadata");
82
83require("core-js/modules/esnext.reflect.get-metadata-keys");
84
85require("core-js/modules/esnext.reflect.get-own-metadata");
86
87require("core-js/modules/esnext.reflect.get-own-metadata-keys");
88
89require("core-js/modules/esnext.reflect.has-metadata");
90
91require("core-js/modules/esnext.reflect.has-own-metadata");
92
93require("core-js/modules/esnext.reflect.metadata");
94
95require("core-js/modules/esnext.set.add-all");
96
97require("core-js/modules/esnext.set.delete-all");
98
99require("core-js/modules/esnext.set.difference");
100
101require("core-js/modules/esnext.set.every");
102
103require("core-js/modules/esnext.set.filter");
104
105require("core-js/modules/esnext.set.find");
106
107require("core-js/modules/esnext.set.from");
108
109require("core-js/modules/esnext.set.intersection");
110
111require("core-js/modules/esnext.set.is-disjoint-from");
112
113require("core-js/modules/esnext.set.is-subset-of");
114
115require("core-js/modules/esnext.set.is-superset-of");
116
117require("core-js/modules/esnext.set.join");
118
119require("core-js/modules/esnext.set.map");
120
121require("core-js/modules/esnext.set.of");
122
123require("core-js/modules/esnext.set.reduce");
124
125require("core-js/modules/esnext.set.some");
126
127require("core-js/modules/esnext.set.symmetric-difference");
128
129require("core-js/modules/esnext.set.union");
130
131require("core-js/modules/esnext.string.at");
132
133require("core-js/modules/esnext.string.code-points");
134
135require("core-js/modules/esnext.symbol.dispose");
136
137require("core-js/modules/esnext.symbol.observable");
138
139require("core-js/modules/esnext.symbol.pattern-match");
140
141require("core-js/modules/esnext.weak-map.delete-all");
142
143require("core-js/modules/esnext.weak-map.from");
144
145require("core-js/modules/esnext.weak-map.of");
146
147require("core-js/modules/esnext.weak-set.add-all");
148
149require("core-js/modules/esnext.weak-set.delete-all");
150
151require("core-js/modules/esnext.weak-set.from");
152
153require("core-js/modules/esnext.weak-set.of");
154
155require("core-js/modules/web.immediate");
156
157const main = () => {
158 Promise.resolve().then(() => console.log('then'));
159};
160
161main();

Chrome 86 をターゲットにしたビルドでは、 core-js/modules/es.promise が追加されていません。このように、ターゲットに合わせて自動で不要なものを削減する機能です。

useBuiltIns: 'usage'

useBuildIns に usage を指定する場合、 targets に合わせ、不足しているものを自動で挿入します。

input
1const main = () => {
2 Promise.resolve().then(() => console.log('then'));
3}
4
5main();
.babelrc
1{
2 "presets": [
3 [
4 "@babel/preset-env",
5 {
6 "targets": {
7 "ie": "11"
8 },
9 "useBuiltIns": "usage",
10 "corejs": {
11 "version": 3,
12 "proposal": false
13 }
14 }
15 ]
16 ]
17}
output
1"use strict";
2
3require("core-js/modules/es.object.to-string");
4
5require("core-js/modules/es.promise");
6
7var main = function main() {
8 Promise.resolve().then(function () {
9 return console.log('then');
10 });
11};
12
13main();

core-js/modules/es.promise と、 core-js/modules/es.object.to-string が挿入されました。 なお、 Chrome 向けの場合は Polyfill は挿入されません。

output
1"use strict";
2
3const main = () => {
4 Promise.resolve().then(() => console.log('then'));
5};
6
7main();

比較

useBuiltIns では、 usage を使用する方が使用されている機能に応じて Polyfill を挿入するため、 entry で一括で挿入するよりもビルドサイズは小さくなります。 ただし、 usage は現状 experimental であること、 core-js@3 に依存すること、機能の利用状況を走査するためビルドが遅くなる可能性があること、がプロジェクトにおいて問題ないか検討した上で採用する必要があります。 なお、 **async-await** については、上記の **@babel/plugin-transform-runtime** を使用して **runtimeRegenerator** を挿入する必要があります。 @babel/plugin-transform-runtime の項の通り core-js をオフにする(デフォルトでオフなので、オプションを指定しない)ことで、 regeneratorRuntime のみ挿入することができます。

総括

@babel/polyfill を既に使用している場合、もしくは async-await が存在している場合は @babel/plugin-transform-runtime を使用するのが良いでしょう。 大掛かりな工事なく既存のコードが Polyfill を意識していないコードである場合、 polyfill.io の使用もしくは @babel/preset-env の useBuiltIns を usage で使うのがもっとも導入が簡単かと思います。 前述の通り polyfill.io はビルドバージョンを超えてキャッシュが効き得る / 他サイトでのキャッシュが生き得る(かつて UMD が見た夢ですね……)可能性があるため、一概にどちらが早いとは断定できません。 polyfill.io という外部サービスに依存することを勘案、計測し、必要に応じて判断するのが良いでしょう。

TypeError: Class constructor App cannot be invoked without 'new'

@babel/preset-env でのビルドを行う場合、 es6 class の継承まわりで上記のバグが発生することがあります。 原因は、 es6 class を継承したものを es5 にトランスパイルすることにより発生しているようです。 多くのライブラリは es5 向けビルドで提供されているかと思いますが、ものによっては main として es6 が export されており、このようなバグに遭遇するようです。(筆者は flux-utils で遭遇しました) 詳細と対処方法はこちらの記事をご参考ください。

https://qiita.com/masato_makino/items/00953951bbecbb8e7c9b

上記の通り、解決には副作用が伴い、具体的には babel のビルドターゲットに制約が生まれます。そのため、 useBuiltIns による Polyfill が使えません。 根本解決できない場合は、 transform-runtime による Polyfill を使うことでお茶を濁すのが良いかと思われます。

おわりに

この記事は弁護士ドットコムアドベントカレンダー 2020 の 5 日目の記事でした。 (業務委託としてお手伝いさせていただいています!) 明日は @matsuyoshi30 さんの記事です!