小言_互联网的博客

Webpack5 教程(8)----- 优化代码运行性能

466人阅读  评论(0)

目录

Preload / Prefetch

为什么

是什么

怎么用

Network Cache

为什么

#是什么

怎么用

Core-js

为什么

是什么

怎么用

PWA

为什么

是什么

怎么用


Preload / Prefetch

为什么

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

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

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

是什么

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

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

它们共同点:

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

它们区别:

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

总结:

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

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

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

怎么用

  1. 下载包
npm i @vue/preload-webpack-plugin -D
  1. 配置 webpack.prod.js

  
  1. const os = require( "os");
  2. const path = require( "path");
  3. const ESLintWebpackPlugin = require( "eslint-webpack-plugin");
  4. const HtmlWebpackPlugin = require( "html-webpack-plugin");
  5. const MiniCssExtractPlugin = require( "mini-css-extract-plugin");
  6. const CssMinimizerPlugin = require( "css-minimizer-webpack-plugin");
  7. const TerserPlugin = require( "terser-webpack-plugin");
  8. const ImageMinimizerPlugin = require( "image-minimizer-webpack-plugin");
  9. const PreloadWebpackPlugin = require( "@vue/preload-webpack-plugin");
  10. // cpu核数
  11. const threads = os. cpus(). length;
  12. // 获取处理样式的Loaders
  13. const getStyleLoaders = ( preProcessor) => {
  14. return [
  15. MiniCssExtractPlugin. loader,
  16. "css-loader",
  17. {
  18. loader: "postcss-loader",
  19. options: {
  20. postcssOptions: {
  21. plugins: [
  22. "postcss-preset-env", // 能解决大多数样式兼容性问题
  23. ],
  24. },
  25. },
  26. },
  27. preProcessor,
  28. ]. filter( Boolean);
  29. };
  30. module. exports = {
  31. entry: "./src/main.js",
  32. output: {
  33. path: path. resolve(__dirname, "../dist"), // 生产模式需要输出
  34. filename: "static/js/[name].js", // 入口文件打包输出资源命名方式
  35. chunkFilename: "static/js/[name].chunk.js", // 动态导入输出资源命名方式
  36. assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)
  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|svg)$/,
  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. use: [
  90. {
  91. loader: "thread-loader", // 开启多进程
  92. options: {
  93. workers: threads, // 数量
  94. },
  95. },
  96. {
  97. loader: "babel-loader",
  98. options: {
  99. cacheDirectory: true, // 开启babel编译缓存
  100. cacheCompression: false, // 缓存文件不要压缩
  101. plugins: [ "@babel/plugin-transform-runtime"], // 减少代码体积
  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/[name].css",
  132. chunkFilename: "static/css/[name].chunk.css",
  133. }),
  134. // css压缩
  135. // new CssMinimizerPlugin(),
  136. new PreloadWebpackPlugin({
  137. rel: "preload", // preload兼容性更好
  138. as: "script",
  139. // rel: 'prefetch' // prefetch兼容性更差
  140. }),
  141. ],
  142. optimization: {
  143. minimizer: [
  144. // css压缩也可以写到optimization.minimizer里面,效果一样的
  145. new CssMinimizerPlugin(),
  146. // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
  147. new TerserPlugin({
  148. parallel: threads, // 开启多进程
  149. }),
  150. // 压缩图片
  151. new ImageMinimizerPlugin({
  152. minimizer: {
  153. implementation: ImageMinimizerPlugin. imageminGenerate,
  154. options: {
  155. plugins: [
  156. [ "gifsicle", { interlaced: true }],
  157. [ "jpegtran", { progressive: true }],
  158. [ "optipng", { optimizationLevel: 5 }],
  159. [
  160. "svgo",
  161. {
  162. plugins: [
  163. "preset-default",
  164. "prefixIds",
  165. {
  166. name: "sortAttrs",
  167. params: {
  168. xmlnsOrder: "alphabetical",
  169. },
  170. },
  171. ],
  172. },
  173. ],
  174. ],
  175. },
  176. },
  177. }),
  178. ],
  179. // 代码分割配置
  180. splitChunks: {
  181. chunks: "all", // 对所有模块都进行分割
  182. // 其他内容用默认配置即可
  183. },
  184. },
  185. // devServer: {
  186. // host: "localhost", // 启动服务器域名
  187. // port: "3000", // 启动服务器端口号
  188. // open: true, // 是否自动打开浏览器
  189. // },
  190. mode: "production",
  191. devtool: "source-map",
  192. };

