HotModuleReplacement

为什么

开发时我们修改了其中一个模块代码,Webpack 默认会将所有模块全部重新打包编译,速度很慢。
所以我们需要做到修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变,这样打包速度就能很快。

是什么

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

怎么用

  1. 基本配置 ```javascript // webpack.dev.js

module.exports = { // 其他省略 devServer: { host: “localhost”, // 启动服务器域名 port: “3000”, // 启动服务器端口号 open: true, // 是否自动打开浏览器 hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了) }, };

  1. 此时 css 样式经过 style-loader 处理,已经具备 HMR 功能了。但是 js 还不行。
  2. 2. JS 配置
  3. ```javascript
  4. // main.js
  5. // 判断是否支持HMR功能
  6. // 两种写法
  7. if (module.hot) {
  8. module.hot.accept("./js/count.js");
  9. module.hot.accept("./js/sum.js", function (sum) {
  10. const result2 = sum(1, 2, 3, 4);
  11. console.log(result2);
  12. });
  13. }

上面这样写会很麻烦,所以实际开发我们会使用其他 loader 来解决。
比如:vue-loader, react-hot-loader

OneOf

为什么

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

是什么

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

怎么用

  1. // webpack.dev.js
  2. // 生产模式也是如此配置。
  3. const path = require("path");
  4. const ESLintWebpackPlugin = require("eslint-webpack-plugin");
  5. const HtmlWebpackPlugin = require("html-webpack-plugin");
  6. module.exports = {
  7. entry: "./src/main.js",
  8. output: {
  9. path: undefined, // 开发模式没有输出,不需要指定输出目录
  10. filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
  11. // clean: true, // 开发模式没有输出,不需要清空输出结果
  12. },
  13. module: {
  14. rules: [
  15. {
  16. + oneOf: [
  17. {
  18. // 用来匹配 .css 结尾的文件
  19. test: /\.css$/,
  20. // use 数组里面 Loader 执行顺序是从右到左
  21. use: ["style-loader", "css-loader"],
  22. },
  23. {
  24. test: /\.less$/,
  25. use: ["style-loader", "css-loader", "less-loader"],
  26. },
  27. {
  28. test: /\.s[ac]ss$/,
  29. use: ["style-loader", "css-loader", "sass-loader"],
  30. },
  31. {
  32. test: /\.styl$/,
  33. use: ["style-loader", "css-loader", "stylus-loader"],
  34. },
  35. {
  36. test: /\.(png|jpe?g|gif|webp)$/,
  37. type: "asset",
  38. parser: {
  39. dataUrlCondition: {
  40. maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
  41. },
  42. },
  43. generator: {
  44. // 将图片文件输出到 static/imgs 目录中
  45. // 将图片文件命名 [hash:8][ext][query]
  46. // [hash:8]: hash值取8位
  47. // [ext]: 使用之前的文件扩展名
  48. // [query]: 添加之前的query参数
  49. filename: "static/imgs/[hash:8][ext][query]",
  50. },
  51. },
  52. {
  53. test: /\.(ttf|woff2?)$/,
  54. type: "asset/resource",
  55. generator: {
  56. filename: "static/media/[hash:8][ext][query]",
  57. },
  58. },
  59. {
  60. test: /\.js$/,
  61. exclude: /node_modules/, // 排除node_modules代码不编译
  62. loader: "babel-loader",
  63. },
  64. ],
  65. },
  66. ],
  67. },
  68. plugins: [
  69. new ESLintWebpackPlugin({
  70. // 指定检查文件的根目录
  71. context: path.resolve(__dirname, "../src"),
  72. }),
  73. new HtmlWebpackPlugin({
  74. // 以 public/index.html 为模板创建文件
  75. // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
  76. template: path.resolve(__dirname, "../public/index.html"),
  77. }),
  78. ],
  79. // 开发服务器
  80. devServer: {
  81. host: "localhost", // 启动服务器域名
  82. port: "3000", // 启动服务器端口号
  83. open: true, // 是否自动打开浏览器
  84. hot: true, // 开启HMR功能
  85. },
  86. mode: "development",
  87. devtool: "cheap-module-source-map",
  88. };

Include/Exclude

为什么

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

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

是什么

  • include

包含,只处理 xxx 文件

  • exclude

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

