background

At present, front-end engineering is very good, front-end engineering in the construction of the core is undoubtedly Webpack, Webpack engineer, is every front-end engineer to have the title (dog head to survive), in order to learn Webpack, After reading Webpack Chinese website and looking up many Webpack articles and VueCli and CRA scaffolding, I explored the current basic Webpack configuration, hoping to accumulate the best practices of Webpack and could not resist the temptation of WebPack 5. Added the difficult process of upgrading this project from Webpack4 (Master branch) to Webpack5 (webpackV5 branch) and the final code address

Install webpack

#Install the latest versionYarn add webpack @ "^ 4.0.0"Copy the code

Add the configuration file webpack.config.js

module.exports = {
    entry: './src/index.js'
    // Development mode
    mode:'development'.// Extract source-map separately, output js more readable
    devtool : 'source-map'
};
Copy the code

Adding a Run command

package.json

{
  "name": "webpack4-best-pratice"."version": "1.0.0"."main": "index.js"."license": "MIT"."scripts": {
     // Add the dev command
    "dev": "webpack"
  },
  "dependencies": {
    "webpack": "^ 4.0.0"}}Copy the code

Add the SRC directory and run it

yarn dev
Copy the code

Degraded javascript code environment

Babel compiles ES6 to ES5 to use ES6 code in development and deploy without considering browser compatibility

Install Babel

#@babel/core Babel converter core package
#@babel/preset-env Babel Transforms the configuration pack
#Babel-loader Webpack plug-in for baberl
yarn add babel-loader @babel/core @babel/preset-env
Copy the code

Compile code using Webpack

In the SRC/index. Js added

const a = 1
Copy the code

Run the dev command

yarn dev
Copy the code

Looking at the output, we see that our const is not compiled to ES5. Let’s fix that

Configure the Babel translation syntax

New Babel. Config. Js

module.exports = {
    // Introduce compilation options
    presets: [['@babel/preset-env']],};Copy the code

Configure Webpack to compile js files using Babel

module.exports = {
    / /... Add module field
    module: {
        rules: [{// Match.js files
            test: /\.js$/.// Remove node_modules to improve compilation efficiency
            exclude: /(node_modules)/./ / use the Babel - loader
            use: {
              loader: 'babel-loader',}}]}// ...
};
Copy the code

Run commands to verify the compilation result

yarn dev
Copy the code

You can see that our const has been translated to var

Missing ES6+ API compilation

We added in index.js

Promise.resolve(1)
Copy the code

Perform yarn dev

As you can see, our promises are not translated, thus lacking API-level compatibility

Configure ES6+ API compilation

Babel divides compilation into two classes, one called syntactic compilation and one called polyfill

Grammar:

const a = 1
/ / compile
var a  =  1
Copy the code

polyfill

Promise.resolve(1)
// By introducing Promise
Promise = require('Promise')
Copy the code

That is, we need to find an API implementation package that implements Promise and complies with ECMAScript. The current recommendation is core-JS, in Babel configuration

babel.config.js

module.exports = {
    presets: [['@babel/preset-env', {// Set useBuiltIns as entry to prevent errors in our application caused by dependent third-party libraries not declaring their ES6+ API. Usage option is not recommended. We need to be familiar with whether third-party packages use ES6+ API during development
                useBuiltIns: 'entry'.// With corejs3, corejs2 freezes branches very early, e.g. array.prototype. flat is only available in Corejs3
                corejs: 3}]],};Copy the code

src/index.js

// Introduce core-js/stable and regenerator-Runtime /runtime, equivalent to the obsolete babel-polyfill
import 'core-js/stable'
import 'regenerator-runtime/runtime'

const a = 1
Promise.resolve(1)
Copy the code

Pack the result

CSS introduction Support

Add globel. CSS under SRC/CSS as follows:

body {
    background-color: rebeccapurple;
}
Copy the code

SRC /index.js

import './css/global.css'
Copy the code

Perform yarn dev

There is an error message indicating that we may need loader of this file type. Here we need to install CSS-Loader and configure WebPack