Network Cache

为什么

将来开发时我们对静态资源会使用缓存来优化,这样浏览器第二次请求资源就能读取缓存了,速度很快。

但是这样的话就会有一个问题, 因为前后输出的文件名是一样的,都叫 main.js,一旦将来发布新版本,因为文件名没有变化导致浏览器会直接读取缓存,不会加载新资源,项目也就没法更新了。

所以我们从文件名入手,确保更新前后文件名不一样,这样就可以做缓存了。

#是什么

它们都会生成一个唯一的 hash 值。

  • fullhash(webpack4 是 hash)

每次修改任何一个文件,所有文件名的 hash 至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。

  • chunkhash

根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。我们 js 和 css 是同一个引入,会共享一个 hash 值。

  • contenthash

根据文件内容生成 hash 值,只有文件内容变化了,hash 值才会变化。所有文件 hash 值是独享且不同的。

怎么用


  
  1. const os = require( "os");
  2. const path = require( "path");
  3. const ESLintWebpackPlugin = require( "eslint-webpack-plugin");
  4. const HtmlWebpackPlugin = require( "html-webpack-plugin");
  5. const MiniCssExtractPlugin = require( "mini-css-extract-plugin");
  6. const CssMinimizerPlugin = require( "css-minimizer-webpack-plugin");
  7. const TerserPlugin = require( "terser-webpack-plugin");
  8. const ImageMinimizerPlugin = require( "image-minimizer-webpack-plugin");
  9. const PreloadWebpackPlugin = require( "@vue/preload-webpack-plugin");
  10. // cpu核数
  11. const threads = os. cpus(). length;
  12. // 获取处理样式的Loaders
  13. const getStyleLoaders = ( preProcessor) => {
  14. return [
  15. MiniCssExtractPlugin. loader,
  16. "css-loader",
  17. {
  18. loader: "postcss-loader",
  19. options: {
  20. postcssOptions: {
  21. plugins: [
  22. "postcss-preset-env", // 能解决大多数样式兼容性问题
  23. ],
  24. },
  25. },
  26. },
  27. preProcessor,
  28. ]. filter( Boolean);
  29. };
  30. module. exports = {
  31. entry: "./src/main.js",
  32. output: {
  33. path: path. resolve(__dirname, "../dist"), // 生产模式需要输出
  34. // [contenthash:8]使用contenthash,取8位长度
  35. filename: "static/js/[name].[contenthash:8].js", // 入口文件打包输出资源命名方式
  36. chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 动态导入输出资源命名方式
  37. assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)
  38. clean: true,
  39. },
  40. module: {
  41. rules: [
  42. {
  43. oneOf: [
  44. {
  45. // 用来匹配 .css 结尾的文件
  46. test: /\.css$/,
  47. // use 数组里面 Loader 执行顺序是从右到左
  48. use: getStyleLoaders(),
  49. },
  50. {
  51. test: /\.less$/,
  52. use: getStyleLoaders( "less-loader"),
  53. },
  54. {
  55. test: /\.s[ac]ss$/,
  56. use: getStyleLoaders( "sass-loader"),
  57. },
  58. {
  59. test: /\.styl$/,
  60. use: getStyleLoaders( "stylus-loader"),
  61. },
  62. {
  63. test: /\.(png|jpe?g|gif|svg)$/,
  64. type: "asset",
  65. parser: {
  66. dataUrlCondition: {
  67. maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
  68. },
  69. },
  70. // generator: {
  71. // // 将图片文件输出到 static/imgs 目录中
  72. // // 将图片文件命名 [hash:8][ext][query]
  73. // // [hash:8]: hash值取8位
  74. // // [ext]: 使用之前的文件扩展名
  75. // // [query]: 添加之前的query参数
  76. // filename: "static/imgs/[hash:8][ext][query]",
  77. // },
  78. },
  79. {
  80. test: /\.(ttf|woff2?)$/,
  81. type: "asset/resource",
  82. // generator: {
  83. // filename: "static/media/[hash:8][ext][query]",
  84. // },
  85. },
  86. {
  87. test: /\.js$/,
  88. // exclude: /node_modules/, // 排除node_modules代码不编译
  89. include: path. resolve(__dirname, "../src"), // 也可以用包含
  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. cacheCompression: false, // 缓存文件不要压缩
  102. plugins: [ "@babel/plugin-transform-runtime"], // 减少代码体积
  103. },
  104. },
  105. ],
  106. },
  107. ],
  108. },
  109. ],
  110. },
  111. plugins: [
  112. new ESLintWebpackPlugin({
  113. // 指定检查文件的根目录
  114. context: path. resolve(__dirname, "../src"),
  115. exclude: "node_modules", // 默认值
  116. cache: true, // 开启缓存
  117. // 缓存目录
  118. cacheLocation: path. resolve(
  119. __dirname,
  120. "../node_modules/.cache/.eslintcache"
  121. ),
  122. threads, // 开启多进程
  123. }),
  124. new HtmlWebpackPlugin({
  125. // 以 public/index.html 为模板创建文件
  126. // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
  127. template: path. resolve(__dirname, "../public/index.html"),
  128. }),
  129. // 提取css成单独文件
  130. new MiniCssExtractPlugin({
  131. // 定义输出文件名和目录
  132. filename: "static/css/[name].[contenthash:8].css",
  133. chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
  134. }),
  135. // css压缩
  136. // new CssMinimizerPlugin(),
  137. new PreloadWebpackPlugin({
  138. rel: "preload", // preload兼容性更好
  139. as: "script",
  140. // rel: 'prefetch' // prefetch兼容性更差
  141. }),
  142. ],
  143. optimization: {
  144. minimizer: [
  145. // css压缩也可以写到optimization.minimizer里面,效果一样的
  146. new CssMinimizerPlugin(),
  147. // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
  148. new TerserPlugin({
  149. parallel: threads, // 开启多进程
  150. }),
  151. // 压缩图片
  152. new ImageMinimizerPlugin({
  153. minimizer: {
  154. implementation: ImageMinimizerPlugin. imageminGenerate,
  155. options: {
  156. plugins: [
  157. [ "gifsicle", { interlaced: true }],
  158. [ "jpegtran", { progressive: true }],
  159. [ "optipng", { optimizationLevel: 5 }],
  160. [
  161. "svgo",
  162. {
  163. plugins: [
  164. "preset-default",
  165. "prefixIds",
  166. {
  167. name: "sortAttrs",
  168. params: {
  169. xmlnsOrder: "alphabetical",
  170. },
  171. },
  172. ],
  173. },
  174. ],
  175. ],
  176. },
  177. },
  178. }),
  179. ],
  180. // 代码分割配置
  181. splitChunks: {
  182. chunks: "all", // 对所有模块都进行分割
  183. // 其他内容用默认配置即可
  184. },
  185. },
  186. // devServer: {
  187. // host: "localhost", // 启动服务器域名
  188. // port: "3000", // 启动服务器端口号
  189. // open: true, // 是否自动打开浏览器
  190. // },
  191. mode: "production",
  192. devtool: "source-map",
  193. };

  • 问题:

当我们修改 math.js 文件再重新打包的时候,因为 contenthash 原因,math.js 文件 hash 值发生了变化(这是正常的)。

但是 main.js 文件的 hash 值也发生了变化,这会导致 main.js 的缓存失效。明明我们只修改 math.js, 为什么 main.js 也会变身变化呢?

  • 原因:

    • 更新前:math.xxx.js, main.js 引用的 math.xxx.js
    • 更新后:math.yyy.js, main.js 引用的 math.yyy.js, 文件名发生了变化,间接导致 main.js 也发生了变化
  • 解决:

将 hash 值单独保管在一个 runtime 文件中。

我们最终输出三个文件:main、math、runtime。当 math 文件发送变化,变化的是 math 和 runtime 文件,main 不变。

runtime 文件只保存文件的 hash 值和它们与文件关系,整个文件体积就比较小,所以变化重新请求的代价也小。


  
  1. const os = require( "os");
  2. const path = require( "path");
  3. const ESLintWebpackPlugin = require( "eslint-webpack-plugin");
  4. const HtmlWebpackPlugin = require( "html-webpack-plugin");
  5. const MiniCssExtractPlugin = require( "mini-css-extract-plugin");
  6. const CssMinimizerPlugin = require( "css-minimizer-webpack-plugin");
  7. const TerserPlugin = require( "terser-webpack-plugin");
  8. const ImageMinimizerPlugin = require( "image-minimizer-webpack-plugin");
  9. const PreloadWebpackPlugin = require( "@vue/preload-webpack-plugin");
  10. // cpu核数
  11. const threads = os. cpus(). length;
  12. // 获取处理样式的Loaders
  13. const getStyleLoaders = ( preProcessor) => {
  14. return [
  15. MiniCssExtractPlugin. loader,
  16. "css-loader",
  17. {
  18. loader: "postcss-loader",
  19. options: {
  20. postcssOptions: {
  21. plugins: [
  22. "postcss-preset-env", // 能解决大多数样式兼容性问题
  23. ],
  24. },
  25. },
  26. },
  27. preProcessor,
  28. ]. filter( Boolean);
  29. };
  30. module. exports = {
  31. entry: "./src/main.js",
  32. output: {
  33. path: path. resolve(__dirname, "../dist"), // 生产模式需要输出
  34. // [contenthash:8]使用contenthash,取8位长度
  35. filename: "static/js/[name].[contenthash:8].js", // 入口文件打包输出资源命名方式
  36. chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 动态导入输出资源命名方式
  37. assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)
  38. clean: true,
  39. },
  40. module: {
  41. rules: [
  42. {
  43. oneOf: [
  44. {
  45. // 用来匹配 .css 结尾的文件
  46. test: /\.css$/,
  47. // use 数组里面 Loader 执行顺序是从右到左
  48. use: getStyleLoaders(),
  49. },
  50. {
  51. test: /\.less$/,
  52. use: getStyleLoaders( "less-loader"),
  53. },
  54. {
  55. test: /\.s[ac]ss$/,
  56. use: getStyleLoaders( "sass-loader"),
  57. },
  58. {
  59. test: /\.styl$/,
  60. use: getStyleLoaders( "stylus-loader"),
  61. },
  62. {
  63. test: /\.(png|jpe?g|gif|svg)$/,
  64. type: "asset",
  65. parser: {
  66. dataUrlCondition: {
  67. maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
  68. },
  69. },
  70. // generator: {
  71. // // 将图片文件输出到 static/imgs 目录中
  72. // // 将图片文件命名 [hash:8][ext][query]
  73. // // [hash:8]: hash值取8位
  74. // // [ext]: 使用之前的文件扩展名
  75. // // [query]: 添加之前的query参数
  76. // filename: "static/imgs/[hash:8][ext][query]",
  77. // },
  78. },
  79. {
  80. test: /\.(ttf|woff2?)$/,
  81. type: "asset/resource",
  82. // generator: {
  83. // filename: "static/media/[hash:8][ext][query]",
  84. // },
  85. },
  86. {
  87. test: /\.js$/,
  88. // exclude: /node_modules/, // 排除node_modules代码不编译
  89. include: path. resolve(__dirname, "../src"), // 也可以用包含
  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. cacheCompression: false, // 缓存文件不要压缩
  102. plugins: [ "@babel/plugin-transform-runtime"], // 减少代码体积
  103. },
  104. },
  105. ],
  106. },
  107. ],
  108. },
  109. ],
  110. },
  111. plugins: [
  112. new ESLintWebpackPlugin({
  113. // 指定检查文件的根目录
  114. context: path. resolve(__dirname, "../src"),
  115. exclude: "node_modules", // 默认值
  116. cache: true, // 开启缓存
  117. // 缓存目录
  118. cacheLocation: path. resolve(
  119. __dirname,
  120. "../node_modules/.cache/.eslintcache"
  121. ),
  122. threads, // 开启多进程
  123. }),
  124. new HtmlWebpackPlugin({
  125. // 以 public/index.html 为模板创建文件
  126. // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
  127. template: path. resolve(__dirname, "../public/index.html"),
  128. }),
  129. // 提取css成单独文件
  130. new MiniCssExtractPlugin({
  131. // 定义输出文件名和目录
  132. filename: "static/css/[name].[contenthash:8].css",
  133. chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
  134. }),
  135. // css压缩
  136. // new CssMinimizerPlugin(),
  137. new PreloadWebpackPlugin({
  138. rel: "preload", // preload兼容性更好
  139. as: "script",
  140. // rel: 'prefetch' // prefetch兼容性更差
  141. }),
  142. ],
  143. optimization: {
  144. minimizer: [
  145. // css压缩也可以写到optimization.minimizer里面,效果一样的
  146. new CssMinimizerPlugin(),
  147. // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
  148. new TerserPlugin({
  149. parallel: threads, // 开启多进程
  150. }),
  151. // 压缩图片
  152. new ImageMinimizerPlugin({
  153. minimizer: {
  154. implementation: ImageMinimizerPlugin. imageminGenerate,
  155. options: {
  156. plugins: [
  157. [ "gifsicle", { interlaced: true }],
  158. [ "jpegtran", { progressive: true }],
  159. [ "optipng", { optimizationLevel: 5 }],
  160. [
  161. "svgo",
  162. {
  163. plugins: [
  164. "preset-default",
  165. "prefixIds",
  166. {
  167. name: "sortAttrs",
  168. params: {
  169. xmlnsOrder: "alphabetical",
  170. },
  171. },
  172. ],
  173. },
  174. ],
  175. ],
  176. },
  177. },
  178. }),
  179. ],
  180. // 代码分割配置
  181. splitChunks: {
  182. chunks: "all", // 对所有模块都进行分割
  183. // 其他内容用默认配置即可
  184. },
  185. // 提取runtime文件
  186. runtimeChunk: {
  187. name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名规则
  188. },
  189. },
  190. // devServer: {
  191. // host: "localhost", // 启动服务器域名
  192. // port: "3000", // 启动服务器端口号
  193. // open: true, // 是否自动打开浏览器
  194. // },
  195. mode: "production",
  196. devtool: "source-map",
  197. };

