babel-plugin-external-helpers について

rollup の babel 用プラグインである rollup-plugin-babel の README に,

npm install --save-dev babel-preset-es2015 babel-plugin-external-helpers

という記載があります. babel-preset-es2015 はよく目にしますが,babel-plugin-external-helpers は初めて見たため,どういうものか調べてみました.

babel-plugin-external-helpers

babel-plugin-external-helpers は,babel での変換時に各ファイルごとに出力される共通コードを,グローバル変数を用いて1つにまとめることで,重複するコード出力を削減するプラグインです.

以下の2つのファイルを babel で変換する場合を例として,出力の違いを説明します.

src/Hoge.js

export default class Hoge {
  constructor(name) {
    this.name = name;
  }

  hello() {
    return `Hello ${this.name}`;
  }
};

src/Fuga.js

export default class Fuga {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  hello() {
    return `Hello ${this.name} (${this.age})`;
  }
};

例:babel でプラグインを用いずに変換した場合

babel-preset-es2015 をプリセットとして babel src -d lib で変換すると,以下のような結果となります.

変換後のlib/Hoge.jslib/Fuga.jsには,var _createClass = function () { ...function _classCallCheck(instance, Constructor) { ... といった class 生成に関連する共通コードが出力されています.

変換後

lib/Hoge.js

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Hoge = function () {
  function Hoge(name) {
    _classCallCheck(this, Hoge);

    this.name = name;
  }

  _createClass(Hoge, [{
    key: "hello",
    value: function hello() {
      return "Hello " + this.name;
    }
  }]);

  return Hoge;
}();

exports.default = Hoge;
;

lib/Fuga.js

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Fuga = function () {
  function Fuga(name, age) {
    _classCallCheck(this, Fuga);

    this.name = name;
    this.age = age;
  }

  _createClass(Fuga, [{
    key: "hello",
    value: function hello() {
      return "Hello " + this.name + " (" + this.age + ")";
    }
  }]);

  return Fuga;
}();

exports.default = Fuga;
;

例:babel で babel-plugin-external-helpers を用いて変換した場合

babel-preset-es2015 と babel-plugin-external-helpers を用いて変換すると,以下のような出力となります.

babel-plugin-external-helpers を用いずに変換した場合と比較して,出力コードが削減されました.
これは,babelHelpers というグローバル変数のプロパティとして,class 生成に関連するコードが定義されていることを前提とした出力となっているためです.
当然ながら,出力後の各ファイルでは babelHelpers の定義はされていないため,このままでは動作しません.

変換後

lib/Hoge.js

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

var Hoge = function () {
  function Hoge(name) {
    babelHelpers.classCallCheck(this, Hoge);

    this.name = name;
  }

  babelHelpers.createClass(Hoge, [{
    key: "hello",
    value: function hello() {
      return "Hello " + this.name;
    }
  }]);
  return Hoge;
}();

exports.default = Hoge;
;

lib/Fuga.js

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

var Fuga = function () {
  function Fuga(name, age) {
    babelHelpers.classCallCheck(this, Fuga);

    this.name = name;
    this.age = age;
  }

  babelHelpers.createClass(Fuga, [{
    key: "hello",
    value: function hello() {
      return "Hello " + this.name + " (" + this.age + ")";
    }
  }]);
  return Fuga;
}();

exports.default = Fuga;
;

babelHelpers が定義されたコードの取得

まず,babel-cli をインストールします.

$ npm install --dev-dependencies babel-cli

babel-cli をインストールすると babel-external-helpers コマンドも導入されます. babel-external-helpers コマンドは,babel で変換したスクリプトの利用法に合わせて,3種類の babelHelpers の定義コードの出力が可能です.

$ $(npm/bin)/babel-external-helpers -t global # Node.jsで利用する場合
$ $(npm/bin)/babel-external-helpers -t var # ブラウザで利用する場合
$ $(npm/bin)/babel-external-helpers -t umd # UMDで利用する場合

例えば,babel で変換したスクリプトをブラウザで利用する場合は,まず babel-external-helpers コマンドで lib/util.js を作成します.

$ $(npm/bin)/babel-external-helpers -t var > lib/util.js

次に,html から以下のように読み込みます.このとき lib/util.js を先に読み込む必要があります

<script src="lib/util.js"></script>
<script src="lib/Hoge.js"></script>

まとめ

開発の規模によっては,babel-plugin-external-helpers を用いて,共通するコードの出力を1箇所にまとめておくと,ファイルサイズの削減が可能となります.

参考