yarn add css-loader
Copy the code
// Add CSS file handling
module.exports = {
    module: {
        rules: [{test: /\.css$/i,
                use: ["css-loader"],},]}};Copy the code

After the installation is complete, run yarn dev. No error message is reported

Add index. HTML to validate the output under SRC

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
</head>
<body>
    
</body>
<script src=".. /dist/main.js"></script>
</html>
Copy the code

When we open index.html in the browser and find that the style does not work, we need to introduce style-loader to convert CSS to the browser

yarn add style-loader
Copy the code

webpack.config.js

module.exports = {
    module: {
        rules: [{test: /\.css$/i./ / new style - loader
                use: ["style-loader"."css-loader"],},]}};Copy the code

Run yarn dev and refresh the page. The CSS takes effect

Configure CSS private prefix compilation

When some features are not fully implemented, browser vendors often use prefixes to allow us to use them, which can be done at compile time with PostCSS

Install dependencies

#Postcss is used to compile the CSS
#Postcss-loader PostCSS webpack plug-in
#Postcss-preset -env Is similar to @babel/preset-env to configure the compilation environment
yarn add  postcss-loader postcss postcss-preset-env
Copy the code

Configuration webpack

module.exports = {
    module: {
        rules: [{test: /\.css$/i./ / new postcss - loader
                use: ["style-loader"."css-loader"."postcss-loader"],},]}};Copy the code

Added the postCSS configuration file

postcss.config.js