Core-js

为什么

过去我们使用 babel 对 js 代码进行了兼容性处理,其中使用@babel/preset-env 智能预设来处理兼容性问题。

它能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理。

所以此时我们 js 代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错。所以我们想要将 js 兼容性问题彻底解决

是什么

core-js 是专门用来做 ES6 以及以上 API 的 polyfill

polyfill翻译过来叫做垫片/补丁。就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性。

怎么用

  1. 修改 main.js


  
  1. import count from "./js/count";
  2. import sum from "./js/sum";
  3. // 引入资源,Webpack才会对其打包
  4. import "./css/iconfont.css";
  5. import "./css/index.css";
  6. import "./less/index.less";
  7. import "./sass/index.sass";
  8. import "./sass/index.scss";
  9. import "./styl/index.styl";
  10. const result1 = count( 2, 1);
  11. console. log(result1);
  12. const result2 = sum( 1, 2, 3, 4);
  13. console. log(result2);
  14. // 添加promise代码
  15. const promise = Promise. resolve();
  16. promise. then( () => {
  17. console. log( "hello promise");
  18. });

此时 Eslint 会对 Promise 报错。

  1. 修改配置文件
  • 下载包
npm i @babel/eslint-parser -D
  • .eslintrc.js


  
  1. module.exports = {
  2. // 继承 Eslint 规则
  3. extends: [ "eslint:recommended"],
  4. parser: "@babel/eslint-parser", // 支持最新的最终 ECMAScript 标准
  5. env: {
  6. node: true, // 启用node中全局变量
  7. browser: true, // 启用浏览器中全局变量
  8. },
  9. plugins: [ "import"], // 解决动态导入import语法报错问题 --> 实际使用eslint-plugin-import的规则解决的
  10. parserOptions: {
  11. ecmaVersion: 6, // es6
  12. sourceType: "module", // es module
  13. },
  14. rules: {
  15. "no-var": 2, // 不能使用 var 定义变量
  16. },
  17. };

  1. 运行指令
