webpackとbabelを組み合わせて、es2015からes5へのトランスパイルができます。
標準的な設定(webpack.config.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がトランスパイルされます。
トランスパイル前
'use strict';
class Module1 {
constructor() {
}
}
module.exports = Module1;
トランスパイル後
/***/ (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
が量産されます。
例えば以下のようになります。
/***/ }),
/* 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というのがありました。
使ってみます。
'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
実行後↓
'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'
などとすると問題が起きます。
(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になって困った件
対応②
this
がtop levelにならないようにしてやればいいみたいなので、
トランスパイル前に、
(function(){ ~ }).call(typeof global !== "undefined" ? global : window);
というfunction
でwrapします。
今回はwrapper-webpack-pluginというpluginを使ってこれを実現してみます。
(wrapするプラグインはいくつかある様子なので好みのものを選んでください)
'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
実行後↓
///・・・・・
(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を使って回避します。
'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
実行後↓
(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はsourceMaps
optionをtrue
にすることでSource Mapを出力できる様子ですが、(私が確認した限りでは)出力されるSource Mapは不正なものになっていました。
new BabelPlugin({
test: /\.js$/,
presets: ['es2015'],
sourceMaps: true,
})
対応③(最終系)
github:babel-webpack-pluginをforkして修正してみたので、私と同じようにSource Mapがうまくできない場合はこちらも試してみてください。
(ただし、正しく修正できているかは自信ないです。。。)
以下に今回使用した設定をまとめます。
{
///・・・・・・・・・
"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"
}
}
'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',
}];