Summary of advanced use of WebPack5 (3)

Supporting video: Getting started with Webpack 5 in Shang Silicon Valley
Supporting code: https://gitee.com/Augenstern-creator/kuang-study-web-pack5
Insert image description here

1. Improve development physical examination

1.1、SourceMap

The code we run during development is compiled by webpack. All css and js are merged into one file, and other codes are added. At this time, if the code runs incorrectly, we will not be able to understand the error location of the code. Once there are a lot of code files developed in the future, it will be difficult to find where the errors occur.

So we need more accurate error prompts to help us develop better code.

  • SourceMap (Source Code Mapping) is a solution for generating files that map source code to built code one-to-one.
  • It will generate a xxx.map file, which contains the mapping relationship between each row and column of the source code and the built code. When an error occurs in the built code, the xxx.map file will be used to find the mapped source code error location from the post-build code error location, allowing the browser to prompt the source code file error location and help us find the source of the error faster.

1.2. Use

By looking at the Webpack DevTool document open in new window , we can see that there are many situations for the value of SourceMap, but in actual development we only need to pay attention to two situations:

  • Development mode:cheap-module-source-map
    • Advantages: Packaging and compilation are fast, only include line mapping
    • Disadvantages: No column mapping
module.exports = {
    
    
  // 其他省略
  mode: "development",
  devtool: "cheap-module-source-map",
};
  • Production mode:source-map
    • Advantages: Contains row/column mapping
    • Disadvantages: Packaging and compilation are slower
module.exports = {
    
    
  // 其他省略
  mode: "production",
  devtool: "source-map",
};

2. Improve packaging and construction speed

During development, we modified one of the module codes. Webpack will repackage and compile all modules by default, which is very slow.

Therefore, we need to modify a certain module code, and only this module code needs to be repackaged and compiled, while other modules remain unchanged, so that the packaging speed can be very fast.

2.1、HotModuleReplacement

  • HotModuleReplacement(HMR/Hot Module Replacement): Replace, add or remove modules while the program is running without reloading the entire page

webpack.dev.jsConfigure in

// 开发服务器
devServer: {
    
    
    host: "localhost", // 启动服务器域名
        port: "3000", // 启动服务器端口号
            open: true, // 是否自动打开浏览器
                hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
},

2.2、oneOf

When packaging, each file will be processed by all loaders. Although it testis not actually processed due to regularity reasons, it must be processed once. slower

Insert image description here

OneOf As the name implies, it can only match the previous loader, and the rest will not match.

Modify webpack.dev.jsthe code: put all loaders oneOf:[]in

 // 加载器
  module: {
    
    
    // loader的配置
    rules: [
      {
    
    
        oneOf: [
          {
    
    
            // 用来匹配 .css 结尾的文件
            test: /\.css$/,
            // use 数组里面 Loader 执行顺序是从右到左
            use: ["style-loader", "css-loader"],
          },
          {
    
    
            test: /\.less$/,
            use: ["style-loader", "css-loader", "less-loader"],
          },
          {
    
    
            test: /\.s[ac]ss$/,
            use: ["style-loader", "css-loader", "sass-loader"],
          },
          {
    
    
            test: /\.styl$/,
            use: ["style-loader", "css-loader", "stylus-loader"],
          },
          {
    
    
            test: /\.(png|jpe?g|gif|webp)$/,
            type: "asset",
            parser: {
    
    
              dataUrlCondition: {
    
    
                maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
              }
            },
            generator: {
    
    
              // 将图片文件输出到 static/imgs 目录中
              // 将图片文件命名 [hash:8][ext][query]
              // [hash:8]: hash值取8位
              // [ext]: 使用之前的文件扩展名
              // [query]: 添加之前的query参数
              filename: "static/imgs/[hash:8][ext][query]",
            },
          },
          {
    
    
            // 开发中可能还存在一些其他资源,如音视频等,我们也一起处理了
            test: /\.(ttf|woff2?|map4|map3|avi)$/,
            type: "asset/resource",
            generator: {
    
    
              filename: "static/media/[hash:8][ext][query]",
            },
          },
          {
    
    
            test: /\.js$/,
            exclude: /node_modules/, // 排除node_modules代码不编译
            use: {
    
    
              loader: "babel-loader",
            }
          },
        ]
      }
    ],
  },

The production mode is also configured in the same way. We execute the following running command. dist/index.htmlIf there is an effect, it means success.

# 开发模式
npm run dev

# 生产模式
npm run build

2.3、Include/Exclude

During development, we need to use third-party libraries or plug-ins, and all files are downloaded to node_modules. These files can be used directly without compilation. Therefore, when we process js files, we must exclude the files under node_modules.

  • include: Contains, only processes xxx files
  • exclude: Excluded, all files except xxx files will be processed

webpack.dev.jsMake the following configuration in :

