# 打包文件的 bundle 文件的网络缓存

目的:网络缓存(Network Cache)优化用户的访问速度

# 什么是网络缓存

浏览器网络请求会对已请求的文件和资源进行缓存,下次客户端访问时就可以直接访问本地缓存中的对应文件,加快访问速度;

# webpack 打包哈希值命名 bundle 文件(编译后文件)

利用打包是拆分代码的能力将不同的模块和资源分别命名,命名的方式采用文件哈希值的模式

这样开发过程中 没有变动代码的内容文件打包后的名称将和之前的文件名称一致,只有代码内容有变动的文件的名称会变化

# 浅析实现流程

从项目发布线上流程来说,比如:

发布前线上项目文件为

index.html;
aaa.js;
bbb.js;
1
2
3

改动项目发布后线上项目文件为

index.html;
aaa.js;
ccc.js;
1
2
3

文件名称 aaa bbb ccc 表示文件哈希值

只要用户之前请求过对应文件aaa.js文件,浏览器就会缓存该文件,重新发版之后aaa.js文件内容未变动,故而可以直接使用本地缓存,无需重复向服务器重新请求; 因为 bbb.js 文件有变动所以文件名称变为了 ccc.js, 就会重新请求文件

































































 
 















































 






 






 













































 
 


































































/**
 * webpack.config.js 配置参考标准
 */

/** nodejs -- path文件路径模块 */
const path = require("path");

/** eslint代码检测 */
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
/** html文件处理  */
const HtmlWebpackPlugin = require("html-webpack-plugin");
/** css文件提取处理 */
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
/** css文件压缩处理 */
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
/** 预加载插件 */
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
/** 可视化依赖分析插件 */
const BundleAnalyzerPlugin =
  require("webpack-bundle-analyzer").BundleAnalyzerPlugin;

//#region ===== tool 工具函数集
/**
 * @description 获取处理css样式的loaders工具配置函数, 配置了基础的loader,同时接收传参自定义loader
 * @param {Array} preProcessorList []
 * @returns
 */
const setStyleLoaders = (preProcessorList = []) => {
  return [
    // 单独提取css文件loader
    MiniCssExtractPlugin.loader,
    // 基础css-loader
    "css-loader",
    // css代码兼容处理loader配置
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            "postcss-preset-env", // 能解决大多数样式兼容性问题
          ],
        },
      },
    },
    // 外部传入自定义 loader
    ...preProcessorList,
  ].filter(Boolean); // 筛选所有存在的loader
};
//#endregion

