Gulp: webpack and ES6 - fevrcoding/wok GitHub Wiki

This recipe will guide you through the installation and configuration of webpack4 and Babel (ES6) in the WOK workflow

Installation and Requirements

Install babel polyfill, core, and webpack and its babel loader

yarn add babel-core babel-loader  babel-plugin-transform-runtime babel-preset-env babel-preset-stage-2 webpack json-loader --save-dev
yarn add babel-runtime babel-polyfill --save 

Note: you may replace yarn add with npm install.

webpack / Babel configurations

Create a webpack.conf.js file under build/gulp-config folder and paste the following code:

module.exports = (options) => {

    const path = require('path');
    const paths = require('./paths');
    const template = require('lodash/template');
    const webpack = require('webpack');

    const srcPath = paths.toAbsPath('src.assets');
    const destPath = paths.toPath('dist.assets');
    const plugins = [];

    plugins.push(
        new webpack.NoEmitOnErrorsPlugin(),
        new webpack.DefinePlugin({
            __PRODUCTION__: options.production,
            __TARGET__: JSON.stringify(options.target),
            __TARGET_HOST__: JSON.stringify(options.hosts[options.target] || {}),
            'process.env': {
                'NODE_ENV': JSON.stringify(options.production ? 'production' : 'development') //eslint-disable-line quote-props
            }
        })
    );

    if (options.production) {
        plugins.push(
            new webpack.LoaderOptionsPlugin({
                minimize: true
            }),
            new webpack.BannerPlugin({
                banner: template(options.banners.application)(options),
                entryOnly: true,
                raw: true
            })
        );
    }

    return {
        context: srcPath,
        entry: {},
        output: {
            path: path.join(process.cwd(), destPath),
            publicPath: destPath.replace(paths.toPath('dist.root'), '').replace(/\\/g, '/') + '/',
            filename: `${paths.get('js')}/[name].js`
        },
        watch: !!options.isWatching,
        devtool: (options.production ? '#source-map' : '#cheap-module-source-map'),
        mode: options.production ? "production" : "development",
        plugins,
        target: 'web',
        performance: {
            hints: false
        },
        node: {
            fs: 'empty'
        },
        module: {
            rules: [
                { parser: { amd: false } },
                {
                    test: /\.js$/,
                    include: [
                        paths.toAbsPath('src.assets/js')
                    ],
                    loader: 'babel-loader',
                    options: {
                        cacheDirectory: true
                    }
                }, {
                    test: /\.json$/,
                    exclude: /(node_modules|vendors)/,
                    loader: 'json-loader'
                }
            ]
        },
        resolve: {
            modules: ['node_modules', paths.toAbsPath('src.assets/vendors')]
        }
    };
};

Next, ensure you have a .babelrc file in the project root that looks something like this:

{
    "presets": [
        ["env", {
            "modules": false,
            "loose": true,
            "useBuiltIns": false,
            "targets": {
                "browsers": ["> 1%", "last 2 versions", "not ie < 11"]
            }
        }],
        "stage-2"
    ],

    "plugins": [
        ["transform-runtime", {"polyfill": false, "regenerator": false }]
    ]
}

Notes:

  • the entry property will be automatically populated with every top level JavaScript file into the application/assets/javascripts folder.

  • application source files are loaded by the babel-loader.

Gulp tasks

Replace existing build/gulp-tasks/scripts.js contents with:

/**
 * JavaScript Related Task
 * ===============================
 */