// 加载器
  module: {
    
    
    // loader的配置
    rules: [
      {
    
    
        oneOf: [
          {
    
    
            // 用来匹配 .css 结尾的文件
            test: /\.css$/,
            // use 数组里面 Loader 执行顺序是从右到左
            use: ["style-loader", "css-loader"],
          },
          {
    
    
            test: /\.less$/,
            use: ["style-loader", "css-loader", "less-loader"],
          },
          {
    
    
            test: /\.s[ac]ss$/,
            use: ["style-loader", "css-loader", "sass-loader"],
          },
          {
    
    
            test: /\.styl$/,
            use: ["style-loader", "css-loader", "stylus-loader"],
          },
          {
    
    
            test: /\.(png|jpe?g|gif|webp)$/,
            type: "asset",
            parser: {
    
    
              dataUrlCondition: {
    
    
                maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
              }
            },
            generator: {
    
    
              // 将图片文件输出到 static/imgs 目录中
              // 将图片文件命名 [hash:8][ext][query]
              // [hash:8]: hash值取8位
              // [ext]: 使用之前的文件扩展名
              // [query]: 添加之前的query参数
              filename: "static/imgs/[hash:8][ext][query]",
            },
          },
          {
    
    
            // 开发中可能还存在一些其他资源,如音视频等,我们也一起处理了
            test: /\.(ttf|woff2?|map4|map3|avi)$/,
            type: "asset/resource",
            generator: {
    
    
              filename: "static/media/[hash:8][ext][query]",
            },
          },
          {
    
    
            test: /\.js$/,
            // exclude: /node_modules/, // 排除node_modules代码不编译
            include: path.resolve(__dirname, "../src"), // 只包含 src 下的文件
            use: {
    
    
              loader: "babel-loader",
            }
          },
        ]
      }
    ],
  },
  // 插件
  plugins: [
    new ESLintWebpackPlugin({
    
    
      // 指定检查文件的根目录(这里检查 src 文件夹)
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 默认值:排除 node_modules 文件夹
    }),
    new HtmlWebpackPlugin({
    
    
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "../public/index.html"),
    }),
  ],

The same goes for production mode

2.4、Cache

Each time the js file is packaged, it must be checked by Eslint and compiled by Babel, which is relatively slow. We can cache previous Eslint checks and Babel compilation results so that the second packaging will be faster.

Cache can cache Eslint checks and Babel compilation results.

module: {
    
    
    // loader的配置
    rules: [
      {
    
    
        oneOf: [
          {
    
    
            // 用来匹配 .css 结尾的文件
            test: /\.css$/,
            // use 数组里面 Loader 执行顺序是从右到左
            use: getStyleLoaders(),
          },
          {
    
    
            test: /\.less$/,
            use:getStyleLoaders("less-loader"),
          },
          {
    
    
            test: /\.s[ac]ss$/,
            use: getStyleLoaders("sass-loader"),
          },
          {
    
    
            test: /\.styl$/,
            use:getStyleLoaders("stylus-loader"),
          },
          {
    
    
            test: /\.(png|jpe?g|gif|webp)$/,
            type: "asset",
            parser: {
    
    
              dataUrlCondition: {
    
    
                maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
              }
            },
            generator: {
    
    
              // 将图片文件输出到 static/imgs 目录中
              // 将图片文件命名 [hash:8][ext][query]
              // [hash:8]: hash值取8位
              // [ext]: 使用之前的文件扩展名
              // [query]: 添加之前的query参数
              filename: "static/imgs/[hash:8][ext][query]",
            },
          },
          {
    
    
            // 开发中可能还存在一些其他资源,如音视频等,我们也一起处理了
            test: /\.(ttf|woff2?|map4|map3|avi)$/,
            type: "asset/resource",
            generator: {
    
    
              filename: "static/media/[hash:8][ext][query]",
            },
          },
          {
    
    
            test: /\.js$/,
            //exclude: /node_modules/, // 排除node_modules代码不编译
            include: path.resolve(__dirname, "../src"), // 只包含 src 下的文件
            use: {
    
    
              loader: "babel-loader",
            },
            options: {
    
    
              cacheDirectory: true, // 开启babel编译缓存
              cacheCompression: false, // 缓存文件不要压缩
            },
          },
        ]
      }
    ],
  },
  // 插件
  plugins: [
    new ESLintWebpackPlugin({
    
    
      // 指定检查文件的根目录(这里检查 src 文件夹)
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 默认值:排除 node_modules 文件夹
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(
          __dirname,
          "../node_modules/.cache/.eslintcache"
      ),
    }),

2.5、Thead

As the project becomes larger and larger, the packaging speed becomes slower and slower, and it even takes an afternoon to package the code. This speed is relatively slow. We want to continue to improve the packaging speed. In fact, we need to improve the packaging speed of js, because there are relatively few other files.

  • The main tools for processing js files are eslint, babel, and Terser, so we need to improve their running speed.
  • We can enable multiple processes to process js files at the same time, which is faster than the previous single-process packaging.

The number of processes we start is the number of cores of our CPU:

  1. How to get the number of cores of the CPU, because every computer is different
// nodejs核心模块,直接使用
const os = require("os");
// cpu核数
const threads = os.cpus().length;
  1. Download package
npm i thread-loader -D
  1. Introduce webpack.dev.jsos andterser-webpack-plugin
const os = require("os");

const TerserPlugin = require("terser-webpack-plugin");
  1. webpack.prod.jsused in
// Node.js的核心模块,专门用来处理文件路径
const os = require("os");
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");


// cpu核数
const threads = os.cpus().length;
console.log(threads); // 8

// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
    
    
  return [
    MiniCssExtractPlugin.loader,
    "css-loader",
    {
    
    
      loader: "postcss-loader",
      options: {
    
    
        postcssOptions: {
    
    
          plugins: [
            "postcss-preset-env", // 能解决大多数样式兼容性问题
          ],
        },
      },
    },
    preProcessor,
  ].filter(Boolean);
};

