かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

今更のバベる。 Babel 7を試してみたメモ

BabelやWebpackとかを業務で使う機会がなくて、なんとなくな感じだったので自分の手動かして設定しながら試してみました。Babel 7になっていて検索で出てくる情報だとうまく行かなかったり試行錯誤したのでメモ。

Babelとは

babel + webpack の構成で合わせて説明されている場合が多いので、まずbabelとwebpackを切り分けて理解する必要がありました。
雑な理解👇

babel

Babel is a JavaScript compiler

Babelはjavascriptコンパイラ。新しい書き方を古い書き方に変換できる。

webpack

webpack is a static module bundler for modern JavaScript applications.

webpackはモジュールバンドラ。別々のファイルに分かれてあるものの依存関係を見つけて1つのファイルにまとめる。

ざっくり言えば、
ES2015とかES2017とかで書かれたJavaScriptのファイルをbabelでES2015に変換して、
webpackで1つのJavaScriptファイルにまとめる。
という使われ方が多いと言うことですね。

本記事ではbabelについてだけ。

babelのインストール

babel7からインストールするパッケージの指定方法が@babel/〜に変更になっていました。

$ npm install --save-dev @babel/core @babel/cli @babel/preset-env

※ babel7より前の記事ではbabel-cliとかbabel-preset-envと書かれているので、babel7の時は置き換えて読む必要があります。

設定ファイルbabel.config.jsの作成

$ touch babel.config.js

設定ファイル

// babel.config.js
const presets =  [
  ["@babel/preset-env"]
];
module.exports = { presets }

単純に@babel/preset-envを使うだけの設定。
※ これもbabel7より前の記事では["env"]とか["preset-env"]とか書かれていて、そう指定するとエラーになってしまうので注意が必要。

対象ブラウザの指定

babelで変換する対象はbabel.config.jstargetsパラメーターで指定します。
cf. @babel/preset-env · Babel

ブラウザのバージョンを直接指定する場合はオブジェクト形式で

// babel.config.js
const presets =  [
  ["@babel/preset-env", {
    "targets": {
      "chrome": "58",
      "ie": "11" 
    }
  }]
];

Example environments: chrome, opera, edge, firefox, safari, ie, ios, android, node, electron.

electronとかも指定できるようです。

browserslist で指定

browserslistのクエリを使って、>=1% in JP(日本で使われている1%以上)のような指定も可能です。
browserslistはtargetsに直接渡せばOK。複数のクエリを組み合わせる時は配列形式で指定。(1つでも配列にしておいた方が楽かも)

// babel.config.js
const presets =  [
  ["@babel/preset-env", {
    "targets": [">0.25% in JP", "not ie <= 10", "not op_mini all"]
  }]
];

どんなブラウザが対象になるかは👇のサービスで確認ができます。便利!!

targets.browsers での指定は廃止予定

検索していると

{
  "targets": {
    "browsers": ["last 2 version"]
  }
}

のような指定を見かけることもありましたが、この方法はbabel 7では有効だけど将来的に廃止予定なようです。

targets.browsers
A query to select browsers (ex: last 2 versions, > 5%, safari tp) using browserslist.
Note, browsers' results are overridden by explicit items from targets.

Note: this will be removed in later version in favor of just setting "targets" to a query directly.

出典: @babel/preset-env · Babel

参考にする記事がtargets.browsers: ...の方法で書かれている場合は、targets: ...の形式に置き換えて参考にするのが良さそうです。

動作テスト

構成

/
 |- package.json
 |- babel.config.js
 |- /src
      |- index.js

package.jsonにバベるためのnpm scriptsを書きます

"scripts": {
  "babel": "./node_modules/.bin/babel src --out-dir dist/js"
}
// src/index.js
import { foo, bar } from './test.js'
console.log( foo() );

const countup = (x) => x + 1;
console.log( countup(1) )

Promise.resolve().then(res => console.log("Promise end") );

バベる

$ npm run babel

👇

// dist/js/index.js
var _test = require("./test.js");

console.log((0, _test.foo)());

var countup = function countup(x) {
  return x + 1;
};

console.log(countup(1));
Promise.resolve().then(function (res) {
  return console.log("Promise end");
});

アロー関数や変数定義のconstimport文などが変換されました!

@babel/polyfill

  • polyfill (ポリフィル) ... 壁の穴埋め剤 Polyfilla を動詞化。転じて、古いブラウザやまだ実装されていない新しい機能など足りない機能の穴を埋めることっぽい。

PromiseWeakMap, Array.from, Object.assign, Array.prototype.includesなど文法ではなく、機能を対応していないブラウザ用に変換
つまり、コードを直接変換して対応させる機能って事かな。

インストール

$ npm install --save @babel/polyfill

設定 useBuiltIns: "usage"

babel.config.jsuseBuiltIns: "usage"オプションを追加すると、必要なものだけ自動的にプラグインをインストールして変換してくれる

Now luckily for us, we're using the env preset which has a "useBuiltIns" option that when set to "usage" will practically apply the last optimization mentioned above where you only include the polyfills you need. cf. Usage Guide · Babel
@babel/polyfill · Babel