怎么用

  1. // webpack.dev.js
  2. // 生产模式也是如此配置。
  3. module.exports = {
  4. module: {
  5. rules: [
  6. {
  7. oneOf: [
  8. {
  9. test: /\.js$/,
  10. + // exclude: /node_modules/, // 排除node_modules代码不编译
  11. + include: path.resolve(__dirname, "../src"), // 也可以用包含
  12. loader: "babel-loader",
  13. },
  14. ],
  15. },
  16. ],
  17. },
  18. plugins: [
  19. new ESLintWebpackPlugin({
  20. // 指定检查文件的根目录
  21. context: path.resolve(__dirname, "../src"),
  22. + exclude: "node_modules", // 默认值
  23. }),
  24. ],

Cache

为什么

每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。
我们可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了。

是什么

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

怎么用

  1. module.exports = {
  2. module: {
  3. rules: [
  4. {
  5. oneOf: [
  6. {
  7. test: /\.js$/,
  8. // exclude: /node_modules/, // 排除node_modules代码不编译
  9. include: path.resolve(__dirname, "../src"), // 也可以用包含
  10. loader: "babel-loader",
  11. + options: {
  12. + cacheDirectory: true, // 开启babel编译缓存
  13. + cacheCompression: false, // 缓存文件不要压缩
  14. + },
  15. },
  16. ],
  17. },
  18. ],
  19. },
  20. plugins: [
  21. new ESLintWebpackPlugin({
  22. // 指定检查文件的根目录
  23. context: path.resolve(__dirname, "../src"),
  24. exclude: "node_modules", // 默认值
  25. + cache: true, // 开启缓存
  26. + // 缓存目录
  27. + cacheLocation: path.resolve(
  28. + __dirname,
  29. + "../node_modules/.cache/.eslintcache"
  30. + ),
  31. }),
  32. ],
  33. };

Thead

为什么

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

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

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

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

是什么

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

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

怎么用

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

  1. 如何获取 CPU 的核数,因为每个电脑都不一样。
  1. // nodejs核心模块,直接使用
  2. const os = require("os");
  3. // cpu核数
  4. const threads = os.cpus().length;
  1. 下载包
  1. npm i thread-loader -D
  1. 使用
  1. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  2. const os = require("os");
  3. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  4. const path = require("path");
  5. const ESLintWebpackPlugin = require("eslint-webpack-plugin");
  6. const HtmlWebpackPlugin = require("html-webpack-plugin");
  7. const MiniCssExtractPlugin = require("mini-css-extract-plugin");
  8. const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
  9. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  10. const TerserPlugin = require("terser-webpack-plugin");
  11. // cpu核数
  12. const threads = os.cpus().length;
  13. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  14. // 获取处理样式的Loaders
  15. const getStyleLoaders = (preProcessor) => {
  16. return [
  17. MiniCssExtractPlugin.loader,
  18. "css-loader",
  19. {
  20. loader: "postcss-loader",
  21. options: {
  22. postcssOptions: {
  23. plugins: [
  24. "postcss-preset-env", // 能解决大多数样式兼容性问题
  25. ],
  26. },
  27. },
  28. },
  29. preProcessor,
  30. ].filter(Boolean);
  31. };
  32. module.exports = {
  33. entry: "./src/main.js",
  34. output: {
  35. path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
  36. filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
  37. clean: true,
  38. },
  39. module: {
  40. rules: [
  41. {
  42. oneOf: [
  43. {
  44. // 用来匹配 .css 结尾的文件
  45. test: /\.css$/,
  46. // use 数组里面 Loader 执行顺序是从右到左
  47. use: getStyleLoaders(),
  48. },
  49. {
  50. test: /\.less$/,
  51. use: getStyleLoaders("less-loader"),
  52. },
  53. {
  54. test: /\.s[ac]ss$/,
  55. use: getStyleLoaders("sass-loader"),
  56. },
  57. {
  58. test: /\.styl$/,
  59. use: getStyleLoaders("stylus-loader"),
  60. },
  61. {
  62. test: /\.(png|jpe?g|gif|webp)$/,
  63. type: "asset",
  64. parser: {
  65. dataUrlCondition: {
  66. maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
  67. },
  68. },
  69. generator: {
  70. // 将图片文件输出到 static/imgs 目录中
  71. // 将图片文件命名 [hash:8][ext][query]
  72. // [hash:8]: hash值取8位
  73. // [ext]: 使用之前的文件扩展名
  74. // [query]: 添加之前的query参数
  75. filename: "static/imgs/[hash:8][ext][query]",
  76. },
  77. },
  78. {
  79. test: /\.(ttf|woff2?)$/,
  80. type: "asset/resource",
  81. generator: {
  82. filename: "static/media/[hash:8][ext][query]",
  83. },
  84. },
  85. {
  86. test: /\.js$/,
  87. // exclude: /node_modules/, // 排除node_modules代码不编译
  88. include: path.resolve(__dirname, "../src"), // 也可以用包含
  89. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  90. use: [
  91. {
  92. loader: "thread-loader", // 开启多进程
  93. options: {
  94. workers: threads, // 数量
  95. },
  96. },
  97. {
  98. loader: "babel-loader",
  99. options: {
  100. cacheDirectory: true, // 开启babel编译缓存
  101. },
  102. },
  103. ],
  104. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  105. },
  106. ],
  107. },
  108. ],
  109. },
  110. plugins: [
  111. new ESLintWebpackPlugin({
  112. // 指定检查文件的根目录
  113. context: path.resolve(__dirname, "../src"),
  114. exclude: "node_modules", // 默认值
  115. cache: true, // 开启缓存
  116. // 缓存目录
  117. cacheLocation: path.resolve(
  118. __dirname,
  119. "../node_modules/.cache/.eslintcache"
  120. ),
  121. threads, // 开启多进程
  122. }),
  123. new HtmlWebpackPlugin({
  124. // 以 public/index.html 为模板创建文件
  125. // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
  126. template: path.resolve(__dirname, "../public/index.html"),
  127. }),
  128. // 提取css成单独文件
  129. new MiniCssExtractPlugin({
  130. // 定义输出文件名和目录
  131. filename: "static/css/main.css",
  132. }),
  133. // css压缩
  134. // new CssMinimizerPlugin(),
  135. ],
  136. optimization: {
  137. minimize: true,
  138. minimizer: [
  139. // css压缩也可以写到optimization.minimizer里面,效果一样的
  140. new CssMinimizerPlugin(),
  141. // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
  142. new TerserPlugin({
  143. parallel: threads // 开启多进程
  144. })
  145. ],
  146. },
  147. // devServer: {
  148. // host: "localhost", // 启动服务器域名
  149. // port: "3000", // 启动服务器端口号
  150. // open: true, // 是否自动打开浏览器
  151. // },
  152. mode: "production",
  153. devtool: "source-map",
  154. };

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