gulp-babelでES6(ES2015)をES5に変換してNode.jsでもブラウザでも使えるようにUMD化する

JSプラグインを作る際、BabelでES6を使いつつ、Node.jsでもブラウザでもどちらの環境でも使えるようにする方法の紹介です。

ES6→ES5に変換


npm i gulp gulp-babel babel-core babel-preset-env -D

まずは、必要となるライブラリをインストールします。gulp-babel はgulpでbabelを使えるようにするもので、 gulp-core はbabelの実行に必要なもの、 babel-preset-env はES5に変換する際のプリセットです。


class Foo {
  bar() {
    alert('bar');
  }
}

コンパイルするサンプルプログラムとして script.es6 ファイルを作成し、このように書いておきます。


const gulp = require('gulp');
const babel = require('gulp-babel');

gulp.task('compile', function() {
  gulp
    .src('script.es6')
    .pipe(babel({
      presets: ['env']
    }))
    .pipe(gulp.dest('./'));
});

gulpfile.js を作成します。


gulp compile

あとは、 gulp コマンドでコンパイルします。


'use strict';

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 Foo = function () {
  function Foo() {
    _classCallCheck(this, Foo);
  }

  _createClass(Foo, [{
    key: 'bar',
    value: function bar() {
      alert('bar');
    }
  }]);

  return Foo;
}();

コンパイルできたら同じディレクトリにこのような script.js ができているはずです。


const gulp = require('gulp');
const babel = require('gulp-babel');

gulp.task('compile', function() {
  gulp
    .src('script.es6')
    .pipe(babel({
      presets: [
        ['env', {
          'targets': {
            'browsers': ['last 2 versions', 'safari >= 7']
          }
        }]
      ]
    }))
    .pipe(gulp.dest('./'));
});

また、 targets にサポートするブラウザを指定すればその環境に合わせてコンパイルしてくれます。

他にもドキュメントを読むと色々 オプション があります。

UMD化

Node.jsでは importrequire で、ブラウザからは script 要素から利用したい場合、両方に対応する必要があります。そこで、よく使われているのが UMD(Universal Module Definition) です。


(function (root, factory) {
  // AMDのとき
  if (typeof define === 'function' && define.amd) {
    define([], factory);
  }
  // CommonJS(Node.js)のとき
  else if (typeof exports === 'object') {
    module.exports = factory();
  }
  // ブラウザのとき
  else {
    root.Lib = factory();
  }
})(this, function () {

  class Foo {
    bar() {
      alert('bar');
    }
  }

  return Foo;
});

色々書き方はありますが、 UMD化 するとこんな感じで書くことができます。これを先ほどと同じくコンパイルしてみます。


'use strict';

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; }; }();

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 (root, factory) {
  // AMDのとき
  if (typeof define === 'function' && define.amd) {
    define([], factory);
  }
  // CommonJS(Node.js)のとき
  else if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object') {
      module.exports = factory();
    }
    // ブラウザのとき
    else {
        root.Lib = factory();
      }
})(undefined, function () {
  var Foo = function () {
    function Foo() {
      _classCallCheck(this, Foo);
    }

    _createClass(Foo, [{
      key: 'bar',
      value: function bar() {
        alert('bar');
      }
    }]);

    return Foo;
  }();

  return Foo;
});

コンパイルはできましたが、このプログラムは正しく動作しません。コンパイル前とコンパイル後で見てみると、 thisundefined になってしまっています。


const gulp = require('gulp');
const babel = require('gulp-babel');

gulp.task('compile', function() {
  gulp
    .src('script.es6')
    .pipe(babel({
      presets: [
        ['env', { 'modules': false }]
      ]
    }))
    .pipe(gulp.dest('./'));
});

これを防ぐには gulpfile.js を少し修正します。'modules': false を加えるとモジュールとして認識されなくなり正しくコンパイルできるようになります。


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; }; }();

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 (root, factory) {
  // AMDのとき
  if (typeof define === 'function' && define.amd) {
    define([], factory);
  }
  // CommonJS(Node.js)のとき
  else if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object') {
      module.exports = factory();
    }
    // ブラウザのとき
    else {
        root.Lib = factory();
      }
})(this, function () {
  var Foo = function () {
    function Foo() {
      _classCallCheck(this, Foo);
    }

    _createClass(Foo, [{
      key: 'bar',
      value: function bar() {
        alert('bar');
      }
    }]);

    return Foo;
  }();

  return Foo;
});

コンパイルしてみると、 this のままであることが確認できます。