module.exports = {
    plugins: [["postcss-preset-env",
        {
          // Other options},]],};Copy the code

New item compatible browser range configuration

.browserslistrc

last 2 versions
Copy the code

Modify the global. The CSS

body {
    background-color: rebeccapurple;
    /* New Flex properties */
    display: flex;
}
Copy the code

Perform compilation to verify the results

Flex’s browser private prefix is printed after compilation

Configure the CSS precompiled language

Here we configure sass

Install dependencies

#Sass, the sASS compiler, is more compatible than Node-sass
#Sass-loader Webpack plug-in for Sass
yarn add sass  sass-loader
Copy the code

Configuration webpack

module.exports = {
    module: {
        rules: [{test: /\.s[ac]ss$/i,
                use: [
                  "style-loader"."css-loader"."sass-loader",],},]}};Copy the code

Modify file suffix verification

Change the suffix of SRC/CSS /global. CSS to SCSS, and change the suffix of index.js to CSS. Run yarn dev

Configure the SVG

Currently, the most appropriate way to use SVG is through SVG Sprite

Install dependencies

#Svgo SVG optimization
#Svgo-loader Indicates the SvGo Webpack plug-in
#SVG Sprite - loader SVG - Sprite plug-in
yarn add svgo-loader svgo svg-sprite-loader
Copy the code

Configuration webpack

webpack.config.js

module.exports = {
module: {
    rules: [{test: /\.svg$/,
            use: [
                { loader: 'svg-sprite-loader'.options: {}},'svgo-loader']]}},Copy the code

Add SVG directory

Add SVG /index.js and SVG /assets directories and add them to them

<svg id="Layer _1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40." "><title>My order</title><path d="M20.23, 4 h0l16. 12,6.37 L19.93, 16.22 h - 11 l3. 7,9.86, 20.15, 4 h. m0 08-2 a2. 07,2.07, 0, 0, -. 78.14 l. 76,8.78 a1.1, 1.1, 0,0,0,0,2.06 l18 . 33,7.25 a2.06, 2.06, 0, 0, 77.14, 2.11, 2.11, 0, 0,. 78-14 l18 a1.11 69-6.63, 1.11, 0,0,0,0-2.07 L21, a2.06 2.15, 2.06, 0,0,0,20.23, 2Z"/><path d="M19.79, a3.14 27.9, 3.14, 0, 1-1.13 -. 21 l - 18-7.13 a1, 1,0,0,1,. 74-1.86 l18 a1.25 7.13, 1.25, 0, 0,. 83, 0 l18.51 6.57 a1, 1,0,1,1,. 67 And 1.89 L20.89, A3.19 27.7, 3.19, 27.9 0,0,1,19.79, Z"/><path d="M19.79, a3.36 37.92, 3.36, 0, 1-1.13 -. 2 l - 18-7.13 a1, 1,0,0,1-56-1.3, 1,1,0,0,1,1.3 -. 56 l18, a1.13 7.12, 1.13, 0, 0,. 83, 0 l18.51-6 . 56 a1, 1,0,1,1,. 67,1.88 l - 18.5, 6.56 A3.19, 3.19, 37.92 0,0,1,19.79, Z"/></svg>
Copy the code
let req = require.context('./assets'.false./\.svg$/);

let requireAll = function (requireContext) {
    requireContext.keys().map(requireContext);
};

requireAll(req);

Copy the code

Run the command to verify the effect

yarn dev

Static resource support

Install dependencies

yarn add file-loader  url-loader
Copy the code

Configuration webpack

module.exports = {
    module: {
        rules: [{test: /\.(png|jpg|gif)$/i,
                use: [
                  {
                    loader: 'url-loader'.options: {
                      limit: 8192,},},],}, {test: /\.(png|jpe? g|gif)$/i,
                use: [
                  {
                    loader: 'file-loader',},],},]},};Copy the code

Configure the Vue environment

Install dependencies

#Vue-loader vue-template-compiler vue is used to compile VUE files
#@vue/babel-preset- JSX @vue/babel-helper-vue-jsx-merge-props Supports Vue JSX writing
yarn add vue-loader vue-template-compiler vue  @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props
Copy the code

Configuration webpack

module.exports = {
    module: {
        rules: [{test: /\.vue$/,
                use: ['cache-loader'.'thread-loader'.'vue-loader'],},]},plugins: [
        new VueLoaderPlugin()
    ],
};
Copy the code

New File Verification

New SRC /test.vue, introduced in index.js

import testVue from './test.vue'
console.log(testVue);
Copy the code

Run yarn dev, and the console output is displayed

Hot update

Automatically inject dependencies and copy HTML to the dist directory

Install dependencies

yarn add html-webpack-plugin
Copy the code

Configuration webpack

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    / / use HtmlWebpackPlugin
    plugins: [new HtmlWebpackPlugin()]
};
Copy the code

Configuring hot Update

Install dependencies

#Webpack-dev-server Hot update server
#Webpack -cli Webpack command package
yarn add webpack-cli webpack-dev-server
Copy the code

Enabling Hot Update

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    // Enable hot update server configuration
    devServer: {
        contentBase: './dist'.hot: true,}};Copy the code

Modify run command verification

package.json

"scripts": {
"dev": "webpack serve"
},
Copy the code

Run the yarn dev

Open the prompt address and view the result. You can see that our code has been running on the hot update server. At this time, we can modify the style at will, which can take effect in real time

Separate production and development environment configurations

The separation configuration

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    entry: './src/index.js'.module: {
        rules: [{test: /\.js$/,
                exclude: /(node_modules)/,
                use: {
                    loader: 'babel-loader',},},]},plugins: [new HtmlWebpackPlugin()]
};
Copy the code

webpack.dev.config.js

const baseWebpackConfig = require('./webpack.config')
const { merge } = require('webpack-merge');
module.exports =   merge(baseWebpackConfig, {
    mode:'development'.devtool : 'eval-source-map'.devServer: {
        contentBase: './dist'.hot: true,},module: {
        rules: [{test: /\.css$/i,
                use: ["style-loader"."css-loader"."postcss-loader"],}, {test: /\.s[ac]ss$/i,
                use: [
                  "style-loader"."css-loader"."sass-loader",],},]},});Copy the code

webpack.prod.config.js

const baseWebpackConfig = require('./webpack.config')
const { merge } = require('webpack-merge');
module.exports =   merge(baseWebpackConfig, {
    mode:'production'.devtool : 'source-map'.module: {
        rules: [{test: /\.css$/i,
                use: ["style-loader"."css-loader"."postcss-loader"],}, {test: /\.s[ac]ss$/i,
                use: [
                  "style-loader"."css-loader"."sass-loader",],},]},});Copy the code

Add development commands and production commands

package.json

"scripts": {
    "dev": "webpack serve --config=./webpack.dev.config.js"."build": "webpack --config=./webpack.prod.config.js"
},
Copy the code

Run a command attempt

yarn dev

Configuration of compression

Javascript compression

Install dependencies

#Install the latest version 4.0. Version 5 only supports webpack5Terser webpack - plugin @ "^ 4.0.0"Copy the code

Configuration webpack

webpack.prod.config.js

const TerserPlugin = require("terser-webpack-plugin");
module.exports =   merge(baseWebpackConfig, {
    optimization: {
        minimize: true.minimizer: [new TerserPlugin()],
    },
});
Copy the code

Executing command Verification

yarn build

Separate CSS into separate files

Install dependencies

yarn add mini-css-extract-plugin
Copy the code

Configuration webpack

webpack.prod.config.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports =   merge(baseWebpackConfig, {
    module: {
        rules: [{test: /\.css$/i./ / plus the loader
                use: [MiniCssExtractPlugin.loader,"css-loader"."postcss-loader"],}, {test: /\.s[ac]ss$/i./ / plus the loader
                use: [
                  MiniCssExtractPlugin.loader,
                  "css-loader"."sass-loader",],},]},// Add plugins
    plugins: [new MiniCssExtractPlugin()],
});
Copy the code

Executing command Verification

After executing YARN Build, you can see that a main.css has been added to our dist file

Compress CSS

Install dependencies

yarn add css-minimizer-webpack-plugin
Copy the code

Configuration webpack

webpack.prod.config.js

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports =   merge(baseWebpackConfig, {
    // New CssMinimizerPlugin compression plugin
    minimizer: [new TerserPlugin(),new CssMinimizerPlugin()],
});
Copy the code

Run command verification

Yarn dev, see main. CSS has been compressed

Persistent cache

It is recommended to read this article, which details how to deal with file cache name changes in each case, and concludes with the following configuration:

webpack.config.js

module.exports = {
    output: { 
       filename: '[name].js'.chunkFilename: '[name].js'}};Copy the code

webpack.prod.config.js

module.exports =   merge(baseWebpackConfig, {
    plugins: 
    [   
        // Stabilize the CSS hash
        new MiniCssExtractPlugin(
            {
                filename: '[name].[contenthash:8].css'.chunkFilename: '[name].[contenthash:8].css'}),// Stabilize the chunk ID
        new webpack.NamedChunksPlugin(
            chunk= > chunk.name || Array.from(chunk.modulesIterable, m= > m.id).join("_")),,// Stable module ID
    optimization: {
        hashedModuleIds: true,},output: { 
        // Separate chunks mapping to avoid major JS hash changes during chunk changes
        runtimeChunk: true.// Stabilize the file hash
        filename: '[name].[contenthash:8].js'.// Stabilize chunk hash
        chunkFilename: '[name].[contenthash:8].js'}});Copy the code

Performance optimization – speed

concurrent

javascript

Currently, it is recommended to use the official thread-loader. Because multithreading has communication loss, it is recommended to use the loader with high consumption, such as Babel-loader

yarn add thread-loader
Copy the code

webpack.config.js

module.exports = {
    module: {
        rules: [{test: /\.js$/,
                exclude: /(node_modules)/.// Add thread-loader to Babel
                use: ['thread-loader'.'babel-loader']},]},};Copy the code

sass

The official recommendation is to use fibers to improve sASS compilation speed

yarn add fibers
Copy the code
{
    loader: "sass-loader".options: {
        sassOptions: {
            require("fibers"),}}},Copy the code

The cache

Currently, cache-loader is recommended

yarn add  cache-loader
Copy the code

webpack.config.js

module.exports = {
    module: {
        rules: [{test: /\.js$/,
                exclude: /(node_modules)/.// Add cache-loader to improve secondary compilation speed
                use: ['cache-loader'.'thread-loader'.'babel-loader']},]},};Copy the code

Performance optimization – Volume

The subcontract

Webpack’s default subcontracting is just subcontracting asynchronous blocks, which we’ll need to tweak ourselves

webpack.config.js

module.exports = {
    optimization: {
        splitChunks: {
            cacheGroups: {
                // node_modules is packaged in a single file to improve caching
                vendors: {
                  name: `chunk-vendors`.test: /[\\/]node_modules[\\/]/,
                  priority: -10.chunks: 'initial'
                },
                // Extract code introduced more than twice to reduce packaging volume
                common: {
                  name: `chunk-common`.minChunks: 2.priority: -20.chunks: 'initial'.reuseExistingChunk: true}}}}};Copy the code

Run YARN Build and you can see that a chunk-vendors file is added under DIST

Upgrade to webpack5

Upgrade webpack

yarn add webpack
Copy the code

Upgrade terser webpack — the plugin

yarn add terser-webpack-plugin
Copy the code

Remove the persistent cache option

Webpack already supports moduleID and chunkID stabilization algorithms by default, so these two plug-ins are removed

module.exports =   merge(baseWebpackConfig, {
    plugins: 
    [ 
        // new webpack.NamedChunksPlugin(
        // chunk => chunk.name || Array.from(chunk.modulesIterable, m => m.id).join("_")
        // ),].optimization: {
        //hashedModuleIds: true,}});Copy the code

Delete the cache – loader

Webpack5 has a built-in cache mechanism to improve the cache effect and cache security. Cache-loader can be deleted

Update the HTML – webpack – the plugin

yarn add  html-webpack-plugin@next
Copy the code

Disable file-loader and url-loader

Webpack introduces the concept of resources, the previous file-loader and url-loader have been regarded as resources, if the resource configuration meets your needs, migrate the two loaders to the corresponding resource type

{
    test: /\.(png|jpg|gif)$/i,
    type: 'asset/resource'
},
/ / {
// test: /\.(png|jpg|gif)$/i,
// use: [
/ / {
// loader: 'url-loader',
// options: {
// limit: 8192,
/ /},
/ /},
/ /,
// },
{
    test: /\.(png|jpg|gif)$/i,
    type: 'asset/inline'
},

/ / {
// test: /\.(png|jpe? g|gif)$/i,
// use: [
/ / {
// loader: 'file-loader',
/ /},
/ /,
// },
Copy the code

Hot update failure

Target needs to be set to the Web platform

webpack.config.js

module.exports = {
    target: 'web'
}
Copy the code

Hot update overlay fails

Configuration failure

After trying to trigger an error and configuring the devServer overlay attribute, the error popover does not display the error message

devServer: {
    static: {
        directory: './dist',},// Error pop-ups are configured
    overlay: {
        warnings: true.errors: true}},Copy the code

Webpack dev – server did not fit

After debugging and reading the source code, the problem is that Webpack-dev-server 3.0 has not been adapted to Webpack 5

Upgrade the adapted version of Webpack-dev-server

To upgrade to a normal beta version of webpack5, run

yarn add webpack-dev-server@next 
Copy the code

Debug and patch

After installation, I still found that the display could not be displayed, so I continued debugging and found that the variable here was not assigned, because I first used patch-package to patch myself

Install dependencies

yarn add patch-package
Copy the code

Modify the file

node_modules\webpack-dev-server\lib\utils\normalizeOptions.js

  options.clientOverlay =
    typeofoptions.overlay ! = ='undefined' ? options.overlay : false;

Copy the code

Perform patch

npx patch-package webpack-dev-server
Copy the code

Rerun to see the results

Give a webpack dev – PR server

PR steps:

  • Fork open source repository

  • Modify the code, pass the warehouse’s specification checks (style, quality, type checks), and add unit tests and pass all the tests at the time, as shown in Webpack-dev-server

  • Push to your own remote repository

  • Initiate PR in the original warehouse

  • Communicate with the maintainer in Level 3 English

PR address