UMD を babel-preset-es2015 で transpile すると UMD として機能しなくなる


例えば次のような js (UMD script) があったとします。

umd.js
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global.foo = factory());
}(this, (function () {
  'use strict';

  class foo {}

  return foo;
})));

これを babel の es2015 preset で変換すると次のようになります。

transpiled.js
'use strict';

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };

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

(function (global, factory) {
  (typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : global.foo = factory();
})(undefined, function () {
  'use strict';

  var foo = function foo() {
    _classCallCheck(this, foo);
  };

  return foo;
});

ここで問題は、global パラメータに渡っている this の部分です。この this はスクリプトのトップレベルの this のため、babel の変換をかけると、undefined に置き換えられます (ES module のトップレベルの this は undefined になる、という仕様があるためです。)

UMD はこのトップレベルの this に依存して、グローバル変数にモジュールをエクスポートしているため、この変換がかかると UMD として機能しなくなってしまいます。

対応策

この現象を回避するためには、以下のように es2015 プリセットに対して下のようにオプションを渡すことで現象を回避することができます。

.babelrc
{
  "presets": [
    [   
      "es2015",
      {   
        "modules": false
      }   
    ]   
  ]
}

上のように { modules: false } オプションを与える事で、UMD スクリプトが ES module として解釈される事を防ぐことができ、トランスパイルの結果は下のようになります。

transpiled.js
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };

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

(function (global, factory) {
  (typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : global.foo = factory();
})(this, function () {
  'use strict';

  var foo = function foo() {
    _classCallCheck(this, foo);
  };

  return foo;
});

このように this が消されることが防がれるため、UMD としての機能を維持することができます

参考:
- https://github.com/babel/babel/issues/5082