【webpack】【babel】トランスパイルでbabelが作るボイラープレートコードを削減する

[webpack](https://webpack.js.org/)と[babel](https://babeljs.io/)を組み合わせて、es2015からes5へのトランスパイルができます。

# 標準的な設定（`webpack.config.js`）

この方法をググってぱっと出てくる感じだと、`webpack.config.js`で以下のように設定します。

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

module.exports = [{
	context: path.resolve(__dirname, 'src/'),
	entry: {
		sample: './index.js'
	},
	output: {
		path: path.resolve(__dirname, 'dist/'),
		filename: '[name].js',
	},
	resolve: {
		extensions: ['.js'],
	},
	module: {
		loaders: [
			{
				test: /\.js$/,
				exclude: /node_modules/,
				loader: 'babel-loader',
				query: {
					presets: ['es2015']
				}
			}
		]
	},
	plugins: [],
}];
```

## 出力結果
`webpack`を実行して結果ファイルを見ると`class`で書いたjsがトランスパイルされます。

トランスパイル前

```js:トランスパイル前
'use strict';
class Module1 {
	constructor() {
	}
}

module.exports = Module1;
```

トランスパイル後

```js:トランスパイル後（抜粋）
/***/ (function(module, exports, __webpack_require__) {

"use strict";


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

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

module.exports = Module1;

/***/ })
```

と、ここまでは良いのですが、モジュールが増えていくと`_classCallCheck`という`function`が量産されます。

例えば以下のようになります。

```js
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


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

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

module.exports = Module1;

/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


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

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

module.exports = Module2;

/***/ })
/******/ ]);
```

今回の例は`_classCallCheck`のみですが、js内の書き方によっては、`_get`・`_createClass`・`_typeof`・`_toConsumableArray`・`_possibleConstructorReturn`・`_inherits`などの`function`がモジュール毎にできてしまいます。

動くので何の問題もないですが、何だか気に入らないです。

# 対応策を考える
`loaders`でトランスパイルするからこうなるんだと考えました。
`loaders`でトランスパイルするということは、ファイル（モジュール）毎にトランスパイルして、
その後、bundleされるため、ファイル（モジュール）分のボイラープレートコードが作られてしまいます。

これが問題なのであれば、トランスパイルをbundle後にすることができれば、ボイラープレートコードを1つにすることができるのではないでしょうか？
→実際そうでした。というのが本記事の内容です。

# 対応①
bundle後にトランスパイルしたいので、`plugins`で`babel`を実行したいです。
`babel`する`webpack plugin`を探したところ、[babel-webpack-plugin](https://www.npmjs.com/package/babel-webpack-plugin)というのがありました。

使ってみます。

```js:webpack.config.js
'use strict';
const path = require('path');
const BabelPlugin = require('babel-webpack-plugin');

module.exports = [{
	context: path.resolve(__dirname, 'src/'),
	entry: {
		sample: './index.js'
	},
	output: {
		path: path.resolve(__dirname, 'dist/'),
		filename: '[name].js',
	},
	resolve: {
		extensions: ['.js'],
	},
	module: {
		loaders: [
			{
				test: /\.js$/,
				exclude: /node_modules/,
				loader: 'babel-loader',
				// ここではトランスパイルしない
				// query: {
				// 	presets: ['es2015']
				// }
			}
		]
	},
	plugins: [
		new BabelPlugin({
			test: /\.js$/,
			presets: ['es2015']
		}),
	],
}];
```

`webpack`実行後↓

```js:トランスパイル後
'use strict';

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

///・・・・・

