JavaScript
babel

Babel 7 の主な変更点まとめ

はじめに

今更ですが、Babel 7 の主な変更点をまとめた備忘録です。

ほとんどの内容は公式ドキュメントである「Upgrade to Babel 7 」と「Usage Guide
」を参考にしているため、既にこちらを読んで理解した方々には不要な記事だと思います。

また、普段 Babel と webpack を併用しているため、Babel CLI や.babelrcなどに関しては触れません。

記事本文でも紹介しますが、Babel 7 にマイグレーションしたサンプル(webpack と併用)は GitHub に置いてあります。

hira777/webpack-with-babel7

本記事での「プロポーザル」の定義

以下のような意味があるが

  • ECMAScript の新たな仕様として提案された機能
  • ECMAScript の新たな仕様として提案され、策定中の機能
  • ECMAScript の新たな仕様として追加する機能の提案書

本記事では「(ECMAScript の新たな仕様として提案され、)策定中の機能」ぐらいな認識で問題ない。

そのため、以下の言葉の意味は大体同じ。

  • 「Stage 4 未満のプロポーザル」 = 「Stage 4 未満の策定中の機能」

プロポーザルや Stage などをより詳しく知りたい方は以下を参照。

目次

yearly presets は非推奨になった

以下のような yearly presets は非推奨になった(Babel 6 から非推奨だった気がするが、一応記載)。

  • babel-preset-es2015
  • babel-preset-es2016
  • babel-preset-es2017
  • babel-preset-latest

そのため preset は@babel/preset-envを利用する。

Babel 6

babel-preset-es2015を利用する場合。

npm install --save-dev babel-preset-es2015

Babel 7

npm install --save-dev @babel/preset-env

stage-x presets は非推奨になった

preset-stage-0などの stage-x presets は非推奨になった。

Babel 6

preset-stage-1を利用する場合。

npm install --save-dev babel-preset-stage-1

Babel 7

個別にプラグインをインストールする必要がある(以下は 2018-10-11 時点での Stege 1 のプラグイン)。

npm install --save-dev @babel/plugin-proposal-export-default-from @babel/plugin-proposal-logical-assignment-operators @babel/plugin-proposal-optional-chaining @babel/plugin-proposal-pipeline-operator @babel/plugin-proposal-nullish-coalescing-operator @babel/plugin-proposal-do-expressions

babel-upgrade を利用して Babel 7 へのアップグレードを自動で行える(環境によっては完全にアップグレードできるわけではない)

babel-upgradeを利用すれば、Babel 7 へのアップグレードに伴う依存関係、設定ファイルや JavaScript ファイルをに自動的に更新できる。

例えば、以下のようなpackage.jsonのある階層でnpx babel-upgrade --writeを実行すると

{
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.4",
    "babel-preset-env": "^1.7.0",
    "babel-preset-stage-1": "^6.24.1"
  }
}

以下のようにpackage.jsonが更新される。

{
  "devDependencies": {
    "@babel/core": "^7.0.0",
    "@babel/plugin-proposal-class-properties": "^7.0.0",
    "@babel/plugin-proposal-decorators": "^7.0.0",
    "@babel/plugin-proposal-do-expressions": "^7.0.0",
    "@babel/plugin-proposal-export-default-from": "^7.0.0",
    "@babel/plugin-proposal-export-namespace-from": "^7.0.0",
    "@babel/plugin-proposal-function-sent": "^7.0.0",
    "@babel/plugin-proposal-json-strings": "^7.0.0",
    "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
    "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
    "@babel/plugin-proposal-numeric-separator": "^7.0.0",
    "@babel/plugin-proposal-optional-chaining": "^7.0.0",
    "@babel/plugin-proposal-pipeline-operator": "^7.0.0",
    "@babel/plugin-proposal-throw-expressions": "^7.0.0",
    "@babel/plugin-syntax-dynamic-import": "^7.0.0",
    "@babel/plugin-syntax-import-meta": "^7.0.0",
    "@babel/preset-env": "^7.0.0",
    "babel-loader": "^8.0.0"
  }
}

