preface

The purpose of this article is not just to build a Webpack5 project, but also to understand each plug-in, loader’s purpose, why it is used, and what it brings to the table during the build process.

I wrote a cli tool for this project: fight-react-cli. If you want to see the final result directly, install this CLI tool and initialize a project to see the entire configuration.

The preparatory work

First we create a project webpack-demo, then initialize NPM, then install webpack and webpack-CLI locally:

mkdir webpack-demo
cd webpack-demo
npm init -y
npm install webpack webpack-cli --save-dev
Copy the code

Webpack installation is the core function package of Webpack, webpack-CLI is the command line tool of Webpack, you can use webpack command in the terminal to start the project and package the project.

We then create a folder webpack under the root of the project and create three files in this folder to distinguish the environment:

webpack.common.js // Public configuration

webpack.dev.js // Development-time configuration

webpack.prod.js // Package the build-time configuration
Copy the code

Then create a SRC folder in the root directory and create index.js under the SRC folder:

// src/index.js

const el = document.getElementById('root');
el.innerHTML = 'hello webpack5';
Copy the code

The basic configuration

Let’s write the basic configuration in webpack.common.js under webPack:

// webpack/webpack.common.js

const path = require('path');

module.exports = (webpackEnv) = > {
    const isEnvDevelopment = webpackEnv === 'development';
    const isEnvProduction = webpackEnv === 'production';

    return {
      mode: webpackEnv, 
      entry: './src/index.js'.output: {
        filename: 'main.js'.path: path.resolve(__dirname, 'dist'),},module: {
          rules: []},plugins: [],}; };Copy the code

Here we export a function that returns webPack configuration information. When this function is called, it passes in the current running environment identifier webpackEnv, which has a development or production value, and assigns the webpackEnv value to mode, which is used to enable built-in optimizations depending on the mode. Another function is to customize different configurations according to different environments, which will be used in the subsequent configuration.

In the configuration information are the five basic modules of Webpack:

  1. mode: mode, by selecting:Development, production, noneThese three parameters tell WebPack to use the built-in optimizations for the corresponding pattern.
  2. entry: Sets the entry file.
  3. output: Tell WENpack where to store the packaged files
  4. module.rules: Loader, Webpack itself only supports processing JS and JSON files. If you want to be able to process other types of files, such as CSS, JSX, TS, Vue, etc., you need the corresponding Loader to convert these files into valid modules.
  5. pluginsLoader is used to process unsupported types of files, while Plugin can be used to perform a wider range of tasks, such as compressing code (new TerserWebpackPlugin()), Resource Management (new HtmlWebPackPlugin()), inject environment variables (new webpack.DefinePlugin({... })), etc.

Configuration webpack – dev – server

With the basic configuration done, we now want to get the code running and refresh the page automatically when the code changes.

Install webpack-dev-server first:

npm install --save-dev webpack-dev-server
Copy the code

With the installation complete, we go to webpack.dev.js to add the development-time configuration:

const webpackCommonConfig = require('./webpack.common.js') ('development');

module.exports = {
  devServer: {
    host: 'localhost'.// Set host to 0.0.0.0, which can be accessed externally
    port: 8081.// Specify a port number
    open: true.// The default browser opens automatically when the service starts
    historyApiFallback: true.// When the page is not found, index.html is returned
    hot: true.// Enable module hot replacement HMR. When modifying a module, the entire page is not reloaded, only the changed content is updated
    compress: true.// Start GZip compression
    https: false.// Whether to enable HTTPS
    proxy: { // Enable the request proxy to solve the problem of front-end cross-domain requests
      '/api': 'www.baidu.com',}},... webpackCommonConfig, };Copy the code

This file exports a function that takes the environment id as a parameter. In this case, we pass in development, and then merge the returned configuration object, webpackCommonConfig, with the developing-time configuration.

Configure HTML – webpack – the plugin

The html-webpack-plugin generates an HTML file and automatically references the webpack-built file.

npm install --save-dev html-webpack-plugin
Copy the code

Once installed, add the plugin to webpack.common.js:

// webpack/webpack.common.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = (webpackEnv) = > {
    const isEnvDevelopment = webpackEnv === 'development';
    const isEnvProduction = webpackEnv === 'production';

    return {
      mode: webpackEnv, 
      entry: './src/index.js'.output: {
        filename: 'main.js'.path: path.resolve(__dirname, 'dist'),},module: {
          rules: []},plugins: [
          new HtmlWebpackPlugin(),
      ],
    };
};
Copy the code