module.exports = {
    
    
  // 入口
  // 相对路径和绝对路径都行
  entry: "./src/main.js",
  // 输出
  output: {
    
    
    // path: 文件输出目录,必须是绝对路径
    // path.resolve()方法返回一个绝对路径
    // __dirname 当前文件的文件夹绝对路径
    path: path.resolve(__dirname, "../dist"),   // 生产模式需要输出
    // filename: 输出文件名,入口文件打包输出到 `static/js/main.js`中,其他文件仍打包到上方 path 下
    filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
    clean: true, // 自动将上次打包目录资源清空

  },
  // 加载器
  module: {
    
    
    // loader的配置
    rules: [
      {
    
    
        oneOf: [
          {
    
    
            // 用来匹配 .css 结尾的文件
            test: /\.css$/,
            // use 数组里面 Loader 执行顺序是从右到左
            use: getStyleLoaders(),
          },
          {
    
    
            test: /\.less$/,
            use:getStyleLoaders("less-loader"),
          },
          {
    
    
            test: /\.s[ac]ss$/,
            use: getStyleLoaders("sass-loader"),
          },
          {
    
    
            test: /\.styl$/,
            use:getStyleLoaders("stylus-loader"),
          },
          {
    
    
            test: /\.(png|jpe?g|gif|webp)$/,
            type: "asset",
            parser: {
    
    
              dataUrlCondition: {
    
    
                maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
              }
            },
            generator: {
    
    
              // 将图片文件输出到 static/imgs 目录中
              // 将图片文件命名 [hash:8][ext][query]
              // [hash:8]: hash值取8位
              // [ext]: 使用之前的文件扩展名
              // [query]: 添加之前的query参数
              filename: "static/imgs/[hash:8][ext][query]",
            },
          },
          {
    
    
            // 开发中可能还存在一些其他资源,如音视频等,我们也一起处理了
            test: /\.(ttf|woff2?|map4|map3|avi)$/,
            type: "asset/resource",
            generator: {
    
    
              filename: "static/media/[hash:8][ext][query]",
            },
          },
          {
    
    
            test: /\.js$/,
            //exclude: /node_modules/, // 排除node_modules代码不编译
            include: path.resolve(__dirname, "../src"), // 只包含 src 下的文件
            use: [
              {
    
    
                loader: "thread-loader", // 开启多进程
                options: {
    
    
                  workers: threads, // 数量
                },
              },
              {
    
    
                loader: "babel-loader",
                options: {
    
    
                  cacheDirectory: true, // 开启babel编译缓存
                },
              },
            ],
            options: {
    
    
              cacheDirectory: true, // 开启babel编译缓存
              cacheCompression: false, // 缓存文件不要压缩
            },
          },
        ]
      }
    ],
  },
  // 插件
  plugins: [
    new ESLintWebpackPlugin({
    
    
      // 指定检查文件的根目录(这里检查 src 文件夹)
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 默认值:排除 node_modules 文件夹
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(
          __dirname,
          "../node_modules/.cache/.eslintcache"
      ),
      // 开启多进程
      threads,
    }),
    new HtmlWebpackPlugin({
    
    
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    // 提取css成单独文件
    new MiniCssExtractPlugin({
    
    
      // 定义输出文件名和目录
      filename: "static/css/main.css",
    }),
    // css压缩
    //new CssMinimizerPlugin(),
  ],
  // 优化
  optimization: {
    
    
    minimize: true,
    minimizer: [
      // css压缩也可以写到optimization.minimizer里面,效果一样的(webpack5的写法)
      new CssMinimizerPlugin(),
      // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
      new TerserPlugin({
    
    
        parallel: threads // 开启多进程
      })
    ],
  },
  // 模式
  mode: "production", // 生产模式
  devtool: "source-map",
};
  1. run command
npm run build

The same is true for production mode. The difference is that production mode does not require optimizationoptimization and compression.

3. Reduce code size

During development, we defined some tool function libraries, or referenced third-party tool function libraries or component libraries. If there is no special processing, we will introduce the entire library when packaging, but in fact we may only use a very small part of the functions. If the entire library is packaged in this way, the volume will be too large.

3.1、Tree Shaking

Tree Shakingis a term commonly used to describe the removal of unused code from JavaScript.

NOTE: It depends ES Module.

  • Webpack has enabled this feature by default and no additional configuration is required.

3.2、Babel

  • Babel inserts auxiliary code for each file compiled, making the code size too large!

  • Babel uses very small helper code for some public methods, _extende.g. By default it will be added to every file that requires it.

  • You can use these auxiliary codes as an independent module to avoid repeated introduction.

@babel/plugin-transform-runtimeThe plug-in can solve the above problem: disable Babel's automatic runtime injection of each file, and instead introduce @babel/plugin-transform-runtimeand make all auxiliary code reference from here.

  1. Download package
npm i @babel/plugin-transform-runtime -D
  1. Configure in webpack.prod.js: babel-loaderJust configure the plug-in in
{
    
    
    loader: "babel-loader",
        options: {
    
    
            cacheDirectory: true, // 开启babel编译缓存
            cacheCompression: false, // 缓存文件不要压缩
            plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
        },
},

3.3、Image Minimizer

If more pictures are referenced in the development project, the picture size will be larger and the request speed will be slower in the future. We can compress images to reduce image size.

Note: If the images in the project are all online links, then this is not necessary. Only local project static images need to be compressed.

  1. Download package
# image-minimizer-webpack-plugin 用来压缩图片的插件
npm i image-minimizer-webpack-plugin imagemin -D

There are still remaining packages that need to be downloaded. There are two modes:

  • Lossless compression: image quality remains unchanged
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D
  • Lossy compression: picture quality is reduced
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D
  1. Taking the lossless compression configuration as an example, first introduce the plug-in
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
  1. webpack.prod.jsConfigure in
// 优化
  optimization: {
    
    
    minimize: true,
    minimizer: [
      // css压缩也可以写到optimization.minimizer里面,效果一样的(webpack5的写法)
      new CssMinimizerPlugin(),
      // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
      new TerserPlugin({
    
    
        parallel: threads // 开启多进程
      }),
      // 压缩图片
      new ImageMinimizerPlugin({
    
    
        minimizer: {
    
    
          implementation: ImageMinimizerPlugin.imageminGenerate,
          options: {
    
    
            plugins: [
              ["gifsicle", {
    
     interlaced: true }],
              ["jpegtran", {
    
     progressive: true }],
              ["optipng", {
    
     optimizationLevel: 5 }],
              [
                "svgo",
                {
    
    
                  plugins: [
                    "preset-default",
                    "prefixIds",
                    {
    
    
                      name: "sortAttrs",
                      params: {
    
    
                        xmlnsOrder: "alphabetical",
                      },
                    },
                  ],
                },
              ],
            ],
          },
        },
      }),
    ],
    
  },

4. Optimize code running performance

4.1、Code Split

  • When packaging code, all js files will be packaged into one file, which is too large. If we only want to render the homepage, we should only load the js file of the homepage, and other files should not be loaded.
  • Therefore, we need to code-split the files generated by packaging, generate multiple js files, and only load a certain js file for whichever page is rendered, so that fewer resources are loaded and the speed is faster.

Code Split mainly does two things:

  1. Split files: Split the files generated by packaging to generate multiple js files
  2. Load on demand: load whichever file is needed

Steps:

  1. Download package
npm i webpack webpack-cli html-webpack-plugin -D
  1. Create a new file in the following directory
├── public
├── src
|   ├── app.js
|   └── main.js
├── package.json
└── webpack.config.js
  1. Modify files

app.js

console.log("hello app");

main.js

console.log("hello main");

webpack.config.js

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  // 单入口
  // entry: './src/main.js',
  // 多入口
  entry: {
    
    
    main: "./src/main.js",
    app: "./src/app.js",
  },
  output: {
    
    
    path: path.resolve(__dirname, "./dist"),
    // [name]是webpack命名规则,使用chunk的name作为输出的文件名。
    // 什么是chunk?打包的资源就是chunk,输出出去叫bundle。
    // chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名无关。
    // 为什么需要这样命名呢?如果还是之前写法main.js,那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的)
    filename: "static/js/[name].js",
    clean: true
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: "./public/index.html",
    }),
  ],
  mode: "production",
};

