Moving from Webpack 3 to Webpack 4 — Now with some exciting updates

Date
March 9, 2018
Hot topics 🔥
Tech Insights
Contributor
Dmitry Ermakov
Moving from Webpack 3 to Webpack 4 — Now with some exciting updates

By Dmitry Shishko, PHP developer at WeAreBrain.

 


Dive into the journey and insights from upgrading from Webpack 3 to Webpack 4, highlighting the streamlined configuration process and performance improvements.

Key takeaways

  • Upgrade Benefits: Transitioning to Webpack 4 simplifies configurations and enhances build performance.
  • Configuration Changes: Introduction of separate config files for development and production, improving project structure and management.
  • Performance Gains: Notable reduction in build times for both development and production modes.
  • Deprecation Notices: Addressing deprecation warnings by adapting to new plugin APIs and replacing outdated plugins.
  • Continuous Updates: Ongoing improvements and updates to Webpack 4, including the transition from ExtractTextPlugin to MiniCssExtractPlugin for better CSS handling.
  • Community Engagement: Valuable feedback and suggestions from the community leading to further refinements.
  • Measurable Improvements: Quantitative evidence showing faster build times and more efficient development workflows with Webpack 4.

The latest updates

We had such a great response to our article about moving from Webpack 3 to Webpack 4 and so many of you had questions and comments that we decided to do an update. If you haven’t read the original article, you really should but if you have and you’re curious about our new findings scroll down to Appendix A for our latest updates.

Introduction — Moving from Webpack 3 to Webpack 4

Webpack 4.0, was recently released, and in this article, I’d like to share my experience with moving from Webpack 3 to Webpack 4.

Previously I had two Webpack config files for development and production in the root folder. I decided to move the New configs into separate folders/configs.

├──configs
│   ├──webpack.common.js
│   ├──webpack.config.dev.js
│   ├──webpack.config.prod.js

To start setting up Webpack config for development we’ll need to install Webpack as follows:

yarn add webpack webpack-cli --dev
or
npm i webpack webpack-cli --save-dev

I’ve created a common config for development and production ./config/webpack.common.js

const path = require('path');
const webpack = require('webpack');
const ROOT_DIR = path.resolve(__dirname, '../');
const SRC_DIR = path.resolve(ROOT_DIR, 'src');
module.exports = {
  entry: [
    'babel-polyfill',
    path.join(SRC_DIR, 'index'),
  ],
  resolve: {
    modules: [
      'src',
      'node_modules',
      'theme',
      'theme/fonts',
    ],
    extensions: ['.js', '.css', '.scss'],
    alias: {
      theme: path.resolve(ROOT_DIR, 'theme'),
    },
  },
  module: {
    rules: [
      // js
      {
        test: /\.js$/,
        use: 'babel-loader',
        exclude: [
          path.resolve(ROOT_DIR, 'node_modules'),
        ],
      },
      // images
      {
        test: /\.(png|ico|gif|svg|jpe?g)(\?[a-z0-9]+)?$/,
        use: 'url-loader',
      },
      // fonts
      { test: /\.(woff|woff2|eot|ttf|otf)$/, use: ['url-loader'] }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify(process.env.NODE_ENV),
        CUSTOM_HOST: JSON.stringify(process.env.CUSTOM_HOST),
        HTTPS: JSON.stringify(process.env.HTTPS),
        RUBY_BACKEND: JSON.stringify(process.env.RUBY_BACKEND),
      }
    }),
  ]
};

And then I created the config for development ./config/webpack.config.dev.js

const webpack = require('webpack');
const autoprefixer = require('autoprefixer');
const merge = require('webpack-merge');
const path = require('path');
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
const common = require('./webpack.common.js');
const ROOT_DIR = path.resolve(__dirname, '../');
const DIST_DIR = path.resolve(ROOT_DIR, 'dist');
module.exports = merge(common, {
  mode: 'development', //
  devtool: 'eval',
  entry: [
    require.resolve('react-dev-utils/webpackHotDevClient'),
  ],
  output: {
    path: DIST_DIR,
    publicPath: '/dist/',
    filename: 'bundle.js',
  },
  module: {
    rules: [
      // css
      {
        test: /\.css$/,
        include: /node_modules/,
        loader: [
          'style-loader',
          'css-loader',
        ]
      },
      // sass
      {
        test: /\.scss$/,
        use: [
          {
            loader: 'style-loader'
          },
          {
            loader: 'css-loader',
            options: {
              sourceMap: true,
            },
          },
          {
            loader: 'postcss-loader',
            options: {
              sourceMap: true,
              plugins() {
                return [autoprefixer('last 2 version')];
              }
            }
          },
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
            },
          }
        ]
      },
    ]
  },
  plugins: [
    new ProgressBarPlugin({
      format: 'Build [:bar] :percent (:elapsed seconds)',
      clear: false,
    }),
    new webpack.NamedModulesPlugin(),
    new webpack.HotModuleReplacementPlugin(),
  ]
});

Execute npm run start, which runs the script.

package.json
...
"start": "NODE_ENV=dev webpack --config ./configs/webpack.config.dev.js",
...

And I’ve included one Deprecation Warning.

DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead

add process.traceDeprecation = true; to webpack.common.js and find where this warning has been invoked. It’s in webpack-dev-server and an issue is open. So we wait for an update.

Create config for production:

./config/webpack.config.prod.js