The html-webpack-plugin can also add a template file that allows htML-webpack-plugin to generate HTML files from the template file.

We create a public folder in the root directory and create an index.ejs under the folder:

// public/index.ejs<! DOCTYPE html><html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="# 000000" />
    <meta name="description" content="Web site created using create-react-app" />
    <title>Webpack5</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

Copy the code

Then introduce the template in the plug-in:

// webpack/webpack.common.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = (webpackEnv) = >{...return{...plugins: [
          new HtmlWebpackPlugin({
              template: path.resolve(__dirname, '.. /public/index.ejs')})]}; };Copy the code

Note: the html-webpack-plugin does not normally generate HTML for the template with the.html suffix, and then changes it to.ejs.

Configure startup, package commands in package.json

Then we configure the startup and packaging commands in package.js:

{
  "name": "fcc-template-typescript"."version": "1.0.0"."description": ""."private": true."scripts": {
    "build": "webpack --config ./webpack/webpack.prod.js"."start": "webpack server --config ./webpack/webpack.dev.js"
  },
  "keywords": []."author": ""."license": "ISC". }Copy the code

We added build and start commands to scripts. Build is used to package the release and start is used to start the project at development time.

Then go to the project directory from the command line and run the NPM start or YARN start command to start the project.

Load the CSS

We know that WebPack itself only supports handling JS and JSON files. If we want to handle other types of files, we need to use the appropriate Loader.

List the loaders to be used in advance:

  1. style-loader
  2. css-loader
  3. postcss-loader

Installation:

npm install --save-dev style-loader css-loader postcss-loader postcss postcss-preset-env
Copy the code

For CSS files, add the following: CSS-loader:

webpack.common.js

// webpack/webpack.common.js.module.exports = (webpackEnv) = >{...return{...module: {
            rules: [{test: /.css$/i,
                    use: ["css-loader"],},],},}; };Copy the code

index.js

// src/index.js

import './index.css';
Copy the code

index.css

#root {
  color: red;
}
Copy the code

At this point we run and find that the text has no color added, why?

Since csS-loader is only responsible for parsing CSS files, it returns a JS object containing CSS styles:

We need CSS styles to work, so we need to insert CSS styles into the DOM, so we need to install the loader that automatically inserts styles: style-loader.

webpack.common.js

// webpack/webpack.common.js.module.exports = (webpackEnv) = >{...return{...module: {
            rules: [{test: /.css$/i,
                    use: ["style.loader"."css-loader"],},],},}; };Copy the code

Note that loader is executed in reverse order (from right to left or from bottom to top). We need to use CSS-loader to parse the CSS and generate JS objects, and then hand the CSS objects to style-loader. Style-loader will create style tags. Extract the CSS style and place it in the style tag, then insert it into the head.

CSS support is different in different browsers, so we need to use postCSs-loader to do CSS compatibility: webpack.common.js