module.exports = (gulp, $, options) => {

    const path = require('path');
    const _ = require('lodash');
    const webpackConfigDefault = require('../gulp-config/webpack.conf')(options);
    const paths = require('../gulp-config/paths');
    const webpack = require('webpack');

    let compiler;
    let watcher;

    function getEntryPoints(cwd) {
        const entryObj = {};

        require('glob').sync(`./${paths.get('js')}/*.js`, {
            cwd
        }).forEach((filepath) => {
            const entryId = path.basename(filepath, '.js');
            entryObj[entryId] = [filepath];
        });

        return entryObj;
    }


    function compilerCallback(err, stats) {

        $.util.log((stats || {}).toString({
            colors: $.util.colors.supportsColor,
            hash: false,
            timings: false,
            chunks: false,
            chunkModules: false,
            modules: false,
            children: true,
            version: true,
            cached: false,
            cachedAssets: false,
            reasons: false,
            source: false,
            errorDetails: false
        }));

        if (err) {
            throw new $.util.PluginError('webpack', err);
        }
    }

    const webpackConfig = _.assign({}, webpackConfigDefault);

    webpackConfig.entry = _.assign({}, webpackConfigDefault.entry, getEntryPoints(webpackConfigDefault.context));

    compiler = webpack(webpackConfig);


    gulp.task('scripts', (done) => {
        compiler.run((err, stats) => {
            compilerCallback(err, stats);
            if (stats && stats.hasErrors()) {
                done('Error compiling');
            } else {
                done();
            }
        });
    });

    gulp.task('scripts:watch', (done) => {

        const notifier = $.notify({ message: 'Scripts Compiled' });
        const bs = require('browser-sync');
        const del = require('del');
        const browserSync = bs.has(options.buildHash) ? bs.get(options.buildHash) : null;


        function createWatcher(webpackCompiler) {
            return webpackCompiler.watch({
                aggregateTimeout: 200,
                poll: false
            }, (err, stats) => {
                compilerCallback(err, stats);
                if (stats && stats.hasErrors()) {
                    notifier.emit('error', new Error('Compilation error!'));
                } else {
                    notifier.write('Scripts Compiled');
                    if (browserSync) {
                        browserSync.reload();
                    }
                }
            });
        }


        notifier.on('error', $.notify.onError({
            message: 'Error: <%= error.message %>'
        }));

        //force watching
        if (!options.isWatching) {
            options.isWatching = true; //eslint-disable-line
        }

        watcher = createWatcher(compiler);

        process.on('exit', () => {
            watcher.close(done);
        });


        gulp.watch('*.js', { cwd: webpackConfig.context }, (event) => {

            //also delete removed entry point
            if (event.type === 'deleted') {
                const filePathFromSrc = path.relative(paths.toPath('src.assets/js'), event.path);
                del.sync(path.resolve(paths.toPath('dist.assets/js'), filePathFromSrc));
                //also external sourcemap
                del.sync(path.resolve(paths.toPath('dist.assets/js'), filePathFromSrc + '.map'));
            }

            if (event.type === 'added' || event.type === 'deleted') {
                //update entry point list and restart
                watcher.close();
                webpackConfig.entry = _.assign({}, webpackConfigDefault.entry, getEntryPoints(webpackConfig.context));
                compiler = webpack(webpackConfig);
                watcher = createWatcher(compiler);
            }

        });

    });
};

This will register two tasks:

  • gulp scripts: one time script compilation
  • gulp scripts:watch: launch webpack in watch mode and live-reload browser on change. Will also watch for added / removed entry points

ES2015+ support

You should remove from application/views files any reference to external polyfills (for example <script defer src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.26.0/polyfill.min.js"></script>).

To polyfill unsupported ES2015+ features you need to include at the top of the first encountered entry point the babel-polyfill file.

//line 1 in application.js
import 'babel-polyfill';

Workflow integration

Open build/gulp-tasks/serve.js and replace these lines:

gulp.watch([
    paths.toPath('src.assets/js/**/*.js'),
    '!' + paths.toPath('src.assets/js/**/*.{spec,conf}.js')
], ['scripts-watch']);

with these line:

gulp.run('scripts:watch');

You may also want to disable built-in source minification since webpack already does it for you. To do so edit build/gulp-tasks/views.js and remove the following lines (around line 88):

.pipe($.uglify, {preserveComments: 'license'})
.pipe($.header, options.banners.application, {pkg: options.pkg})

Caveats

  • Hot reloading and webpack-dev-server are not enabled for simplicity.
⚠️ **GitHub.com Fallback** ⚠️