npm run build

此时观察打包输出的 js 文件,我们发现 Promise 语法并没有编译转换,所以我们需要使用 core-js 来进行 polyfill

  1. 使用core-js
  • 下载包
npm i core-js
  • 手动全部引入

  
  1. import "core-js";
  2. import count from "./js/count";
  3. import sum from "./js/sum";
  4. // 引入资源,Webpack才会对其打包
  5. import "./css/iconfont.css";
  6. import "./css/index.css";
  7. import "./less/index.less";
  8. import "./sass/index.sass";
  9. import "./sass/index.scss";
  10. import "./styl/index.styl";
  11. const result1 = count( 2, 1);
  12. console. log(result1);
  13. const result2 = sum( 1, 2, 3, 4);
  14. console. log(result2);
  15. // 添加promise代码
  16. const promise = Promise. resolve();
  17. promise. then( () => {
  18. console. log( "hello promise");
  19. });

这样引入会将所有兼容性代码全部引入,体积太大了。我们只想引入 promise 的 polyfill

  • 手动按需引入

  
  1. import "core-js/es/promise";
  2. import count from "./js/count";
  3. import sum from "./js/sum";
  4. // 引入资源,Webpack才会对其打包
  5. import "./css/iconfont.css";
  6. import "./css/index.css";
  7. import "./less/index.less";
  8. import "./sass/index.sass";
  9. import "./sass/index.scss";
  10. import "./styl/index.styl";
  11. const result1 = count( 2, 1);
  12. console. log(result1);
  13. const result2 = sum( 1, 2, 3, 4);
  14. console. log(result2);
  15. // 添加promise代码
  16. const promise = Promise. resolve();
  17. promise. then( () => {
  18. console. log( "hello promise");
  19. });