module.exports = {
  /** 入口文件 */
  entry: "./src/main.js",

  /** 输出文件配置 */
  output: {
    // 输出到本地根目录,所有输出文件的跟目录 ,dist文件夹下
    path: path.resolve(__dirname, "dist"),
    // 注意这里配置的是所有JS文件的输出路径,其他类型单独在 module-rules中配置
    /**
     * [name]: 文件原名称 * [contenthash:8]: 文件内容哈希值取8位
     * [hash:8]: 随机hash值取8位 * [ext]: 使用之前的文件扩展名 * [query]: 添加之前的query参数
     * */
    filename: "static/js/[name].[contenthash:8].js", // 将 js 文件输出到 static/js 目录中, [name] 即为对应引入的文件名
    chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // import()动态导入输出资源命名方式
    assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)
    // 自动将上次打包目录资源清空
    clean: true,
  },

  /** 路径别名 */
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "src/"),
    },
  },

  /** module  */
  module: {
    rules: [
      {
        // 指示所有文件仅能匹配一个 loader,加快编译速度
        oneOf: [
          /** 处理 CSS 文件 */
          {
            test: /\.css$/,
            // use: [MiniCssExtractPlugin.loader, "css-loader"],
            use: setStyleLoaders(), // 结果为: [MiniCssExtractPlugin.loader, "css-loader"],
          },

          /** 处理 less 文件 */
          {
            test: /\.less$/,
            // use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
            use: setStyleLoaders(["less-loader"]), // 结果为: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"]
          },

          /** 处理 scss or sass 文件 */
          {
            test: /\.s[ac]ss$/,
            // use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
            use: setStyleLoaders(["sass-loader"]), // 结果为: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"]
          },

          /** 处理 图片资源 */
          {
            test: /\.(png|jpe?g|gif|webp)$/,
            type: "asset", // 资源类型为 asset,Webpack 会根据文件类型选择合适的加载器进行处理, 比如 url-loader
            parser: {
              dataUrlCondition: {
                maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
              },
            },
            // generator: { filename: "static/imgs/[hash:8][ext][query]" }, // 由 assetModuleFilename 统一设置
          },

          /** 字体资源输出目录 */
          {
            test: /\.(ttf|woff2?)$/,
            type: "asset/resource",
            // generator: { filename: "static/media/[hash:8][ext][query]" }, // 由 assetModuleFilename 统一设置
          },

          /** 媒体资源输出目录 */
          {
            test: /\.(mp4|mp3|avi)$/,
            type: "asset/resource",
            // generator: { filename: "static/media/[hash:8][ext][query]" }, // 由 assetModuleFilename 统一设置
          },

          /** JS 文件的 babel 处理代码语法兼容性 */
          {
            test: /\.js$/,
            exclude: /node_modules/, // 排除node_modules代码不编译
            use: [
              {
                loader: "babel-loader",
                options: {
                  cacheDirectory: true, // 开启babel编译缓存
                  cacheCompression: false, // 缓存文件不要压缩
                  plugins: ["@babel/plugin-transform-runtime"], // 统一引入babel辅助代码-减少代码体积
                },
              },
            ],
          },
        ],
      },
    ],
  },

  /** plugins */
  plugins: [
    /** eslint 检测 */
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, "src"),
      exclude: "node_modules", // 默认值
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(
        __dirname,
        "node_modules/.cache/.eslintcache"
      ),
    }),
    /** 模板 html 处理 */
    new HtmlWebpackPlugin({
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js、css等资源
      template: path.resolve(__dirname, "public/index.html"),
    }),
    /** css文件提取处理 */
    new MiniCssExtractPlugin({
      // 定义输出文件名和目录
      filename: "static/css/[name].[contenthash:8].css",
      chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
    }),
    /** css压缩  */
    new CssMinimizerPlugin(),
    /** 可视化依赖分析配置 */
    // new BundleAnalyzerPlugin({
    //   analyzerMode: "server",
    //   analyzerHost: "127.0.0.1",
    //   analyzerPort: 8888, // 在宿主机打开 127.0.0.1:8888 访问
    //   openAnalyzer: true,
    //   generateStatsFile: false,
    //   statsOptions: null,
    //   logLevel: "info",
    // }),
    /** 预加载文件资源配置 */
    new PreloadWebpackPlugin({
      rel: "preload", // preload兼容性更好
      as: "script",
      // rel: 'prefetch' // prefetch兼容性更差
    }),
  ],

  optimization: {
    chunkIds: "named",
    // 代码分割配置
    splitChunks: {
      chunks: "all", // 对所有模块都进行分割
      // 其他内容用默认配置即可
      minSize: 20000, // 分割代码最小的大小
      minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
      minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
      maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
      maxInitialRequests: 30, // 入口js文件最大并行请求数量
      enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
      cacheGroups: {
        // 组,哪些模块要打包到一个组
        defaultVendors: {
          // 指定chunks名称
          filename: "static/js/bundle_libs.js", // 指定打包到文件夹
          test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
          priority: -10, // 权重(越大越高)
          reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
        },
        default: {
          filename: "static/js/bundle_[contenthash:8].js", // 指定打包到文件夹
          // 其他没有写的配置会使用上面的默认值
          minChunks: 1, // 这里的minChunks权重更大
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },

  /** 配置开发服务器 */
  devServer: {
    host: "localhost", // 启动服务器域名
    port: "3000", // 启动服务器端口号
    open: true, // 是否自动打开浏览器
    hot: true, // 热更新
  },

  /** 环境模式 */
  // mode: "development", // development or production
  mode: "production", // development or 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
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240

# 衍生问题

main.js入口文件引入的文件改变导致打包后main.js名称改变

  • 问题:

当我们修改 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 值和它们与文件关系,整个文件体积就比较小,所以变化重新请求的代价也小。






 
 
 
 



module.exports = {
  // ...其他配置参考上方
  optimization: {
    // ...其他配置参考上方

    // 提取runtime文件
    runtimeChunk: {
      name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名规则
    },
  },
};
1
2
3
4
5
6
7
8
9
10
11