/* 1 */
/***/function (module, exports, __webpack_require__) {

	"use strict";

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

	module.exports = Module1;

	/***/
},
/* 2 */
/***/function (module, exports, __webpack_require__) {

	"use strict";

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

	module.exports = Module2;

	/***/
}]
/******/);
```

どうやらうまくできたようです！
`_classCallCheck`が一つになりました。

## 問題
`webpack`の`output`設定で、`libraryTarget: 'umd'`などとすると問題が起きます。

```js:libraryTarget='umd'の結果抜粋
(function webpackUniversalModuleDefinition(root, factory) {
	if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object' && (typeof module === 'undefined' ? 'undefined' : _typeof(module)) === 'object') module.exports = factory();else if (typeof define === 'function' && define.amd) define([], factory);else {
		var a = factory();
		for (var i in a) {
			((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object' ? exports : root)[i] = a[i];
		}
	}
})(undefined, function () {
```

`webpackUniversalModuleDefinition`を即時実行しますが、`root`に与えられる引数が`undefined`になってしまいます。（本来は`this`となるべき）
これは`babel`の仕様でこうなってしまうようです。

[参考：Babelで top level this が undefinedになって困った件](http://lealog.hateblo.jp/entry/2015/04/27/203147)

# 対応②
`this`がtop levelにならないようにしてやればいいみたいなので、
トランスパイル前に、
`(function(){ ～ }).call(typeof global !== "undefined" ? global : window);`
という`function`でwrapします。
今回は[wrapper-webpack-plugin](https://www.npmjs.com/package/wrapper-webpack-plugin)というpluginを使ってこれを実現してみます。
（wrapするプラグインはいくつかある様子なので好みのものを選んでください）

```js:webpack.config.js
'use strict';
const path = require('path');
const WrapperPlugin = require('wrapper-webpack-plugin');
const BabelPlugin = require('babel-webpack-plugin');

module.exports = [{
	context: path.resolve(__dirname, 'src/'),
	entry: {
		sample: './index.js'
	},
	output: {
		path: path.resolve(__dirname, 'dist/'),
		filename: '[name].js',
		libraryTarget: 'umd',
	},
	resolve: {
		extensions: ['.js'],
	},
	module: {
		loaders: [
			{
				test: /\.js$/,
				exclude: /node_modules/,
				loader: 'babel-loader',
			}
		]
	},
	plugins: [
		new WrapperPlugin({
			test: /\.js$/,
			header: '(function(){\n',
			footer: '\n}).call(typeof global !== "undefined" ? global : window);'
		}),
		new BabelPlugin({
			test: /\.js$/,
			presets: ['es2015']
		}),
	],
}];
```


`webpack`実行後↓

```js:トランスパイル後
///・・・・・
(function () {
	(function webpackUniversalModuleDefinition(root, factory) {
		if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object' && (typeof module === 'undefined' ? 'undefined' : _typeof(module)) === 'object') module.exports = factory();else if (typeof define === 'function' && define.amd) define([], factory);else {
			var a = factory();
			for (var i in a) {
				((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object' ? exports : root)[i] = a[i];
			}
		}
	})(this, function () {

///・・・・・・・・・・

}).call(typeof global !== "undefined" ? global : window);
```

どうやらうまくできたようです！
`webpackUniversalModuleDefinition`の`root`に与えられる引数が`this`になりました。

ついでなので、`_classCallCheck`等のボイラープレートコードがグローバルスコープになってしまう問題も、
[wrapper-webpack-plugin](https://www.npmjs.com/package/wrapper-webpack-plugin)を使って回避します。

```js:webpack.config.js
'use strict';
const path = require('path');
const WrapperPlugin = require('wrapper-webpack-plugin');
const BabelPlugin = require('babel-webpack-plugin');

module.exports = [{
	context: path.resolve(__dirname, 'src/'),
	entry: {
		sample: './index.js'
	},
	output: {
		path: path.resolve(__dirname, 'dist/'),
		filename: '[name].js',
		libraryTarget: 'umd',
	},
	resolve: {
		extensions: ['.js'],
	},
	module: {
		loaders: [
			{
				test: /\.js$/,
				exclude: /node_modules/,
				loader: 'babel-loader',
			}
		]
	},
	plugins: [
		new WrapperPlugin({
			test: /\.js$/,
			header: '(function(){\n',
			footer: '\n}).call(typeof global !== "undefined" ? global : window);'
		}),
		new BabelPlugin({
			test: /\.js$/,
			presets: ['es2015']
		}),
		new WrapperPlugin({
			test: /\.js$/,
			header: '(function(){\n',
			footer: '\n})();'
		}),
	],
}];
```

`webpack`実行後↓

```js:トランスパイル後
(function(){
'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"); } }

///・・・・・・・・・・・

})();
```

グローバルスコープ汚染が無くなりました。

## 問題
[babel-webpack-plugin](https://www.npmjs.com/package/babel-webpack-plugin)は`sourceMaps`optionを`true`にすることでSource Mapを出力できる様子ですが、（私が確認した限りでは）出力されるSource Mapは不正なものになっていました。

```js:SourceMap出力のoptionをtrue
    new BabelPlugin({
        test: /\.js$/,
        presets: ['es2015'],
        sourceMaps: true,
    })
```

# 対応③（最終系）

[github:babel-webpack-plugin](https://github.com/simlrh/babel-webpack-plugin)を[fork](https://github.com/ota-meshi/babel-webpack-plugin)して修正してみたので、私と同じようにSource Mapがうまくできない場合はこちらも試してみてください。
（ただし、正しく修正できているかは自信ないです。。。）

以下に今回使用した設定をまとめます。

```json:package.json
{
  ///・・・・・・・・・
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-es2015": "^6.24.1",
    "babel-webpack-plugin": "git+https://github.com/ota-meshi/babel-webpack-plugin.git",
    "webpack": "^3.6.0",
    "wrapper-webpack-plugin": "^1.0.0"
  }
}

```
```js:webpack.config.js
'use strict';
const path = require('path');
const WrapperPlugin = require('wrapper-webpack-plugin');
const BabelPlugin = require('babel-webpack-plugin');

module.exports = [{
	context: path.resolve(__dirname, 'src/'),
	entry: {
		sample: './index.js'
	},
	output: {
		path: path.resolve(__dirname, 'dist/'),
		filename: '[name].js',
		libraryTarget: 'umd',
	},
	resolve: {
		extensions: ['.js'],
	},
	module: {
		loaders: [
			{
				test: /\.js$/,
				exclude: /node_modules/,
				loader: 'babel-loader',
			}
		]
	},
	plugins: [
		new WrapperPlugin({
			test: /\.js$/,
			header: '(function(){\n',
			footer: '\n}).call(typeof global !== "undefined" ? global : window);'
		}),
		new BabelPlugin({
			test: /\.js$/,
			presets: ['es2015'],
			sourceMaps: true,
		}),
		new WrapperPlugin({
			test: /\.js$/,
			header: '(function(){\n',
			footer: '\n})();'
		}),
	],
	devtool: '#source-map',
}];
```