只引入打包 promise 的 polyfill,打包体积更小。但是将来如果还想使用其他语法,我需要手动引入库很麻烦。

  • 自动按需引入

    • main.js
    
        
    1. import count from "./js/count";
    2. import sum from "./js/sum";
    3. // 引入资源,Webpack才会对其打包
    4. import "./css/iconfont.css";
    5. import "./css/index.css";
    6. import "./less/index.less";
    7. import "./sass/index.sass";
    8. import "./sass/index.scss";
    9. import "./styl/index.styl";
    10. const result1 = count( 2, 1);
    11. console. log(result1);
    12. const result2 = sum( 1, 2, 3, 4);
    13. console. log(result2);
    14. // 添加promise代码
    15. const promise = Promise. resolve();
    16. promise. then( () => {
    17. console. log( "hello promise");
    18. });

此时就会自动根据我们代码中使用的语法,来按需加载相应的 polyfill 了。

PWA

为什么

开发 Web App 项目,项目一旦处于网络离线情况,就没法访问了。

我们希望给项目提供离线体验。

是什么

渐进式网络应用程序(progressive web application - PWA):是一种可以提供类似于 native app(原生应用程序) 体验的 Web App 的技术。

其中最重要的是,在 离线(offline) 时应用程序能够继续运行功能。

内部通过 Service Workers 技术实现的。

怎么用

  1. 下载包