// babel.config.js
const presets =  [
  ["@babel/preset-env", {
    "targets": [">0.25% in JP", "not ie <= 10", "not op_mini all"],
    // 必要なpolyfillプラグインを自動的に使う
    "useBuiltIns": "usage",
  }]
];
動作テスト
// src/index.js
Promise.resolve().then(res => console.log("Promise end") );

👇 バベる $ npm run babel

// dist/js/index.js
require("core-js/modules/es6.promise");
Promise.resolve().then(function (res) {
  return console.log("Promise end");
});

おそらくPromiseを定義してあるcore-js/modules/es6.promiseプラグインが読み込まれました。

async/await

既に結構使われている印象があるES2017のasync/awaitでも試してみます。

// src/async-test.js
const asyncSampleFunc = () => {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, 100, "resolve");
  });
};

async function getAsyncVal() {
  const val = await asyncSampleFunc();
  return `Complete Async: ${val}`
}

getAsyncVal()
.then((res) => console.log(res))
.catch((err) => console.log(err));

👇 バベる $ npm run babel

// dist/js/async-test.js
require("regenerator-runtime/runtime");
require("core-js/modules/es6.promise");

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

var asyncSampleFunc = function asyncSampleFunc() {
  return new Promise(function (resolve, reject) {
    setTimeout(resolve, 100, "resolve");
  });
};

function getAsyncVal() {
  return _getAsyncVal.apply(this, arguments);
}

function _getAsyncVal() {
  _getAsyncVal = _asyncToGenerator(
  /*#__PURE__*/
  regeneratorRuntime.mark(function _callee() {
    var val;
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return asyncSampleFunc();

          case 2:
            val = _context.sent;
            return _context.abrupt("return", "Complete Async: ".concat(val));

          case 4:
          case "end":
            return _context.stop();
        }
      }
    }, _callee, this);
  }));
  return _getAsyncVal.apply(this, arguments);
}

getAsyncVal().then(function (res) {
  return console.log(res);
}).catch(function (err) {
  return console.log(err);
});

すっごい色々追加されてますが、変換できたっぽいです!

最近、実際にasync/awaitを使ったプロダクトを作ってる会社の人に、「async/awaitはbabelでも変換できないよ」って言われてたので、出来ないものかと思っていたのですが、ふつーに変換できてしまったっぽい.... (出来ない時代があったのですかね?

import文がrequireに変換されるのを防ぐ

デフォルトではimport文がCommonJSの?requireに変換されるようです。
検索すると、babelでiomport文のままにする方法が特に説明なく書かれているケースが多かったのですが、webpackとかで1つにまとめる時にimport文の方がいろいろ都合が良いのでしょうか?

設定 modules オプション

babel.config.jsmodules: falseを指定するとimport文がそのままになるようです。

modules
"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false, defaults to "auto".
Enable transformation of ES6 module syntax to another module type.
Setting this to false will not transform modules.
Also note that cjs is just an alias for commonjs.

// babel.config.js
const presets =  [
  ["@babel/preset-env", {
    // import文をそのままにする
    "modules": false,
    "targets": [">0.25% in JP", "not ie <= 10", "not op_mini all"],
    // 必要なpolyfillプラグインを自動的に使う
    "useBuiltIns": "usage",
  }]
];
動作テスト
// src/index.js
import { foo, bar } from './test.js'
console.log( foo() );

const countup = (x) => x + 1;
console.log( countup(1) )

Promise.resolve().then(res => console.log("Promise end") );

👇バベる npm run babel

// dist/js/index.js
import "core-js/modules/es6.promise";
import { foo, bar } from './test.js';
console.log(foo());

var countup = function countup(x) {
  return x + 1;
};

console.log(countup(1));
Promise.resolve().then(function (res) {
  return console.log("Promise end");
});

import文がそのまま出力されました!

 

これで、なんとなくですが新しい書き方のJavaScriptをバベってブラウザ対応させることができるようになった気がします。
と、qiita投稿したことなかったので、qiitaに本記事のざっくりしたものを書いてみました。
babel 7 触ってみたメモ - Qiita

次はwebpackの理解に進みたいと思います。


[WIP] babel.config.jsと.babelrcの使い方の違いについて。

babel7からbabel.config.jsがプロジェクトのrootにあれば、これを見つけて設定を読み込むっぽい。
以前の.babelrcとは別アプローチで、.babelrcも引き続き使えるみたいだけど、ちょっと英語ちからが足りなくて違いを理解できてない。
cf. Configure Babel · Babel

/
 |- package.json
 |- babel.config.js
 |- /src
      |- .babelrc
      |- index.js

こんな構成で試してみたら、index.jsは同じディレクトリにある.babelrcの設定で変換されたので、全体の変換の設定をbabel.config.jsで行って、特別に別な指定を行いたい部分に.babelrcを使うのでしょうか?

ドキュメント( Config Files · Babel )には「babel.config.jsで指定できるから、それぞれのディレクトリに.babelrc作る必要はなくなる」みたいなことも書かれてあるっぽいので....ぬーん....
詳しい人に教えていただきたい。よろしくおねがいします。


[参考]

シャングリ・ラ 上 (角川文庫)

シャングリ・ラ 上 (角川文庫)