module: {
    rules: [{test: /.css$/i,
            use: [
                "style.loader"."css-loader",
                 {
                    // CSS compatibility processing
                    loader: 'postcss-loader'.options: {
                      postcssOptions: {
                        plugins: [['postcss-preset-env',
                            {
                              autoprefixer: {
                                flexbox: 'no-2009',},stage: 3,},],],},},},Copy the code

The postCSS-PRESET -env plugin is used in PostCSS to automatically prefix.

Loading image

Before webpack5 we used url-loader to load images. In webpack5 we used built-in Asset Modules to load image resources.

Before WebPack 5, it was common to use:

  • Raw-loader imports the file as a string
  • Url-loader inlines the file into the bundle as a data URI
  • File-loader sends files to the output directory

Asset Module Type replaces all of these loaders by adding 4 new module types:

  • asset/resourceSend a separate file and export the URL. Previously by usingfile-loaderThe implementation.
  • asset/inlineExport the data URI of a resource. Previously by usingurl-loaderThe implementation.
  • asset/sourceExport the source code of the resource. Previously by usingraw-loaderThe implementation.
  • assetAutomatically choose between exporting a data URI and sending a separate file. Previously by usingurl-loaderAnd configure the resource volume limitation implementation.

webpack.common.js

 module: {
   rules: [{test: /\.(png|svg|jpg|jpeg|gif)$/,
      type: 'asset'.generator: {
        filename: 'image/[name].[contenthash:8][ext][query]'}}},],Copy the code

Add generator properties to customize file name and file location.

You can also define assetModuleFilename in output to set the default location and filename format:

output: {
  assetModuleFilename: 'asset/[name].[contenthash:8][ext][query]',}Copy the code

Load fonts or other resources

webpack.common.js

 module: {
   rules: [{exclude: /\.(js|mjs|ejs|jsx|ts|tsx|css|scss|sass|png|svg|jpg|jpeg|gif)$/i,
      type: 'asset/resource'}},],Copy the code

We load other resources by excluding their suffixes.

Compatible JS: Convert ES6 syntax to ES5

Loader to be used:

  1. babel-loader

Installation:

npm install --save-dev babel-loader @babel/core @babel/preset-env
Copy the code

The Babel plug-in you need to use:

  1. @babel/plugin-transform-runtime
  2. @babel/runtime

Installation:

npm install --save-dev @babel/plugin-transform-runtime
Copy the code
npm install --save @babel/runtime
Copy the code

webpack.common.js

 module: {
   rules: [{test: /\.js$/,
        use: [
          {
            loader: 'babel-loader'.include: path.resolve(__dirname, './src'),
            options: {
              presets: [["@babel/preset-env",
                    {
                        "useBuiltIns": "usage"."corejs": 3,}]],}},],},]},Copy the code

We’ll use Babel’s plugin here: @babel/preset-env, which is a collection of preset plugins.

For example, if we use the arrow function and the browser doesn’t know anything else and needs to translate it into a normal function, we need to add the Babel plugin: @babel/plugin-transform-arrow-functions to handle arrow functions. If we use a lot of ES6 apis, we need to manually add plug-ins, which will be very troublesome. Gather all es6 feature plug-ins to be converted into @babel/ PRESET -env.

To use @babel/preset-env we need to configure the corejs attribute. What is corejs?

Babel only supports the latest syntax transformations, such as extends, but it does not support the latest apis, such as: Map, Set, Promise, etc., need to support the latest Api in an incompatible environment. Then we need to Polyfill the missing Api in the target environment. In this case, we need to introduce Core-JS to Polyfill.

UseBuiltIns tells Babel how to introduce Polyfill.

When entry is selected, Babel does not introduce polyfill, requiring us to manually introduce the full:

import "core-js"; 

var a = new Promise(a);Copy the code

When Usage is selected, Babel automatically introduces the required polyfill based on the current code:

import "core-js/modules/es.promise"; 

var a = new Promise(a);Copy the code

But we found that using polyfill like this would pollute the global object as follows:

"foobar".includes("foo"); After using polyfill, it will be inStringAdd the includes method to the prototype object:String.prototype.includes = function() {}
Copy the code

If we had used other plug-ins to add methods of the same name to the prototype object, that would have caused problems.

In this case, we can use @babel/ plugin-transform-Runtime to implement polyfill by introducing modules:

 module: {
   rules: [{test: /\.js$/,
        use: [
          {
            loader: 'babel-loader'.options: {
              presets: [
                "@babel/preset-env",].plugins: [['@babel/plugin-transform-runtime',
                  {
                    "helpers": true."corejs": 3."regenerator": true,}]],}},],},]},Copy the code

Let’s take a look at the effect:

"foobar".includes("foo");
Copy the code

After the translation:

var _babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_1___default = __webpack_require__.n(_babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_1__);


_babel_runtime_corejs3_core_js_stable_instance_includes__WEBPACK_IMPORTED_MODULE_1___default()(_context = "foobar").call(_context, "foo");
Copy the code

As you can see, the translated includes is implemented by calling the Includes method in Runtime-corejs3.

Now that we know what @babel/ plugin-transform-Runtime does, let’s take a look at its common configuration properties.

Helpers — let’s set helpers to false to see what happens after we compile it.

class Test {}
Copy the code

After the translation:

function _classCallCheck(instance, Constructor) { if(! (instanceinstanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); }}var Test = function Test() {
  _classCallCheck(this, Test);
};
Copy the code

We saw that after the translation, we added a _classCallCheck utility function at the top. If we package multiple files, each of which uses class, we will generate the same _classCallCheck utility function at the top, which will make the final packaged file larger.

Helpers is set to true.

var _babel_runtime_corejs3_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);

var Test = function Test() {(0,_babel_runtime_corejs3_helpers_classCallCheck__WEBPACK_IMPORTED_MODULE_0__["default"]) (this, Test);
};
Copy the code

We saw that the _classCallCheck function was introduced as a module, which allowed Babel’s common utility functions to be reused to reduce the size of the packaged files.

Corejs: Specifies the version that depends on Corejs for polyfill.

Regenerator: When we use Generate, we inject the implementation function of Generate into the global environment, which causes global pollution. Set regenerator to true and call Generate by module introduction to avoid global pollution:

function* test() {
  yield 1;
}
Copy the code

Regenerator set to false:

function test() {
  return regeneratorRuntime.wrap(function test$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return 1;

        case 2:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}
Copy the code

You can see that the regeneratorRuntime object is used directly and not imported, so it must exist in the global environment.

Regenerator set to true:

function test() {
  return _babel_runtime_corejs3_regenerator__WEBPACK_IMPORTED_MODULE_0___default().wrap(function test$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          _context.next = 2;
          return 1;

        case 2:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}
Copy the code

As you can see, the generate function used this time is referenced from the runtime-corejs3 export.

Note: You also need to configure the target browser in package.json to tell Babel which browser we want to polyfill:

// package.json

{
  "name": "webpack5"."version": "1.0.0"."browserslist": {
   // Development-time configuration for fewer browsers, resulting in less code for Polyfill and faster compilation
    "development": [
      "last 1 chrome version"."last 1 firefox version"."last 1 safari version"].// Production configuration needs to consider all supported browsers, the more browsers supported, the more polyfill code
    "production": [
      "0.2%" >."not dead"."not op_mini all"]}}Copy the code

The advanced configuration

With the basic configuration of WebPack complete, let’s configure some more advanced ones.

Load the CSS modules

What are CSS modules?

Each CSS file has its own scope, and the properties in the CSS file are unique within that scope.

When we have multiple components, each component has a corresponding CSS file, in which the property name will inevitably have the same name, if we use directly, the latter will overwrite the former, only one style will be in effect. We use CSS Modules to rename attribute names in the form of hash values or path strings to ensure that each attribute name is unique and only applies to its own component without affecting other components.

Add modules attributes directly to CSS-loader:

{
    test: /\.module\.css$/,
    use: [
        ...
        {
            loader: 'css-loader'.options: {
                modules: {
                  localIdentName: '[hash:base64:8]',}}}],},Copy the code

Load the sass

Sass is an auxiliary tool to enhance CSS. It adds variables, nesting, mixing, importing and other functions on the basis of CSS, enabling us to better manage style files and more efficient development projects.

Installation:

npm install sass-loader sass --save-dev
Copy the code

Sass-loader needs to be added in webPack to process SASS files and convert them into CSS files.

{
    test: /\.(scss|sass)$/,
    use: [
        ...
        'sass-loader'],},Copy the code

Configure the React

When we write react code, we use JSX syntax, but the browser does not know JSX syntax, we need to convert JSX syntax to the browser knows JSX syntax, react. CreateElement (…) .

The required Babel plug-in:

  1. @babel/preset-react

Installation:

npm install --save-dev @babel/preset-react
Copy the code

Use:

{
  loader: 'babel-loader'.options: {
    presets: [
      "@babel/preset-env"["@babel/preset-react",
        {
          runtime: 'automatic',}],],}},Copy the code

In previous versions, when using JSX syntax, we had to introduce:

import react from 'react';
Copy the code

In the latest version, we can skip this step by setting the Runtime to Automatic, and Babel will automatically import JSX conversion functions for us.

Configuration Typescript

Browsers do not support the SYNTAX of TS. We need to compile the TS file and convert it to JS before the browser can recognize it.

Installation:

npm install --save-dev @babel/preset-typescript
Copy the code

Use:

{
  loader: 'babel-loader'.options: {
    presets: [
      "@babel/preset-env"["@babel/preset-react",
        {
          runtime: 'automatic',}],"@babel/preset-typescript",].}},Copy the code

You also need to add tsconfig.json to the project root directory:

{
  "compilerOptions": {
    "target": "es5"."lib": [
      "dom"."dom.iterable"."esnext"]."allowJs": true."skipLibCheck": true."esModuleInterop": true."allowSyntheticDefaultImports": true."strict": true."forceConsistentCasingInFileNames": true."noFallthroughCasesInSwitch": true."module": "esnext"."moduleResolution": "node"."resolveJsonModule": true."isolatedModules": true."noEmit": true."jsx": "react-jsx",},"include": [
    "src"]},Copy the code

For details, see the TS official website.

Configuration ESLint

In multiplayer development, we want everyone to write the same code style, so ESLint will help us do that.

Installation:

yarn add -D eslint eslint-webpack-plugin yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser yarn add  -D eslint-config-airbnb eslint-config-airbnb-typescript yarn add -D eslint-plugin-import eslint-plugin-jsx-a11y
yarn add -D eslint-config-prettier eslint-plugin-react eslint-plugin-react-hooks
Copy the code

We chose the ESLint-config-Airbnb configuration, which has good support for JSX, Hooks, TypeScript, and A11y accessibility, and is probably one of the most popular and stringent ESLint validations available.

Next, create the ESLint configuration file.eslintrc.js:

module.exports = {
  root: true.parser: '@typescript-eslint/parser'.extends: [
    'airbnb'.'airbnb-typescript'.'airbnb/hooks'.'plugin:@typescript-eslint/recommended'.'plugin:@typescript-eslint/recommended-requiring-type-checking'.'plugin:react/jsx-runtime',].parserOptions: {
    project: './tsconfig.json',},ignorePatterns: [". *"."webpack"."public"."node_modules"."dist"].// Ignores the specified folder or file
  rules: {
    // Add rules to override here
    "react/function-component-definition": 0."quotes": ["error"."single"]."jsx-quotes": ["error"."prefer-single"]}};Copy the code

Eslint-webpack-plugin: eslint-webpack-plugin: eslint-webpack-plugin: eslint-webpack-plugin

{
    plugins: [
        new ESLintPlugin({
          extensions: ['.tsx'.'.ts'.'.js'.'.jsx'].fix: true.// Automatically fix the error code]}}),Copy the code

Ts error on command line

In the process of packaging, we found that the error in the code indicating TS could still be packaged successfully, which was not what we expected. The expectation is that when TS displays errors in code, it should also report errors in packaging.

Installation:

npm instal --save-dev fork-ts-checker-webpack-plugin
Copy the code

Use:

const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

plugins: [
    new ForkTsCheckerWebpackPlugin({
        typescript: {
             configFile: path.resolve(__dirname, '.. /tsconfig.json')}}); ]Copy the code

Configure aliases and extensions

When importing the file, it will say:

import demo from 'demo.js';
Copy the code

We can configure the extension in Webpack and omit the file suffix when importing files later:

resolve: {
    extensions: ['.tsx'.'.ts'.'.jsx'.'.js'.'.json'],}Copy the code

When we introduce some files, the path is long, cumbersome to write, and difficult to read, such as:

import demo from './src/xxx/xx/components/demo';
Copy the code

We can configure aliases in webpack to shorten file paths:

resolve: {
    alias: {
        'components': path.resolve(__dirname, '.. /src/xxx/xx/components/'),}}Copy the code

We can then import the file like this:

import demo from 'components/demo';
Copy the code

To optimize the

Our optimization can be divided into two aspects, one is the development optimization, the other is the packaging optimization.

Optimization at development time

sourcemap

When developing code, we need to tune and locate errors in order to accurately locate the source code. In this case, we need to configure sourcemap:

webpack.common.js

devtool: 'cheap-module-source-map'.Copy the code

After configuration, when the code reported an error, the browser will display the exact information of the code reported error.

Configure the cache

When we start the project, all files are rebuilt each time, but some files are permanent, such as those in node_modules, and don’t need to be rebuilt each time.

So let’s talk about these long-term invariant files for caching:

webpack.common.js

cache: {
  type: "filesystem".// Use file caching
},
Copy the code

On the next startup, WebPack first reads the files in the cache to speed up the build.

Babel cache: The cache feature of Babel is the same as that of WebPack. When building, Babel first goes back to the cache to speed up the build:

{
  loader: 'babel-loader'.options: {
    cacheDirectory: true,}}Copy the code

By default, cached files are stored in node_modules’. Cache folder.

Enable HRM module hot replacement

What is HRM? In simple terms, when a module is modified, it is immediately refreshed, but other modules are not refreshed.

a.js -> b.js
Copy the code

File A references file B. We modify file B without TURNING on HRM, so the whole page will be refreshed.

After HRM is enabled, file B will be updated immediately, but file A will not be updated.

Use:

webapck.common.js

devServer: {
    hot: true
}
Copy the code

Add the following code to the files that need hot updates:

if(module && module.hot) {
  module.hot.accept() // Accept self-update
}
Copy the code

But it was impossible to add every file manually during our development, and there was no need for hot-updated code when it came time to pack and go live.

So there are plug-ins that automatically add hot update functions:

  • React Hot Loader: Adjust the React component in real time.
  • Vue Loader: This Loader supports HMR of Vue components and provides out-of-the-box experience.
  • Elm Hot WebPack Loader: HMR support for the Elm programming language.
  • Angular HMR: No loader is necessary! Simply modify the NgModule main file, which takes full control of the HMR API.
  • Svelte Loader: This Loader supports hot update of Svelte components immediately after unpacking.

For React, the React Hot Loader is no longer used. Instead, react-refresh is used.

Installation:

yarn add -D @pmmmwh/react-refresh-webpack-plugin react-refresh
Copy the code

Use:

const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); // react hot update


{
    module: {
        rules: [{test: /\.(js|jsx|ts|tsx)$/,
              include: path.resolve(__dirname, '.. /src'),
              use: [{loader: 'babel-loader'.options: {
                    plugins: [
                      isEnvDevelopment && 'react-refresh/babel',
                    ].filter(Boolean),}},]}]},plugins: [
        isEnvDevelopment && new ReactRefreshWebpackPlugin(),
    ]
}
Copy the code

Add react-refresh/ Babel to plugins for babel-load and @pmmmWh /react-refresh-webpack-plugin to plugins for webpack. It is important to note that hot updates are only enabled in the development environment. If it is enabled in production, the hot updates are packaged together, but it has no effect on our production code.

For hot updates to CSS, HRM is already implemented internally in the style-loader we use.

Optimization at packaging time

Out of the CSS

The mini-CSS-extract-plugin extracts CSS from JS into a separate CSS file and supports on-demand loading of CSS and Sourcemaps.

Installation:

npm install --save-dev mini-css-extract-plugin
Copy the code

Use:

const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // Separate CSS from JS into a separate CSS file


{
    module: {
        rules: [{test: /\.css$/,
            use: [
                isEnvProduction ? 
                MiniCssExtractPlugin.loader: 
                'style-loader'.'css-loader'],},]},plugins: [
      new MiniCssExtractPlugin(),
  ]
  
}
Copy the code

By environment difference, style-loader is used in development environment and Mini-CSS-extract-Plugin is used in production environment.

The separation

In the development process, the same file will inevitably be referenced by multiple files. After packaging, the referenced file will repeatedly exist in the file that references it. We need to package it into an independent file to achieve the purpose of reuse.

Using splitChunks:

{
    optimization: {
        splitChunks: {
            chunks: 'all'}}}Copy the code

Minimize the volume of the incoming chunk

Through configuration optimization runtimeChunk, runtime will entry file mentioned separately to create a chunk of code, reduce the volume of the chunk entrance.

{
    optimization: {
        runtimeChunk: 'single'}}Copy the code

Compression js

Terser-webpack-plugin is usually used to compress JS code. This plugin is built into Webpack 5 and is automatically enabled when mode is production.

If we want to customize:

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimizer: [
        newTerserPlugin({... }); ] ,}};Copy the code

Compress CSS

We will use the CSS-Minimizer-Webpack-plugin.

It is more accurate to use query strings in source maps and Assets than the optimize-CSS-assets-webpack-plugin, and supports caching and concurrent mode.

Installation:

npm install css-minimizer-webpack-plugin --save-dev
Copy the code

Use:

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); 

module.exports = {
  optimization: {
    minimizer: [
        new CssMinimizerPlugin();
        '... '],}};Copy the code

Note here that if we only want to add additional plug-ins to work with the default plug-in, we need to add ‘… ‘to add the default plug-in.

dll

Using the DllPlugin to package code that does not change frequently into a single file can speed up the build process when packaging.

Use: First we create a new webapck.dll. Js file and add infrequently changed packages to the entry:

const paths = require('./paths');
const webpack = require('webpack');

module.exports = {

  mode: 'production'.entry: {
    vendor: [
      'react'.'react-dom']},output: {
    path: paths.dllPath,
    filename: paths.dllFilename,
    library: '[name]_dll_library'
  },

  plugins: [
    // Use the DllPlugin plugin to compile the NPM package configured above
    // Generates a JSON file with configuration information about dl.js
    new webpack.DllPlugin({
      path: paths.dllJsonPath,
      name: '[name]_dll_library'}})];Copy the code

Then we add the package command at package.json:

{
  "name": "webpack5"."version": "1.0.0"."description": ""."private": true."scripts": {
    "dll": "webpack --config ./webpack/webpack.dll.js". },... }Copy the code

Then we run: NPM run DLL

Finally, a DLL folder will be generated in the root directory of the project, which will generate a JS file containing the modules we need to package separately: react, react-dom, and a JSON file containing the information of the packaged modules.

- dll
  - vendor.dll.js
  - dll.manifest.json
Copy the code

Then we need to do two more things:

  1. Tell Webpack when we go live to pack, do not pack our DLL modules
  2. The js file after the successful packaging will not contain the module of our DLL, so we need to introduce the JS file from the DLL.

webpack.common.js

We use the DllReferencePlugin to exclude DLL modules:

new webpack.DllReferencePlugin({
  manifest: paths.dllJsonPath
})
Copy the code

We need to import the JSON file that comes out of the DLL. The JSON file contains information about the modules that have been packaged, which will be excluded when the Webpack is packaged.

Then we use add-asset-html-webpack-plugin to import the DLL from the file:

Installation:

yarn add  -D add-asset-html-webpack-plugin
Copy the code

Use:

const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');

new AddAssetHtmlWebpackPlugin({
  filepath: path.resolve(__dirname, '.. /dll/vendor.dll.js'),
  publicPath: ' '
})
Copy the code

It should be noted here that after I introduced the JS file, I packaged it and ran it, but I found that Vendor.dll. js could not be found during the run. I checked the path and found that there was an extra layer: auto, which needs to set publicPath to empty string.

Tree shaking – Tree shaking

The simple way to understand tree shaking is to remove unused JS code when repackaging.

Turn tree shaking on: Just set mode to Production and tree shaking will start automatically.

Sometimes we develop plugins that have a lot of methods for other people to use that are not used inside the plugin and are shaken off by Tree when it is packaged. We need to declare the sideEffects property in package.js:

{
  "name": "webpack5"."version": "1.0.0"."description": ""."private": true."sideEffects": ["*.js"."*.css"]... }Copy the code

Tell webpack via sideEffects that there are sideEffects in my declared file, don’t tree shaking.

Clear the unused CSS

Cleaning up unused CSS is a tree shaking of CSS. We use Purgecss to do this.

Since we use CSS modules a lot, we need to install @fullHuman/postCSS-purgecss

yarn add -D @fullhuman/postcss-purgecss
Copy the code

Use:

 {
    loader: 'postcss-loader'.options: {
      postcssOptions: {
        plugins: [
          ...
          isEnvProduction && 
          [
            '@fullhuman/postcss-purgecss'.// Delete the unused CSS
            {
              content: [ paths.appHtml, ...glob.sync(path.join(paths.appSrc, '/**/*.{tsx,ts,js,jsx}'), { nodir: true }) ],
            }
          ]
        ].filter(Boolean),}}},Copy the code

Just add the plug-in to the PostCSS-loader.

multithreading

We can use thread-loader to enable multi-threaded construction in heavy work and time-consuming operations, which can improve the construction speed.

Installation:

npm install --save-dev thread-loader
Copy the code

Use:

{
  test: /\.(js|jsx|ts|tsx)$/,
  include: paths.appSrc,
  use: [{loader: "thread-loader".options: {
        workers: 2.workerParallelJobs: 2}}},]Copy the code

Webpack mentions a bug in Node-sass that blocks threads from the Node.js thread pool. When thread-Loader is used, you need to set workerParallelJobs: 2.

Clear the output folder before packing

Before Webpack 5, we used the clean-webpack-plugin to clean the output folder.

Use:

output: {
    clean: true,}Copy the code

Set clean to true in output

Lazy loading

Lazy loading can also be called on-demand loading. In essence, modules that are not immediately needed in a file are separated and loaded only when they are in use. In this way, the size of the entry file is greatly reduced and the loading speed is faster.

The use is to use the import dynamic import way to achieve lazy loading.

Use:

import('demo.js')
 .then({default: res} => {
     ...
 });
Copy the code

When webpack is packaged, the demo file is packaged into a separate file that is loaded when used.

You can use magic comments to specify the name of the chunk and how the file is loaded.

Specify chunk name:

import(/* webpackChunkName: "demo_chunk" */ 'demo.js')
 .then({default: res} => {
     ...
 });
Copy the code

Specify the loading mode:

import(
   /* webpackPreload: "demo_chunk", webpackPrefetch: true */
   'demo.js'
)
 .then({default: res} => {
     ...
 });
Copy the code

Let’s look at the difference between the two loading methods:

  1. Prefetch: preloads files when the browser is idle
  2. Preload: files are loaded immediately

Use the CDN

After the packaging is complete, static resources can be uploaded to the CDN to accelerate the loading speed of resources through THE CDN.

webpack.common.js

output: {
    publicPath: isEnvProduction ? 'https://CDNxxx.com' : ' ',},Copy the code

You can set the CDN domain name by configuring publicPath.

Browser cache

The browser cache is used to load the corresponding resources after the first page is loaded. The browser cache is used to read the resources after the next page is entered, which is faster.

Webpack can generate hash values based on the contents of the file, and the hash will change when the contents change.

Use:

module.exports = {
  output: {
    filename: isEnvProduction
      ? "[name].[contenthash].bundle.js"
      : "[name].bundle.js",}};Copy the code

Enabling hash values only in the production environment affects build efficiency in the development environment.

Optimization tools

Here is a brief introduction:

  1. progress-bar-webpack-plugin: Indicates that the packaging progress is displayed, but the packaging speed is affected. Use this parameter with discretion. If the packaging time is too long, use this parameter.
  2. speed-measure-webpack-plugin: View the loader and Plugin time, and optimize the Loader and Plugin time.
  3. webpack-bundle-analyzer: You can view the proportion of each file after packaging, for targeted optimization.

Note: The above are webpack plugins

conclusion

Use the loader

Loader handling CSS:

  1. style-loader: Inserts CSS styles into the DOM
  2. css-loader: Parses CSS files to generate JS objects
  3. postcss-loader: Handles CSS compatibility problems
    • postcss-preset-env: postCSS preconfigured plug-in that handles CSS compatibility and automatically adds browser kernel prefixes
    • @fullhuman/postcss-purgecss: Clears unused CSS styles
  4. sass-loader: Processes sASS files
  5. less-loader: Processes less files
  6. mini-css-extract-plugin: Use the loader inside the plug-in to package the CSS into a separate file. You also need to add the plug-in topluginsIn the.

Deal with js | ts loader:

1. Babel-loader: handle JS compatibility, transfer ES6 to ES5, and polyfill the new Api.

Presets – Babel presets

  • @babel/preset-env: bebal Presets the es6 to ES5 plug-in
  • @babel/preset-react: JSX syntax conversion
  • @/babel/preset-typescript: TS syntax to JS

Plugins-babel plug-in

  • @babel/plugin-transform-runtime: Reference Babel utility functions as modules for reuse purposes. Reference polyfill’s methods as modules to address the problem of polyfill contaminating global variables. Reference Genarate as a module to deal with the problem of generate contaminating global variables.
  • react-refresh/babel: React component Hot Update (HRM)

Loader to handle resource files: images, font files, etc

  1. url-loaderSet the options.limite property. If the value is smaller than this property, the image will be base64 (inline). If the value is larger than this property, the image will be sent to the output directory. (Built-in file-loader)
  2. file-loader: Processes files and sends them to the output directory
  3. raw-loader: Exports the file as a string

Use the built-in Asset Module in webpack5:

  1. asset/resourcealternativefile-loader
  2. asset/inlinealternativeurl-loader
  3. asset/sourcealternativeraw-loader
  4. assetBy configuringparser.dataUrlCondition.maxSizeTo automatically select useasset/resource(larger than MaxSize) andasset/inline(Less than maxSize)

The WebPack plugin used

  1. html-webpack-plugin: Automatically generates HTML files, and automatically references files built by WebPack.
  2. mini-css-extract-plugin: Separate the CSS into a separate file.
  3. clean-webapck-plugin: Automatically clears the output file directory, byoutput.cleanalternative
  4. copy-webpack-plugin: Copies files to another folder
  5. compression-webpack-plugin: Compressed file
  6. webpack.DefinePlugin: Injects global variables
  7. webpack.DllPluginGenerate package a JSON file containing information about the package module
  8. webpack.DllReferancePlugin: Using the JSON file generated by Dllplugin causes WebPack to exclude modules contained in the JSON file when packaging.
  9. add-asset-html-webpack-pluginAdd bundle files to HTML. In this article, js files generated by DLLS are added to HTML using this plug-in.
  10. eslint-webpack-plugin: Verifies the code format
  11. fork-ts-checker-webpack-plugin: Displays ts errors on the command line
  12. @pmmmwh/react-refresh-webpack-plugin: React component Hot Update (HRM)