npm i workbox-webpack-plugin -D
  1. 修改配置文件

  
  1. const os = require( "os");
  2. const path = require( "path");
  3. const ESLintWebpackPlugin = require( "eslint-webpack-plugin");
  4. const HtmlWebpackPlugin = require( "html-webpack-plugin");
  5. const MiniCssExtractPlugin = require( "mini-css-extract-plugin");
  6. const CssMinimizerPlugin = require( "css-minimizer-webpack-plugin");
  7. const TerserPlugin = require( "terser-webpack-plugin");
  8. const ImageMinimizerPlugin = require( "image-minimizer-webpack-plugin");
  9. const PreloadWebpackPlugin = require( "@vue/preload-webpack-plugin");
  10. const WorkboxPlugin = require( "workbox-webpack-plugin");
  11. // cpu核数
  12. const threads = os. cpus(). length;
  13. // 获取处理样式的Loaders
  14. const getStyleLoaders = ( preProcessor) => {
  15. return [
  16. MiniCssExtractPlugin. loader,
  17. "css-loader",
  18. {
  19. loader: "postcss-loader",
  20. options: {
  21. postcssOptions: {
  22. plugins: [
  23. "postcss-preset-env", // 能解决大多数样式兼容性问题
  24. ],
  25. },
  26. },
  27. },
  28. preProcessor,
  29. ]. filter( Boolean);
  30. };
  31. module. exports = {
  32. entry: "./src/main.js",
  33. output: {
  34. path: path. resolve(__dirname, "../dist"), // 生产模式需要输出
  35. // [contenthash:8]使用contenthash,取8位长度
  36. filename: "static/js/[name].[contenthash:8].js", // 入口文件打包输出资源命名方式
  37. chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 动态导入输出资源命名方式
  38. assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)
  39. clean: true,
  40. },
  41. module: {
  42. rules: [
  43. {
  44. oneOf: [
  45. {
  46. // 用来匹配 .css 结尾的文件
  47. test: /\.css$/,
  48. // use 数组里面 Loader 执行顺序是从右到左
  49. use: getStyleLoaders(),
  50. },
  51. {
  52. test: /\.less$/,
  53. use: getStyleLoaders( "less-loader"),
  54. },
  55. {
  56. test: /\.s[ac]ss$/,
  57. use: getStyleLoaders( "sass-loader"),
  58. },
  59. {
  60. test: /\.styl$/,
  61. use: getStyleLoaders( "stylus-loader"),
  62. },
  63. {
  64. test: /\.(png|jpe?g|gif|svg)$/,
  65. type: "asset",
  66. parser: {
  67. dataUrlCondition: {
  68. maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
  69. },
  70. },
  71. // generator: {
  72. // // 将图片文件输出到 static/imgs 目录中
  73. // // 将图片文件命名 [hash:8][ext][query]
  74. // // [hash:8]: hash值取8位
  75. // // [ext]: 使用之前的文件扩展名
  76. // // [query]: 添加之前的query参数
  77. // filename: "static/imgs/[hash:8][ext][query]",
  78. // },
  79. },
  80. {
  81. test: /\.(ttf|woff2?)$/,
  82. type: "asset/resource",
  83. // generator: {
  84. // filename: "static/media/[hash:8][ext][query]",
  85. // },
  86. },
  87. {
  88. test: /\.js$/,
  89. // exclude: /node_modules/, // 排除node_modules代码不编译
  90. include: path. resolve(__dirname, "../src"), // 也可以用包含
  91. use: [
  92. {
  93. loader: "thread-loader", // 开启多进程
  94. options: {
  95. workers: threads, // 数量
  96. },
  97. },
  98. {
  99. loader: "babel-loader",
  100. options: {
  101. cacheDirectory: true, // 开启babel编译缓存
  102. cacheCompression: false, // 缓存文件不要压缩
  103. plugins: [ "@babel/plugin-transform-runtime"], // 减少代码体积
  104. },
  105. },
  106. ],
  107. },
  108. ],
  109. },
  110. ],
  111. },
  112. plugins: [
  113. new ESLintWebpackPlugin({
  114. // 指定检查文件的根目录
  115. context: path. resolve(__dirname, "../src"),
  116. exclude: "node_modules", // 默认值
  117. cache: true, // 开启缓存
  118. // 缓存目录
  119. cacheLocation: path. resolve(
  120. __dirname,
  121. "../node_modules/.cache/.eslintcache"
  122. ),
  123. threads, // 开启多进程
  124. }),
  125. new HtmlWebpackPlugin({
  126. // 以 public/index.html 为模板创建文件
  127. // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
  128. template: path. resolve(__dirname, "../public/index.html"),
  129. }),
  130. // 提取css成单独文件
  131. new MiniCssExtractPlugin({
  132. // 定义输出文件名和目录
  133. filename: "static/css/[name].[contenthash:8].css",
  134. chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
  135. }),
  136. // css压缩
  137. // new CssMinimizerPlugin(),
  138. new PreloadWebpackPlugin({
  139. rel: "preload", // preload兼容性更好
  140. as: "script",
  141. // rel: 'prefetch' // prefetch兼容性更差
  142. }),
  143. new WorkboxPlugin. GenerateSW({
  144. // 这些选项帮助快速启用 ServiceWorkers
  145. // 不允许遗留任何“旧的” ServiceWorkers
  146. clientsClaim: true,
  147. skipWaiting: true,
  148. }),
  149. ],
  150. optimization: {
  151. minimizer: [
  152. // css压缩也可以写到optimization.minimizer里面,效果一样的
  153. new CssMinimizerPlugin(),
  154. // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
  155. new TerserPlugin({
  156. parallel: threads, // 开启多进程
  157. }),
  158. // 压缩图片
  159. new ImageMinimizerPlugin({
  160. minimizer: {
  161. implementation: ImageMinimizerPlugin. imageminGenerate,
  162. options: {
  163. plugins: [
  164. [ "gifsicle", { interlaced: true }],
  165. [ "jpegtran", { progressive: true }],
  166. [ "optipng", { optimizationLevel: 5 }],
  167. [
  168. "svgo",
  169. {
  170. plugins: [
  171. "preset-default",
  172. "prefixIds",
  173. {
  174. name: "sortAttrs",
  175. params: {
  176. xmlnsOrder: "alphabetical",
  177. },
  178. },
  179. ],
  180. },
  181. ],
  182. ],
  183. },
  184. },
  185. }),
  186. ],
  187. // 代码分割配置
  188. splitChunks: {
  189. chunks: "all", // 对所有模块都进行分割
  190. // 其他内容用默认配置即可
  191. },
  192. },
  193. // devServer: {
  194. // host: "localhost", // 启动服务器域名
  195. // port: "3000", // 启动服务器端口号
  196. // open: true, // 是否自动打开浏览器
  197. // },
  198. mode: "production",
  199. devtool: "source-map",
  200. };

  1. 修改 main.js

  
  1. import count from "./js/count";
  2. import sum from "./js/sum";
  3. // 引入资源,Webpack才会对其打包
  4. import "./css/iconfont.css";
  5. import "./css/index.css";
  6. import "./less/index.less";
  7. import "./sass/index.sass";
  8. import "./sass/index.scss";
  9. import "./styl/index.styl";
  10. const result1 = count( 2, 1);
  11. console. log(result1);
  12. const result2 = sum( 1, 2, 3, 4);
  13. console. log(result2);
  14. // 添加promise代码
  15. const promise = Promise. resolve();
  16. promise. then( () => {
  17. console. log( "hello promise");
  18. });
  19. const arr = [ 1, 2, 3, 4, 5];
  20. console. log(arr. includes( 5));
  21. if ( "serviceWorker" in navigator) {
  22. window. addEventListener( "load", () => {
  23. navigator. serviceWorker
  24. . register( "/service-worker.js")
  25. . then( (registration) => {
  26. console. log( "SW registered: ", registration);
  27. })
  28. . catch( (registrationError) => {
  29. console. log( "SW registration failed: ", registrationError);
  30. });
  31. });
  32. }
  1. 运行指令
npm run build

此时如果直接通过 VSCode 访问打包后页面,在浏览器控制台会发现 SW registration failed

因为我们打开的访问路径是:http://127.0.0.1:5500/dist/index.html。此时页面会去请求 service-worker.js 文件,请求路径是:http://127.0.0.1:5500/service-worker.js,这样找不到会 404。

实际 service-worker.js 文件路径是:http://127.0.0.1:5500/dist/service-worker.js

  1. 解决路径问题
  • 下载包
npm i serve -g

serve 也是用来启动开发服务器来部署代码查看效果的。

  • 运行指令
serve dist

此时通过 serve 启动的服务器我们 service-worker 就能注册成功了。


转载:https://blog.csdn.net/qq_63358859/article/details/128765155
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场