const webpack = require('webpack');
const path = require('path');
const merge = require('webpack-merge');
const autoprefixer = require('autoprefixer');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const OfflinePlugin = require('offline-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const common = require('./webpack.common.js');
const ROOT_DIR = path.resolve(__dirname, '../');
const DIST_DIR = path.resolve(ROOT_DIR, 'dist');
const prodConfig = {
  mode: 'production',
  devtool: 'source-map',
  target: 'web',
  output: {
    path: DIST_DIR,
    publicPath: '/',
    filename: '[name].[chunkhash].js',
    chunkFilename: '[name].[chunkhash].js',
  },
  module: {
    rules: [
      // sass
      {
        test: /\.scss$/,
        use: ExtractTextPlugin.extract({
          use: [
            {
              loader: 'css-loader',
              options: {
                sourceMap: true
              }
            },
            {
              loader: 'postcss-loader',
              options: {
                plugins: [autoprefixer('last 2 version')],
                sourceMap: true
              }
            },
            {
              loader: 'sass-loader',
              options: {
                sourceMap: true
              }
            }
          ]
        }),
      },
    ],
  },
  optimization: {
    runtimeChunk: false,
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
    minimizer: [
      new UglifyJsPlugin({
        cache: true,
        parallel: true,
        sourceMap: true
      })
    ]
  },
  plugins: [
    // clean dist folder
    new CleanWebpackPlugin(['dist'], { root: ROOT_DIR }),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new ExtractTextPlugin({
      filename: 'styles.[hash].css',
      allChunks: false,
    }),
    new HtmlWebpackPlugin({
      template: 'src/index.html',
      favicon: 'theme/img/favicon.ico',
      inject: true,
      sourceMap: true,
      chunksSortMode: 'dependency'
    }),
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp('\\.(js|css)$'),
      threshold: 10240,
      minRatio: 0.8
    }),
    new OfflinePlugin({
      caches: 'all',
      AppCache: false,
    }),
  ],
};
if (process.env.NODE_ANALYZE) {
  prodConfig.plugins.push(new BundleAnalyzerPlugin());
}
module.exports = merge(common, prodConfig);

Execute npm run build, witch run script.

package.json
...
"build": "NODE_ENV=production webpack --config ./configs/webpack.config.prod.js",
...

Oops! There’s an error…

Error: webpack.optimize.UglifyJsPlugin has been removed, please use config.optimization.minimize instead.

Update ./config/webpack.config.prod.js

...
new OfflinePlugin({
      caches: 'all',
      AppCache: false,
      ServiceWorker: {
        minify: false, 
      },
    }),
...

Then I run the build script and it appears I have two DeprecationWarnings.

DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
DeprecationWarning: Tapable.apply is deprecated. Call apply on the plugin directly instead

Check trace deprecation and ind, where this warning is invoked. It’s in html-webpack-plugin, and the issue is open. So we wait for an update again.

Check our build performance.

Webpack 3, in develop mode, first build:

Compiled successfully for  0 min 18 sec 158 ms

Webpack 3, in production mode:

Version: webpack 3.11.0
Time: 37173ms

Webpack 4, in develop mode, first build:

Compiled successfully for  0 min 16 sec 269 ms

Building process improved by 10%!

Webpack 4, in production mode:

Version: webpack 4.1.0
Time: 27402ms

Building process became faster by 26%!

I hope this was helpful!

Appendix A — the latest updates

First of all, thank you to all of you for comments and questions. It’s great to engage with everyone. It’s been a little over 6 months since Webpack released a number of updates, and I decided that I’d really like to share what’s changed.

1. I’ve updated libs in package.json

"webpack": "^4.23.1",
"webpack-cli": "^3.1.2",

2. As mentioned by Dave Thompson in a comment, the ExtractTextPlugin has been deprecated.

Updated config for production:

./configs/webpack.config.prod.js

const webpack = require('webpack');
...
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OfflinePlugin = require('offline-plugin');
...
const common = require('./webpack.common.js');
const ROOT_DIR = path.resolve(__dirname, '../');
const DIST_DIR = path.resolve(ROOT_DIR, 'dist');
const MODULES_DIR = path.resolve(ROOT_DIR, 'node_modules');
const prodConfig = {
  mode: 'production',
  ...
  module: {
    rules: [
     // sass
    {
      test: /\.(sa|sc|c)ss$/,
      use: [
         MiniCssExtractPlugin.loader,
         {
           loader: 'css-loader',
           options: {
             sourceMap: true
           },
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: [autoprefixer('last 2 version')],
              sourceMap: true,
             },
           },
           {
             loader: 'sass-loader',
             options: {
               sourceMap: true,
             },
           },
        ]
      },
    ],
  },
  optimization: {
   ...
  },
  plugins: [
   ...
   new MiniCssExtractPlugin({
    // Options similar to the same options in webpackOptions.output
    // both options are optional
    filename: '[name].[hash].css',
    chunkFilename: '[id].[hash].css'
  }),
  ...
 ],
};
if (process.env.NODE_ANALYZE) {
 prodConfig.plugins.push(new BundleAnalyzerPlugin());
}
module.exports = merge(common, prodConfig);

3. Building process

  • In development mode, the first build is:
Compiled successfully for  0 min 15 sec 358 ms
  • In production mode:
Version: webpack 4.23.1
Time: 22502ms

So, the building time has been decreased a little.

Would be great to hear all your thoughts!

Dmitry Ermakov

Dmitry is our our Head of Engineering. He's been with WeAreBrain since the inception of the company, bringing solid experience in software development as well as project management.

Working Machines

An executive’s guide to AI and Intelligent Automation. Working Machines takes a look at how the renewed vigour for the development of Artificial Intelligence and Intelligent Automation technology has begun to change how businesses operate.