LoginSignup
9
10

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-09-28

webpackbabelを組み合わせて、es2015からes5へのトランスパイルができます。

標準的な設定(webpack.config.js

この方法をググってぱっと出てくる感じだと、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後にトランスパイルしたいので、pluginsbabelを実行したいです。
babelするwebpack pluginを探したところ、babel-webpack-pluginというのがありました。

使ってみます。

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実行後↓

トランスパイル後
'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が一つになりました。

問題

webpackoutput設定で、libraryTarget: 'umd'などとすると問題が起きます。

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するプラグインはいくつかある様子なので好みのものを選んでください)

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実行後↓

トランスパイル後
///・・・・・
(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);

どうやらうまくできたようです!
webpackUniversalModuleDefinitionrootに与えられる引数がthisになりました。

ついでなので、_classCallCheck等のボイラープレートコードがグローバルスコープになってしまう問題も、
wrapper-webpack-pluginを使って回避します。

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実行後↓

トランスパイル後
(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-pluginsourceMapsoptionをtrueにすることでSource Mapを出力できる様子ですが、(私が確認した限りでは)出力されるSource Mapは不正なものになっていました。

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

対応③(最終系)

github:babel-webpack-pluginforkして修正してみたので、私と同じようにSource Mapがうまくできない場合はこちらも試してみてください。
(ただし、正しく修正できているかは自信ないです。。。)

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

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"
  }
}

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',
}];
9
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
10