run command

npx webpack

At this time we can see two js files output in the dist directory.

At this time we can see two js files output in the dist directory.

4.1.1. Extract duplicate codes

If the same code is referenced in multiple entry files, we do not want this code to be packaged into two files, resulting in code duplication and larger size.

We need to extract the repeated code of multiple entries, only package it to generate a js file, and other files can reference it.

  • app.js
import {
    
     sum } from "./math";

console.log("hello app");
console.log(sum(1, 2, 3, 4));
  • main.js
import {
    
     sum } from "./math";

console.log("hello main");
console.log(sum(1, 2, 3, 4, 5));
  • math.js
export const sum = (...args) => {
    
    
  return args.reduce((p, c) => p + c, 0);
};
  • Modify configuration file
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    
    
  // 单入口
  // entry: './src/main.js',
  // 多入口
  entry: {
    
    
    main: "./src/main.js",
    app: "./src/app.js",
  },
  output: {
    
    
    path: path.resolve(__dirname, "./dist"),
    // [name]是webpack命名规则,使用chunk的name作为输出的文件名。
    // 什么是chunk?打包的资源就是chunk,输出出去叫bundle。
    // chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名无关。
    // 为什么需要这样命名呢?如果还是之前写法main.js,那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的)
    filename: "static/js/[name].js",
    clean: true
  },
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: "./public/index.html",
    }),
  ],
  mode: "production",
  optimization: {
    
    
    // 代码分割配置
    splitChunks: {
    
    
      chunks: "all", // 对所有模块都进行分割
      // 以下是默认值
      // minSize: 20000, // 分割代码最小的大小
      // minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
      // minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
      // maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
      // maxInitialRequests: 30, // 入口js文件最大并行请求数量
      // enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
      // cacheGroups: { // 组,哪些模块要打包到一个组
      //   defaultVendors: { // 组名
      //     test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
      //     priority: -10, // 权重(越大越高)
      //     reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
      //   },
      //   default: { // 其他没有写的配置会使用上面的默认值
      //     minChunks: 2, // 这里的minChunks权重更大
      //     priority: -20,
      //     reuseExistingChunk: true,
      //   },
      // },
      // 修改配置
      cacheGroups: {
    
    
        // 组,哪些模块要打包到一个组
        // defaultVendors: { // 组名
        //   test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
        //   priority: -10, // 权重(越大越高)
        //   reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
        // },
        default: {
    
    
          // 其他没有写的配置会使用上面的默认值
          minSize: 0, // 我们定义的文件体积太小了,所以要改打包的最小文件体积
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};
  • run command
npx webpack

At this point we will find that 3 js files are generated, one of which is the extracted public module.

4.1.2. On-demand loading and dynamic import

To achieve on-demand loading, dynamically import modules. Additional configuration is required:

  • Revisemain.js
console.log("hello main");

document.getElementById("btn").onclick = function () {
    
    
  // eslint会对动态导入语法报错,需要修改eslint配置文件
  // webpackChunkName: "math":这是webpack动态导入模块命名的方式
  // "math"将来就会作为[name]的值显示
  import("./math.js").then(({
     
      sum }) => {
    
    
    // then 模块加载成功
    alert(sum(1, 2, 3, 4, 5));
  }).catch(() => {
    
    
    // catch 模块加载失败
    console.log("模块加载失败")
  });
};
  • Reviseapp.js
console.log("hello app");
  • Revisepublic/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<h1>Hello WebPack5</h1>
<button id="btn">计算</button>
</body>
</html>
  • modify .eslintrc.js, addplugins: ["import"]
module.exports = {
    
    
  // 继承 Eslint 规则
  extends: ["eslint:recommended"],
  env: {
    
    
    node: true, // 启用node中全局变量
    browser: true, // 启用浏览器中全局变量
  },
  plugins: ["import"], // 解决动态导入import语法报错问题 --> 实际使用eslint-plugin-import的规则解决的
  parserOptions: {
    
    
    ecmaVersion: 6, // es6
    sourceType: "module", // es6 模块化
  },
  rules: {
    
    
    "no-var": 2, // 不能使用 var 定义变量
  },
};
  • run command
npx webpack

We can find that once the module is imported through the import dynamic import syntax, the module is code-split and can also be loaded on demand.

4.1.4. Unified naming configuration

We unify the naming methods for packaged output resources of entry files, dynamic import and output resources, pictures, fonts and other resources.Update the previous configuration file

// Node.js的核心模块,专门用来处理文件路径
const os = require("os");
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");

// cpu核数
const threads = os.cpus().length;
console.log(threads); // 8

// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
    
    
  return [
    MiniCssExtractPlugin.loader,
    "css-loader",
    {
    
    
      loader: "postcss-loader",
      options: {
    
    
        postcssOptions: {
    
    
          plugins: [
            "postcss-preset-env", // 能解决大多数样式兼容性问题
          ],
        },
      },
    },
    preProcessor,
  ].filter(Boolean);
};

module.exports = {
    
    
  // 入口
  // 相对路径和绝对路径都行
  entry: "./src/main.js",
  // 输出
  output: {
    
    
    // path: 文件输出目录,必须是绝对路径
    // path.resolve()方法返回一个绝对路径
    // __dirname 当前文件的文件夹绝对路径
    path: path.resolve(__dirname, "../dist"),   // 生产模式需要输出
    // filename: 输出文件名,入口文件打包输出到 `static/js/[name].js`中,其他文件仍打包到上方 path 下
    filename: "static/js/[name].js", // 入口文件打包输出资源命名方式
    chunkFilename: "static/js/[name].chunk.js", // 动态导入输出资源命名方式
    assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)
    clean: true, // 自动将上次打包目录资源清空

  },
  // 加载器
  module: {
    
    
    // loader的配置
    rules: [
      {
    
    
        oneOf: [
          {
    
    
            // 用来匹配 .css 结尾的文件
            test: /\.css$/,
            // use 数组里面 Loader 执行顺序是从右到左
            use: getStyleLoaders(),
          },
          {
    
    
            test: /\.less$/,
            use:getStyleLoaders("less-loader"),
          },
          {
    
    
            test: /\.s[ac]ss$/,
            use: getStyleLoaders("sass-loader"),
          },
          {
    
    
            test: /\.styl$/,
            use:getStyleLoaders("stylus-loader"),
          },
          {
    
    
            test: /\.(png|jpe?g|gif|webp)$/,
            type: "asset",
            parser: {
    
    
              dataUrlCondition: {
    
    
                maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
              }
            },
            //generator: {
    
    
            //  // 将图片文件输出到 static/imgs 目录中
            //  // 将图片文件命名 [hash:8][ext][query]
            //  // [hash:8]: hash值取8位
            //  // [ext]: 使用之前的文件扩展名
            //  // [query]: 添加之前的query参数
            //  filename: "static/imgs/[hash:8][ext][query]",
            //},
          },
          {
    
    
            // 开发中可能还存在一些其他资源,如音视频等,我们也一起处理了
            test: /\.(ttf|woff2?|map4|map3|avi)$/,
            type: "asset/resource",
            //generator: {
    
    
            //  filename: "static/media/[hash:8][ext][query]",
            //},
          },
          {
    
    
            test: /\.js$/,
            //exclude: /node_modules/, // 排除node_modules代码不编译
            include: path.resolve(__dirname, "../src"), // 只包含 src 下的文件
            use: [
              {
    
    
                loader: "thread-loader", // 开启多进程
                options: {
    
    
                  workers: threads, // 数量
                },
              },
              {
    
    
                loader: "babel-loader",
                options: {
    
    
                  cacheDirectory: true, // 开启babel编译缓存
                  cacheCompression: false, // 缓存文件不要压缩
                  plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
                },
              },
            ],
          },
        ]
      }
    ],
  },
  // 插件
  plugins: [
    new ESLintWebpackPlugin({
    
    
      // 指定检查文件的根目录(这里检查 src 文件夹)
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 默认值:排除 node_modules 文件夹
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(
          __dirname,
          "../node_modules/.cache/.eslintcache"
      ),
      // 开启多进程
      threads,
    }),
    new HtmlWebpackPlugin({
    
    
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    // 提取css成单独文件
    new MiniCssExtractPlugin({
    
    
      // 定义输出文件名和目录
      filename: "static/css/[name].css",
      chunkFilename: "static/css/[name].chunk.css",
    }),
    // css压缩
    //new CssMinimizerPlugin(),
  ],
  // 优化
  optimization: {
    
    
    minimize: true,
    minimizer: [
      // css压缩也可以写到optimization.minimizer里面,效果一样的(webpack5的写法)
      new CssMinimizerPlugin(),
      // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
      new TerserPlugin({
    
    
        parallel: threads // 开启多进程
      }),

    ],
    // 代码分割
    splitChunks: {
    
    
      chunks: "all", // 对所有模块都进行分割
      // 其他的都用默认配置
    }
  },
  // 模式
  mode: "production", // 生产模式
  devtool: "source-map",
};

4.2、Preload/Prefetch

We have already done code splitting before, and will use the import dynamic import syntax to load code on demand (we also call it lazy loading, for example, routing lazy loading is implemented in this way), but the loading speed is not good enough, for example: when the user clicks This resource is only loaded when the button is pressed. If the resource is very large, the user will feel an obvious lag effect.

  • We want to load the resources we need to use later during the browser's idle time. We need to use Preloador Prefetchtechnology

  • Preload: Tells the browser to load the resource immediately

  • Preload: Tells the browser to load the resource immediately

Common points:

  • They will only load resources and not execute them.
  • All cached

the difference:

  • PreloadLoading priority is high, Prefetchloading priority is low
  • PreloadOnly the resources that need to be used on the current page can be loaded. PrefetchThe resources that need to be used on the current page can also be loaded. The resources that need to be used on the next page can also be loaded.

Summarize:

  • Resources with high priority on the current page are Preloadloaded using
  • The resources that need to be used on the next page are Prefetchloaded with

  1. Download package
npm i @vue/preload-webpack-plugin -D
  1. Introduced webpack.prod.js _
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
  1. Configuration
new PreloadWebpackPlugin({
    
    
    rel: "preload", // preload兼容性更好
    as: "script",
    // rel: 'prefetch' // prefetch兼容性更差
}),

4.3、Network Cache

In future development, we will use cache to optimize static resources, so that the browser can read the cache the second time it requests the resource, which is very fast.

  • But there will be a problem in this case, because the file names output before and after are the same, both called main.js. Once a new version is released in the future, because the file name has not changed, the browser will directly read the cache and will not load new resources. , the project cannot be updated.
  • So we start with the file name and make sure the file name is different before and after the update, so that we can cache it.

So the configuration is as follows:

  • Add to the path contenthash: generate a hash value based on the file content. The hash value will only change if the file content changes.
  • Store the hash value in a separate runtimefile
// Node.js的核心模块,专门用来处理文件路径
const os = require("os");
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

// cpu核数
const threads = os.cpus().length;
console.log(threads); // 8

// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
    
    
  return [
    MiniCssExtractPlugin.loader,
    "css-loader",
    {
    
    
      loader: "postcss-loader",
      options: {
    
    
        postcssOptions: {
    
    
          plugins: [
            "postcss-preset-env", // 能解决大多数样式兼容性问题
          ],
        },
      },
    },
    preProcessor,
  ].filter(Boolean);
};

module.exports = {
    
    
  // 入口
  // 相对路径和绝对路径都行
  entry: "./src/main.js",
  // 输出
  output: {
    
    
    // path: 文件输出目录,必须是绝对路径
    // path.resolve()方法返回一个绝对路径
    // __dirname 当前文件的文件夹绝对路径
    path: path.resolve(__dirname, "../dist"),   // 生产模式需要输出
    // filename: 输出文件名,入口文件打包输出到 `static/js/[name].js`中,其他文件仍打包到上方 path 下
    // [contenthash:8]使用contenthash,取8位长度
    filename: "static/js/[name].[contenthash:8].js", // 入口文件打包输出资源命名方式
    chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 动态导入输出资源命名方式
    assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)
    clean: true, // 自动将上次打包目录资源清空

  },
  // 加载器
  module: {
    
    
    // loader的配置
    rules: [
      {
    
    
        oneOf: [
          {
    
    
            // 用来匹配 .css 结尾的文件
            test: /\.css$/,
            // use 数组里面 Loader 执行顺序是从右到左
            use: getStyleLoaders(),
          },
          {
    
    
            test: /\.less$/,
            use:getStyleLoaders("less-loader"),
          },
          {
    
    
            test: /\.s[ac]ss$/,
            use: getStyleLoaders("sass-loader"),
          },
          {
    
    
            test: /\.styl$/,
            use:getStyleLoaders("stylus-loader"),
          },
          {
    
    
            test: /\.(png|jpe?g|gif|webp)$/,
            type: "asset",
            parser: {
    
    
              dataUrlCondition: {
    
    
                maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
              }
            },
            //generator: {
    
    
            //  // 将图片文件输出到 static/imgs 目录中
            //  // 将图片文件命名 [hash:8][ext][query]
            //  // [hash:8]: hash值取8位
            //  // [ext]: 使用之前的文件扩展名
            //  // [query]: 添加之前的query参数
            //  filename: "static/imgs/[hash:8][ext][query]",
            //},
          },
          {
    
    
            // 开发中可能还存在一些其他资源,如音视频等,我们也一起处理了
            test: /\.(ttf|woff2?|map4|map3|avi)$/,
            type: "asset/resource",
            //generator: {
    
    
            //  filename: "static/media/[hash:8][ext][query]",
            //},
          },
          {
    
    
            test: /\.js$/,
            //exclude: /node_modules/, // 排除node_modules代码不编译
            include: path.resolve(__dirname, "../src"), // 只包含 src 下的文件
            use: [
              {
    
    
                loader: "thread-loader", // 开启多进程
                options: {
    
    
                  workers: threads, // 数量
                },
              },
              {
    
    
                loader: "babel-loader",
                options: {
    
    
                  cacheDirectory: true, // 开启babel编译缓存
                  cacheCompression: false, // 缓存文件不要压缩
                  plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
                },
              },
            ],
          },
        ]
      }
    ],
  },
  // 插件
  plugins: [
    new ESLintWebpackPlugin({
    
    
      // 指定检查文件的根目录(这里检查 src 文件夹)
      context: path.resolve(__dirname, "../src"),
      exclude: "node_modules", // 默认值:排除 node_modules 文件夹
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(
          __dirname,
          "../node_modules/.cache/.eslintcache"
      ),
      // 开启多进程
      threads,
    }),
    new HtmlWebpackPlugin({
    
    
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    // 提取css成单独文件
    new MiniCssExtractPlugin({
    
    
      // 定义输出文件名和目录
      filename: "static/css/[name].[contenthash:8].css",
      chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
    }),
    // css压缩
    //new CssMinimizerPlugin(),
    new PreloadWebpackPlugin({
    
    
      rel: "preload", // preload兼容性更好
      as: "script",
      // rel: 'prefetch' // prefetch兼容性更差
    }),
  ],
  // 优化
  optimization: {
    
    
    minimize: true,
    minimizer: [
      // css压缩也可以写到optimization.minimizer里面,效果一样的(webpack5的写法)
      new CssMinimizerPlugin(),
      // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
      new TerserPlugin({
    
    
        parallel: threads // 开启多进程
      }),

    ],
    // 代码分割
    splitChunks: {
    
    
      chunks: "all", // 对所有模块都进行分割
      // 其他的都用默认配置
    },
    // 提取runtime文件
    runtimeChunk: {
    
    
      name: (entrypoint) => `runtime~${
      
      entrypoint.name}`, // runtime文件命名规则
    },
  },
  // 模式
  mode: "production", // 生产模式
  devtool: "source-map",
};

4.4、Core-js

  • In the past, we used babel to handle compatibility of js code, and used the @babel/preset-env smart preset to handle compatibility issues.
  • It can compile and convert some ES6 syntax, such as arrow functions, dot-dot operators, etc. But if it is an async function, a promise object, some methods of an array (includes), etc., it cannot handle it .
  • So at this time, our js code still has compatibility issues, and an error will be reported directly if it encounters a lower version of the browser. So we want to completely solve the js compatibility problem
  • core-jsIt is specially used to make ES6 and above APIspolyfill
  • polyfillTranslated, it's called a shim/patch. Just use a piece of code provided by the community to allow us to use the new features on browsers that are not compatible with the new features.

  1. Download package
npm i @babel/eslint-parser -D

# 下载 core-js
npm i core-js
  1. Change.eslintrc.js
    • Add toparser: "@babel/eslint-parser"
module.exports = {
    
    
  // 继承 Eslint 规则
  extends: ["eslint:recommended"],
  parser: "@babel/eslint-parser", // 支持最新的最终 ECMAScript 标准
  env: {
    
    
    node: true, // 启用node中全局变量
    browser: true, // 启用浏览器中全局变量
  },
  plugins: ["import"], // 解决动态导入import语法报错问题 --> 实际使用eslint-plugin-import的规则解决的
  parserOptions: {
    
    
    ecmaVersion: 6, // es6
    sourceType: "module", // es6 模块化
  },
  rules: {
    
    
    "no-var": 2, // 不能使用 var 定义变量
  },
};
  1. Changebabel.config.js
module.exports = {
    
    
  // 智能预设:能够编译ES6语法
  presets: [
    [
      "@babel/preset-env",
      {
    
    
        // 按需加载自动引入
        useBuiltIns: "usage",
        corejs: {
    
    
          //corejs 版本为3
          version: "3", proposals: true
        }
      },
    ],
  ],
};
  1. Revisemain.js
// 添加promise代码
const promise = Promise.resolve();
promise.then(() => {
    
    
  console.log("hello promise");
});

polyfillAt this point, the corresponding will be automatically loaded on demand based on the syntax used in our code.

4.5、PWA

When developing a Web App project, once the project is offline, it will be inaccessible. We want to provide an offline experience for the project.

  1. Download package
npm i workbox-webpack-plugin -D

npm i serve -g
  1. introduce
const WorkboxPlugin = require("workbox-webpack-plugin");
  1. Configuration
new WorkboxPlugin.GenerateSW({
    
    
    // 这些选项帮助快速启用 ServiceWorkers
    // 不允许遗留任何“旧的” ServiceWorkers
    clientsClaim: true,
    skipWaiting: true,
}),
  1. Revisemain.js
// 离线访问
if ("serviceWorker" in navigator) {
    
    
  window.addEventListener("load", () => {
    
    
    navigator.serviceWorker
        .register("/service-worker.js")
        .then((registration) => {
    
    
          console.log("SW registered: ", registration);
        })
        .catch((registrationError) => {
    
    
          console.log("SW registration failed: ", registrationError);
        });
  });
}
  1. run command
serve dist

At this time, our service-worker can successfully register the server started through serve.

Supongo que te gusta

Origin blog.csdn.net/Augenstern_QXL/article/details/133049905
Recomendado
Clasificación