現時点では、webpack.config.jsなどの設定ファイルは更新されないため、開発環境によっては babel-upgrade を利用してもアップグレードを完全に行えない可能性もあるため注意。

webpack.config.jsの更新は今後実装予定である。他にどのような機能を実装する予定なのかはこちらを参照。

babylon がリネームされた

Babel で利用されている JavaScript のパーサーであるbabylon@babel/parserにリネームされた。

多くのパッケージの提供が Scoped Packages に変更された

多くのパッケージは scoped packages として提供されるようになった。

つまり、@babel/preset-envのように@babel/(スコープ)がついて提供されるようになった。

そのため、様々なパッケージがリネームされており、babel-cli -> @babel/cliのようにbabel-がついていたパッケージは、基本的にbabel-@babel/に置き換わっている。

自分が利用しているパッケージがリネームされていないか確認した方が良い(大体リネームされているらしい)。

リネームに伴い、コンフィグの記述も変更

以下はその例

Babel 6

module.exports = {
  presets: ['preset-env'],
  plugins: ['plugin-transform-arrow-functions']
};

以下のようにショートハンド(preset-plugin-の短縮)も利用できた。

module.exports = {
  presets: ['env'],
  plugins: ['transform-arrow-functions']
};

Babel 7

module.exports = {
  presets: ['@babel/preset-env'],
  plugins: ['@babel/plugin-transform-arrow-functions']
};

ショートハンド(preset-plugin-の短縮)は引き続き利用できるが、@babel/の指定は必須。

module.exports = {
  presets: ['@babel/env'],
  plugins: ['@babel/transform-arrow-functions']
};

ショートハンドは便利だが、パッケージ名をフルで書いた方が何のパッケージを利用しているのかを確実且つ即座に理解できる。

全員がショートハンドを利用できることを知っているわけではないため、チーム開発などの時は認識合わせをした方が良いかもしれない。

Stage 4 未満のプロポーザルのパッケージがリネームされた

Stage 4 未満のプロポーザルのパッケージは以下のように-proposal-が付いたものにリネームされた。

  • @babel/plugin-transform-function-bind(Stage 0) -> @babel/plugin-proposal-function-bind
  • @babel/plugin-transform-class-properties(Stage 3) -> @babel/plugin-proposal-class-properties

プロポーザルの Stage が 4 に移行したら、パッケージ名がリネームされるので留意しておく。

(と書いておきながら、Stage 4 であるObject Rest/Spread Properties@babel/plugin-proposal-object-rest-spreadで提供されているので、ドキュメントを読み間違えているかもしれない。)

パッケージ名から ECMASCript の Edition は削除された

いくつかのプラグインは名前に-es3-、または-es2015-が付いていたが、以下のように削除された。

  • @babel/plugin-transform-es2015-classes -> @babel/plugin-transform-classes

@babel/polyfill から Stage 4 未満のプロポーザルは削除された

@babel/polyfillを import しても Stage 4 未満のプロポーザルは含まれないようになった。

Babel 6

Babel 6 の@babel/polyfillでは以下のようにcore-js/shimを import している(core-js のバージョンは 2)。

babel/packages/babel-polyfill/src/index.js(6.x)

import 'core-js/shim';

import しているcore-js/shimは以下の通り。

core-js/shim.js

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.object.get-prototype-of');
// ...
// 一部記述を省略
// ...
require('./modules/es7.reflect.has-metadata');
require('./modules/es7.reflect.has-own-metadata');
require('./modules/es7.reflect.metadata');
require('./modules/es7.asap');
require('./modules/es7.observable');
require('./modules/web.timers');
require('./modules/web.immediate');
require('./modules/web.dom.iterable');
module.exports = require('./modules/_core');

様々な Stage のプロポーザルが含まれている。

Babel 7

Babel 7 の@babel/polyfillでは以下のようにcore-js/es6core-js/fn/array/includesなどを import している(core-js のバージョンは 2)。

babel/packages/babel-polyfill/src/index.js(7.x)

// Cover all standardized ES6 APIs.
import 'core-js/es6';

