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.
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.
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.
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!
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.
"webpack": "^4.23.1",
"webpack-cli": "^3.1.2",
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);
Compiled successfully for 0 min 15 sec 358 ms
Version: webpack 4.23.1
Time: 22502ms
So, the building time has been decreased a little.
Would be great to hear all your thoughts!
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.