• 尚硅谷webpack5笔记2


    senior部分

    介绍

    本章节主要介绍 Webpack 高级配置。

    所谓高级配置其实就是进行 Webpack 优化,让我们代码在编译/运行时性能更好~

    我们会从以下角度来进行优化:

    1. 提升开发体验
    2. 提升打包构建速度
    3. 减少代码体积
    4. 优化代码运行性能

    提升开发体验

    SourceMap

    为什么

    开发时我们运行的代码是经过 webpack 编译后的,例如下面这个样子:

    /*
     * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
     * This devtool is neither made for production nor for readable output files.
     * It uses "eval()" calls to create a separate source file in the browser devtools.
     * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
     * or disable the default devtool with "devtool: false".
     * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
     */
    /******/ (() => {
        // webpackBootstrap
    /******/ 	"use strict";
    /******/ 	var __webpack_modules__ = ({
       
    
    /***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/less-loader/dist/cjs.js!./src/less/index.less":
    /*!**********************************************************************************************************!*\
      !*** ./node_modules/css-loader/dist/cjs.js!./node_modules/less-loader/dist/cjs.js!./src/less/index.less ***!
      \**********************************************************************************************************/
    /***/ ((module, __webpack_exports__, __webpack_require__) => {
       
    
    eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n// Imports\n\n\nvar ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \".box2 {\\n  width: 100px;\\n  height: 100px;\\n  background-color: deeppink;\\n}\\n\", \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://webpack5/./src/less/index.less?./node_modules/css-loader/dist/cjs.js!./node_modules/less-loader/dist/cjs.js");
    
    /***/ }),
    // 其他省略
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    所有 css 和 js 合并成了一个文件,并且多了其他代码。此时如果代码运行出错那么提示代码错误位置我们是看不懂的。一旦将来开发代码文件很多,那么很难去发现错误出现在哪里。

    所以我们需要更加准确的错误提示,来帮助我们更好的开发代码。

    是什么

    SourceMap(源代码映射)是一个用来生成源代码与构建后代码一一映射的文件的方案。

    它会生成一个 xxx.map 文件,里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后代码出错了,会通过 xxx.map 文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。

    怎么用

    通过查看Webpack DevTool 文档可知,SourceMap 的值有很多种情况.

    但实际开发时我们只需要关注两种情况即可:

    • 开发模式:cheap-module-source-map

      • 优点:打包编译速度快,只包含行映射
      • 缺点:没有列映射
    module.exports = {
       
      // 其他省略
      mode: "development",
      devtool: "cheap-module-source-map",
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 生产模式:source-map
      • 优点:包含行/列映射
      • 缺点:打包编译速度更慢
    module.exports = {
       
      // 其他省略
      mode: "production",
      devtool: "source-map",
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    提升打包构建速度

    HotModuleReplacement

    为什么

    开发时我们修改了其中一个模块代码,Webpack 默认会将所有模块全部重新打包编译,速度很慢。

    所以我们需要做到修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变,这样打包速度就能很快。

    是什么

    HotModuleReplacement(HMR/热模块替换):在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。

    怎么用

    1. 基本配置
    module.exports = {
       
      // 其他省略
      devServer: {
       
        host: "localhost", // 启动服务器域名
        port: "3000", // 启动服务器端口号
        open: true, // 是否自动打开浏览器
        hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    此时 css 样式经过 style-loader 处理,已经具备 HMR 功能了。
    但是 js 还不行。

    1. JS 配置
    // main.js
    import count from "./js/count";
    import sum from "./js/sum";
    // 引入资源,Webpack才会对其打包
    import "./css/iconfont.css";
    import "./css/index.css";
    import "./less/index.less";
    import "./sass/index.sass";
    import "./sass/index.scss";
    import "./styl/index.styl";
    
    const result1 = count(2, 1);
    console.log(result1);
    const result2 = sum(1, 2, 3, 4);
    console.log(result2);
    
    // 判断是否支持HMR功能
    if (module.hot) {
      module.hot.accept("./js/count.js", function (count) {
        const result1 = count(2, 1);
        console.log(result1);
      });
    
      module.hot.accept("./js/sum.js", function (sum) {
        const result2 = sum(1, 2, 3, 4);
        console.log(result2);
      });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    上面这样写会很麻烦,所以实际开发我们会使用其他 loader 来解决。

    比如:vue-loader, react-hot-loader

    OneOf

    为什么

    打包时每个文件都会经过所有 loader 处理,虽然因为 test 正则原因实际没有处理上,但是都要过一遍。比较慢。

    是什么

    顾名思义就是只能匹配上一个 loader, 剩下的就不匹配了。

    怎么用

    const path = require("path");
    const ESLintWebpackPlugin = require("eslint-webpack-plugin");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    
    module.exports = {
       
      entry: "./src/main.js",
      output: {
       
        path: undefined, // 开发模式没有输出,不需要指定输出目录
        filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
        // clean: true, // 开发模式没有输出,不需要清空输出结果
      },
      module: {
       
        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?)$/,
                type: "asset/resource",
                generator: {
       
                  filename: "static/media/[hash:8][ext][query]",
                },
              },
              {
       
                test: /\.js$/,
                exclude: /node_modules/, // 排除node_modules代码不编译
                loader: "babel-loader",
              },
            ],
          },
        ],
      },
      plugins: [
        new ESLintWebpackPlugin({
       
          // 指定检查文件的根目录
          context: path.resolve(__dirname, "../src"),
        }),
        new HtmlWebpackPlugin({
       
          // 以 public/index.html 为模板创建文件
          // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
          template: path.resolve(__dirname, "../public/index.html"),
        }),
      ],
      // 开发服务器
      devServer: {
       
        host: "localhost", // 启动服务器域名
        port: "3000", // 启动服务器端口号
        open: true, // 是否自动打开浏览器
        hot: true, // 开启HMR功能
      },
      mode: "development",
      devtool: "cheap-module-source-map",
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105

    生产模式也是如此配置。

    Include/Exclude

    为什么

    开发时我们需要使用第三方的库或插件,所有文件都下载到 node_modules 中了。而这些文件是不需要编译可以直接使用的。

    所以我们在对 js 文件处理时,要排除 node_modules 下面的文件。

    是什么

    • include

    包含,只处理 xxx 文件

    • exclude

    排除,除了 xxx 文件以外其他文件都处理

    怎么用

    const path = require("path");
    const ESLintWebpackPlugin = require("eslint-webpack-plugin");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    
    module.exports = {
      entry: "./src/main.js",
      output: {
        path: undefined, // 开发模式没有输出,不需要指定输出目录
        filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
        // clean: true, // 开发模式没有输出,不需要清空输出结果
      },
      module: {
        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?)$/,
                type: "asset/resource",
                generator: {
                  filename: "static/media/[hash:8][ext][query]",
                },
              },
              {
                test: /\.js$/,
                // exclude: /node_modules/, // 排除node_modules代码不编译
                include: path.resolve(__dirname, "../src"), // 也可以用包含
                loader: "babel-loader",
              },
            ],
          },
        ],
      },
      plugins: [
        new ESLintWebpackPlugin({
          // 指定检查文件的根目录
          context: path.resolve(__dirname, "../src"),
          exclude: "node_modules", // 默认值
        }),
        new HtmlWebpackPlugin({
          // 以 public/index.html 为模板创建文件
          // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
          template: path.resolve(__dirname, "../public/index.html"),
        }),
      ],
      // 开发服务器
      devServer: {
        host: "localhost", // 启动服务器域名
        port: "3000", // 启动服务器端口号
        open: true, // 是否自动打开浏览器
        hot: true, // 开启HMR功能
      },
      mode: "development",
      devtool: "cheap-module-source-map",
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    生产模式也是如此配置。

    Cache

    为什么

    每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。

    我们可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了。

    是什么

    对 Eslint 检查 和 Babel 编译结果进行缓存。

    怎么用

    const path = require("path");
    const ESLintWebpackPlugin = require("eslint-webpack-plugin");
    const HtmlWebpackPlugin = require("html-webpack-plugin");
    
    module.exports = {
      entry: "./src/main.js",
      output: {
        path: undefined, // 开发模式没有输出,不需要指定输出目录
        filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
        // clean: true, // 开发模式没有输出,不需要清空输出结果
      },
      module: {
        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?)$/,
                type: "asset/resource",
                generator: {
                  filename: "static/media/[hash:8][ext][query]",
                },
              },
              {
                test: /\.js$/,
                // exclude: /node_modules/, // 排除node_modules代码不编译
                include: path.resolve(__dirname, "../src"), // 也可以用包含
                loader: "babel-loader",
                options: {
                  cacheDirectory: true, // 开启babel编译缓存
                  cacheCompression: false, // 缓存文件不要压缩
                },
              },
            ],
          },
        ],
      },
      plugins: [
        new ESLintWebpackPlugin({
          // 指定检查文件的根目录
          context: path.resolve(__dirname, "../src"),
          exclude: "node_modules", // 默认值
          cache: true, // 开启缓存
          // 缓存目录
          cacheLocation: path.resolve(
            __dirname,
            "../node_modules/.cache/.eslintcache"
          ),
        }),
        new HtmlWebpackPlugin({
          // 以 public/index.html 为模板创建文件
          // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
          template: path.resolve(__dirname, "../public/index.html"),
        }),
      ],
      // 开发服务器
      devServer: {
        host: "localhost", // 启动服务器域名
        port: "3000", // 启动服务器端口号
        open: true, // 是否自动打开浏览器
        hot: true, // 开启HMR功能
      },
      mode: "development",
      devtool: "cheap-module-source-map",
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    Thead

    为什么

    当项目越来越庞大时,打包速度越来越慢,甚至于需要一个下午才能打包出来代码。这个速度是比较慢的。

    我们想要继续提升打包速度,其实就是要提升 js 的打包速度,因为其他文件都比较少。

    而对 js 文件处理主要就是 eslint 、babel、Terser 三个工具,所以我们要提升它们的运行速度。

    我们可以开启多进程同时处理 js 文件,这样速度就比之前的单进程打包更快了。

    是什么

    多进程打包:开启电脑的多个进程同时干一件事,速度更快。

    需要注意:请仅在特别耗时的操作中使用,因为每个进程启动就有大约为 600ms 左右开销。

    怎么用

    我们启动进程的数量就是我们 CPU 的核数。

    1. 如何获取 CPU 的核数,因为每个电脑都不一样。
    // nodejs核心模块,直接使用
    const os = require("os");
    // cpu核数
    const threads = os.cpus().length;
    
    • 1
    • 2
    • 3
    • 4
    1. 下载包
    npm i thread-loader -D
    
    • 1
    1. 使用
    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;
    
    // 获取处理样式的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, "../dist"), // 生产模式需要输出
        filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
        clean: true,
      },
      module: {
        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?)$/,
                type: "asset/resource",
                generator: {
                  filename: "static/media/[hash:8][ext][query]",
                },
              },
              {
                test: /\.js$/,
                // exclude: /node_modules/, // 排除node_modules代码不编译
                include: path.resolve(__dirname, "../src"), // 也可以用包含
                use: [
                  {
                    loader: "thread-loader", // 开启多进程
                    options: {
                      workers: threads, // 数量
                    },
                  },
                  {
                    loader: "babel-loader",
                    options: {
                      cacheDirectory: true, // 开启babel编译缓存
                    },
                  },
                ],
              },
            ],
          },
        ],
      },
      plugins: [
        new ESLintWebpackPlugin({
          // 指定检查文件的根目录
          context: path.resolve(__dirname, "../src"),
          exclude: "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里面,效果一样的
          new CssMinimizerPlugin(),
          // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
          new TerserPlugin({
            parallel: threads // 开启多进程
          })
        ],
      },
      // devServer: {
      //   host: "localhost", // 启动服务器域名
      //   port: "3000", // 启动服务器端口号
      //   open: true, // 是否自动打开浏览器
      // },
      mode: "production",
      devtool: "source-map",
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151

    我们目前打包的内容都很少,所以因为启动进程开销原因,使用多进程打包实际上会显著的让我们打包时间变得很长。

    优化代码运行性能

    Code Split

    为什么

    打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。

    所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。

    是什么

    代码分割(Code Split)主要做了两件事:

    1. 分割文件:将打包生成的文件进行分割,生成多个 js 文件。
    2. 按需加载:需要哪个文件就加载哪个文件。

    怎么用

    代码分割实现方式有不同的方式,为了更加方便体现它们之间的差异,我们会分别创建新的文件来演示

    1. 多入口
    1. 文件目录
    ├── public
    ├── src
    |   ├── app.js
    |   └── main.js
    ├── package.json
    └── webpack.config.js
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 下载包
    npm i webpack webpack-cli html-webpack-plugin -D
    
    • 1
    1. 新建文件

    内容无关紧要,主要观察打包输出的结果

    • app.js
    console.log("hello app");
    
    • 1
    • main.js
    console.log("hello main");
    
    • 1
    1. 配置
    // 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: "js/[name].js",
        clear: true,
      },
      plugins: [
        new HtmlWebpackPlugin({
       
          template: "./public/index.html",
        }),
      ],
      mode: "production",
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    1. 运行指令
    npx webpack
    
    • 1

    此时在 dist 目录我们能看到输出了两个 js 文件。

    总结:配置了几个入口,至少输出几个 js 文件。

    2. 提取重复代码

    如果多入口文件中都引用了同一份代码,我们不希望这份代码被打包到两个文件中,导致代码重复,体积更大。

    我们需要提取多入口的重复代码,只打包生成一个 js 文件,其他文件引用它就好。

    1. 修改文件
    • app.js
    import {
        sum } from "./math";
    
    console.log("hello app");
    console.log(sum(1, 2, 3, 4));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • main.js
    import {
        sum } from "./math";
    
    console.log("hello main");
    console.log(sum(1, 2, 3, 4, 5));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • math.js
    export const sum = (...args) => {
       
      return args.reduce((p, c) => p + c, 0);
    };
    
    • 1
    • 2
    • 3
    • 4
    1. 修改配置文件
    // 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: "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,
            },
          },
        },
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    1. 运行指令
    npx webpack
    
    • 1

    此时我们会发现生成 3 个 js 文件,其中有一个就是提取的公共模块。

    3. 按需加载,动态导入

    想要实现按需加载,动态导入模块。还需要额外配置:

    1. 修改文件
    • main.js
    console.log("hello main");
    
    document.getElementById("btn").onclick = function () {
       
      // 动态导入 --> 实现按需加载
      // 即使只被引用了一次,也会代码分割
      import("./math.js").then(({
         sum }) => {
       
        alert(sum(1, 2, 3, 4, 5));
      });
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • app.js
    console.log("hello app");
    
    • 1
    • public/index.html
    DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Code Splittitle>
      head>
      <body>
        <h1>hello webpackh1>
        <button id="btn">计算button>
      body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 运行指令
    npx webpack
    
    • 1

    我们可以发现,一旦通过 import 动态导入语法导入模块,模块就被代码分割,同时也能按需加载了。

    4. 单入口

    开发时我们可能是单页面应用(SPA),只有一个入口(单入口)。那么我们需要这样配置:

    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: "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,
          //   },
          // },
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    5. 更新配置

    最终我们会使用单入口+代码分割+动态导入方式来进行配置。更新之前的配置文件。

    // webpack.prod.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 ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
    
    // cpu核数
    const threads = os.cpus().length;
    
    // 获取处理样式的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, "../dist"), // 生产模式需要输出
        filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
        clean: true,
      },
      module: {
        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|svg)$/,
                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?)$/,
                type: "asset/resource",
                generator: {
                  filename: "static/media/[hash:8][ext][query]",
                },
              },
              {
                test: /\.js$/,
                // exclude: /node_modules/, // 排除node_modules代码不编译
                include: path.resolve(__dirname, "../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({
          // 指定检查文件的根目录
          context: path.resolve(__dirname, "../src"),
          exclude: "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: {
        minimizer: [
          // css压缩也可以写到optimization.minimizer里面,效果一样的
          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",
                          },
                        },
                      ],
                    },
                  ],
                ],
              },
            },
          }),
        ],
        // 代码分割配置
        splitChunks: {
          chunks: "all", // 对所有模块都进行分割
          // 其他内容用默认配置即可
        },
      },
      // devServer: {
      //   host: "localhost", // 启动服务器域名
      //   port: "3000", // 启动服务器端口号
      //   open: true, // 是否自动打开浏览器
      // },
      mode: "production",
      devtool: "source-map",
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    6. 给动态导入文件取名称
    1. 修改文件
    • main.js
    import sum from "./js/sum";
    // 引入资源,Webpack才会对其打包
    import "./css/iconfont.css";
    import "./css/index.css";
    import "./less/index.less";
    import "./sass/index.sass";
    import "./sass/index.scss";
    import "./styl/index.styl";
    
    const result2 = sum(1, 2, 3, 4);
    console.log(result2);
    
    // 以下代码生产模式下会删除
    if (module.hot) {
       
      module.hot.accept("./js/sum.js", function (sum) {
       
        const result2 = sum(1, 2, 3, 4);
        console.log(result2);
      });
    }
    
    document.getElementById("btn").onClick = function () {
       
      // eslint会对动态导入语法报错,需要修改eslint配置文件
      // webpackChunkName: "math":这是webpack动态导入模块命名的方式
      // "math"将来就会作为[name]的值显示。
      import(/* webpackChunkName: "math" */ "./js/math.js").then(({
         count }) => {
       
        console.log(count(2, 1));
      });
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    1. eslint 配置
    • 下载包
    npm i eslint-plugin-import -D
    
    • 1
    • 配置
    // .eslintrc.js
    module.exports = {
      // 继承 Eslint 规则
      extends: ["eslint:recommended"],
      env: {
        node: true, // 启用node中全局变量
        browser: true, // 启用浏览器中全局变量
      },
      plugins: ["import"], // 解决动态导入import语法报错问题 --> 实际使用eslint-plugin-import的规则解决的
      parserOptions: {
        ecmaVersion: 6,
        sourceType: "module",
      },
      rules: {
        "no-var": 2, // 不能使用 var 定义变量
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. 统一命名配置
    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 ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
    
    // cpu核数
    const threads = os.cpus().length;
    
    // 获取处理样式的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, "../dist"), // 生产模式需要输出
        filename: "static/js/[name].js", // 入口文件打包输出资源命名方式
        chunkFilename: "static/js/[name].chunk.js", // 动态导入输出资源命名方式
        assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)
        clean: true,
      },
      module: {
        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|svg)$/,
                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?)$/,
                type: "asset/resource",
                // generator: {
                //   filename: "static/media/[hash:8][ext][query]",
                // },
              },
              {
                test: /\.js$/,
                // exclude: /node_modules/, // 排除node_modules代码不编译
                include: path.resolve(__dirname, "../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({
          // 指定检查文件的根目录
          context: path.resolve(__dirname, "../src"),
          exclude: "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: {
        minimizer: [
          // css压缩也可以写到optimization.minimizer里面,效果一样的
          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",
                          },
                        },
                      ],
                    },
                  ],
                ],
              },
            },
          }),
        ],
        // 代码分割配置
        splitChunks: {
          chunks: "all", // 对所有模块都进行分割
          // 其他内容用默认配置即可
        },
      },
      // devServer: {
      //   host: "localhost", // 启动服务器域名
      //   port: "3000", // 启动服务器端口号
      //   open: true, // 是否自动打开浏览器
      // },
      mode: "production",
      devtool: "source-map",
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    1. 运行指令
    npx webpack
    
    • 1

    观察打包输出 js 文件名称。

    Preload / Prefetch

    为什么

    我们前面已经做了代码分割,同时会使用 import 动态导入语法来进行代码按需加载(我们也叫懒加载,比如路由懒加载就是这样实现的)。

    但是加载速度还不够好,比如:是用户点击按钮时才加载这个资源的,如果资源体积很大,那么用户会感觉到明显卡顿效果。

    我们想在浏览器空闲时间,加载后续需要使用的资源。我们就需要用上 PreloadPrefetch 技术。

    是什么

    • Preload:告诉浏览器立即加载资源。

    • Prefetch:告诉浏览器在空闲时才开始加载资源。

    它们共同点:

    • 都只会加载资源,并不执行。
    • 都有缓存。

    它们区别:

    • Preload加载优先级高,Prefetch加载优先级低。
    • Preload只能加载当前页面需要使用的资源,Prefetch可以加载当前页面资源,也可以加载下一个页面需要使用的资源。

    总结:

    • 当前页面优先级高的资源用 Preload 加载。
    • 下一个页面需要使用的资源用 Prefetch 加载。

    它们的问题:兼容性较差。

    • 我们可以去 Can I Use 网站查询 API 的兼容性问题。
    • Preload 相对于 Prefetch 兼容性好一点。

    怎么用

    1. 下载包
    npm i @vue/preload-webpack-plugin -D
    
    • 1
    1. 配置 webpack.prod.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 ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
    const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
    
    // cpu核数
    const threads = os.cpus().length;
    
    // 获取处理样式的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, "../dist"), // 生产模式需要输出
        filename: "static/js/[name].js", // 入口文件打包输出资源命名方式
        chunkFilename: "static/js/[name].chunk.js", // 动态导入输出资源命名方式
        assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)
        clean: true,
      },
      module: {
        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|svg)$/,
                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?)$/,
                type: "asset/resource",
                // generator: {
                //   filename: "static/media/[hash:8][ext][query]",
                // },
              },
              {
                test: /\.js$/,
                // exclude: /node_modules/, // 排除node_modules代码不编译
                include: path.resolve(__dirname, "../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({
          // 指定检查文件的根目录
          context: path.resolve(__dirname, "../src"),
          exclude: "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(),
        new PreloadWebpackPlugin({
          rel: "preload", // preload兼容性更好
          as: "script",
          // rel: 'prefetch' // prefetch兼容性更差
        }),
      ],
      optimization: {
        minimizer: [
          // css压缩也可以写到optimization.minimizer里面,效果一样的
          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",
                          },
                        },
                      ],
                    },
                  ],
                ],
              },
            },
          }),
        ],
        // 代码分割配置
        splitChunks: {
          chunks: "all", // 对所有模块都进行分割
          // 其他内容用默认配置即可
        },
      },
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
  • 相关阅读:
    docker安装php7.4安装(swoole)
    java main方法控制日志级别
    k8s-服务网格实战-配置 Mesh(灰度发布)
    【pytorch10】统计属性
    Windows服务器TLS协议
    操作系统02_进程管理_同步互斥信号量_PV操作_死锁---软考高级系统架构师007
    AttributeError: ‘NoneType‘ object has no attribute ‘get_fetch_list‘
    A-Level商务模型介绍:波士顿矩阵
    python第五次作业
    快手回应董事长宿华“被带走配合调查”:恶意谣言,已报案;苹果关闭iOS 15.4.1验证通道;Linux 5.18发布|极客头条
  • 原文地址:https://blog.csdn.net/qq_39933787/article/details/136314387