// Standard now
import 'core-js/fn/array/includes';
import 'core-js/fn/string/pad-start';
import 'core-js/fn/string/pad-end';
import 'core-js/fn/symbol/async-iterator';
import 'core-js/fn/object/get-own-property-descriptors';
import 'core-js/fn/object/values';
import 'core-js/fn/object/entries';
import 'core-js/fn/promise/finally';

// Ensure that we polyfill ES6 compat for anything web-related, if it exists.
import 'core-js/web';

Stage 4 未満のプロポーザルは含まれていない。

そのため、Stage 4 未満のプロポーザルを利用したい場合は、以下のように個別に import する必要がある(@babel/polyfillの import はしなくても良い)。

// Stage 3
import 'core-js/fn/array/flat-map';

// Stage 1
import 'core-js/fn/symbol/observable';

どのようなプロポーザルが存在するかは以下の「Below is a list of Stage < 3 proposal polyfills in core-js v2.」を参照。

Remove proposal polyfills in @babel/polyfill

@babel/preset-env の useBuiltIns を利用して、@babel/polyfill から必要な polyfill のみを import できるようになった

以下のように@babel/polyfillを import すると、利用していない不要な polyfill も import されてしまい、トランスパイル後のファイルサイズが非常に大きくなってしまう。

import '@babel/polyfill';

Babel 7 では@babel/preset-envuseBuiltInsという設定を利用し、必要な polyfill のみを import できる。

以下はそのサンプル(webpack と併用)。

hira777/webpack-with-babel7

サンプルの webpack.config.js で指定している Babel の設定は以下の通り(重要な記述だけ抜粋)。

webpack.config.js
const path = require('path');

module.exports = {
  // ...
  // 省略
  // ...
  presets: [
    [
      // プリセットに @babel/preset-env を指定する
      '@babel/preset-env',
      {
        // サポートするブラウザ、この設定に応じて、必要な polyfill のみが import される
        targets: {
          edge: '17',
          firefox: '60',
          chrome: '67',
          safari: '11.1'
        },
        // 必要な polyfill のみを import させたい場合、'usage'を指定する(必須)
        useBuiltIns: 'usage'
      }
    ]
  ]
  // ...
  // 省略
  // ...
};

targetsで指定したブラウザをサポートするために必要な polyfill が import される。useBuiltIns: 'usage'の指定は必須。

また、エントリーポイントである src/app.js は以下の通り。

src/app.js
Promise.resolve().finally();

利用されているPromise.prototype.finally()は、Edge 18 からサポートされている。

今回、targetsedge: '17'を指定しているため、Edge 17 をサポートするためにPromise.prototype.finally()の polyfill が import される。

上記のように Babel の設定をすれば、import '@babel/polyfill';を記述せずに polyfill を import できるが、@babel/polyfill自体はインストールしておく必要があるので注意する。

@babel/preset-env の useBuiltIns を利用する際の注意点

前述の通り@babel/polyfillには Stage 4 未満のプロポーザルは含まれていないため、@babel/preset-envuseBuiltInsを利用する場合でも、プロポーザルを個別に import する必要がある。

NG(サポートしていないブラウザでは動作しない)

const result = [1, 2, 3, 4].flatMap(x => [x * 2]);
console.log(result); // =>  [2, 4, 6, 8]

Array.prototype.flatMap()は Stage 3 のプロポーザルのため、@babel/polyfillには含まれていない。

そのため、上記のコードを@babel/preset-envuseBuiltInsを利用してトランパイルしても、Array.prototype.flatMap()をサポートしていないブラウザでは動作しない。

OK(サポートしていないブラウザでも動作する)

import 'core-js/fn/array/flat-map';

const result = [1, 2, 3, 4].flatMap(x => [x * 2]);
console.log(result); // =>  [2, 4, 6, 8]

利用したいプロポーザル(今回はArray.prototype.flatMap())を import すれば、サポートしていないブラウザでも動作する。

終わり

本記事で記載した変更点は、ざっくり分類すれば以下の通りです。

  • 様々なパッケージがリネームされた
  • @babel/polyfillの変更点
  • @babel/preset-envuseBuiltInsの利用方法

上記を理解すれば、とりあえず Babel 7 へのマイグレーションはできると思います。

変更点をより詳しく知りたい方は以下をご参照ください。