1. Loader vs Plugin
  • Loader 用于加载并解析各类文件,如 css / sass / ts / markdown / png 等;
  • Plugin 用于扩展 Webpack 的能力,实现各种 webpack 本身并不具备的功能,比如清理文件,代码压缩等;
  1. 常用 Loader
  • babel-loader / ts-loader / vue-loader:将各类 js-like 文件解析为 js;
  • sass-loader / less-loader / css-loader / style-loader:解析各类样式文件;
  • file-loader: 文件批处理;
  • url-loader:将文件内容转换为 data-url;
  1. 常用 Plugin
  • HtmlWebpackPlugin 生成 HTML 并自动注入 css 和 js,webpack 5 通过 output.clean: true 代替;
  • MiniCssExtractPlugin 从 js 中分离 css 并生成单独文件;
  • SplitChunksPlugin 自动分割 multi-entry 文件之间的共享模块(内置插件);
  • OptimizeCssAssetsWebpackPlugin 压缩 css;
  • CompressionWebpackPlugin 压缩 js;
  • CleanWebpackPlugin 清理文件;
  • CopyWebpackPlugin 复制文件;
  • BundleAnalyzerPlugin 可视化构建结果分析;
  • HotModuleReplacementPlugin 启动 HMR;
  1. 代码分割
  • Multiple entry + SplitChunksPlugin
  • ES6 新语法 Dynamic import

配置多个入口时,要手动声明公共模块,通过 dependOn 标记入口之间的依赖关系,避免将公共依赖重复打包到每个入口文件中(依赖重复)。

SplitChunksPlugin 是 webpack 内置模块,通过以下选项配置:

1
2
3
4
5
6
7
8
{
optimization: {
// Enable SplitChunksPlugin
splitChunks: {
chunks: "all";
}
}
}

注意,分离代码并非粒度越细越好,因为粒度越细,则 bundles 越多,在 http2 没有普及之前,每个 bundle 都意味着一个独立的 tcp 连接,大量的 tcp 连接也会成为性能瓶颈。可通过以下属性精细化控制代码分割粒度:

  • chunks:指定哪些类型的 chunk 参与拆分(如 ‘initial’、’async’、’all’)。
  • minSize:指定一个模块的最小体积,只有超过这个体积的模块才会被拆分。
  • minChunks:指定一个模块被引用的最小次数,只有被引用次数超过这个值的模块才会被拆分。
  • maxAsyncRequests:指定按需加载的 chunk 的最大数量。
  • maxInitialRequests:指定初始加载的 chunk 的最大数量。
  • cacheGroups:允许你自定义规则来匹配模块并将其拆分到指定的组中。

注:initial 指 entry 中声明的模块,async 指异步加载模块(如动态 import);

  1. 本地缓存

Loader 输出结果缓存:

1
2
3
4
5
6
7
8
9
10
11
{
rules: [
{
test: /\.js$/,
use: ["babel-loader"],
options: {
cache: true,
},
},
];
}

构建结果缓存分为两种——memory 和 filesystem,区别如下:

  • memory:输出资源放入内存,是默认行为,速度更快,但内存容量一般较小,适合小项目;
  • filesystem:输出资源放入文件系统中,可以理解为放硬盘上,速度略慢,但可以跨多次构建复用,且硬盘空间较大,因此适合大型项目;
1
2
3
4
5
{
cache: {
type: "filesystem";
}
}

通过以下属性精细化控制 cache:

  • cacheDirectory:指定 filesystem 类型的缓存结果的存放目录;
  • version:静态指定缓存结果版本号,版本改变,则重新构建;
  • buildDependencies.config:指定一个依赖数组,内部元素改变时将重新构建;
  • buildDependencies.webpack:指定 webpack 依赖版本,版本改变时重新构建;
  1. 长效缓存

目标:通过浏览器缓存策略提高模块加载速度;

第一步,设置唯一文件名:

1
2
3
4
5
6
7
{
output: {
name: "[name][contentHash].js",
path: path.resolve(__dirname, 'dist'),
clean: true,
}
}

第二步,提取 runtime 和 manifest:

1
2
3
4
5
6
7
8
9
10
{
output: {
name: "[name][contentHash].js",
path: path.resolve(__dirname, 'dist'),
clean: true,
},
optimization: {
runtimeChunk: 'single',
}
}

第三步,提取很少改变的外部依赖,并改用稳定的 moduleId 生成方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
output: {
name: "[name][contentHash].js",
path: path.resolve(__dirname, 'dist'),
clean: true,
},
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
catchGroups:{
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all",
}
}
}
}
}
  1. SPA 场景下的 HTTP 缓存策略

首先,入口 HTML 文件必须禁用强缓存:cache-control: no-store;

其次,JS、CSS 文件则可以使用强缓存:

  • 每次验证有效性,cache-control:no-cache;
  • 有效期内无需验证,除非过期,cache-control:max-age=3600, must-revalidate

此时,假如 JS、CSS 有更新,则基于上文中的构建策略,bundle 文件名将改变,HTML 中对应资源的 URL 也就发生改变,因此会及时响应更新。

当然,JS、CSS 文件也可以使用协商缓存,但需要服务器端进行额外配置(响应条件请求),与之相比,前端通过简单的配置即可实现类似效果,免去与后端开发沟通的过程。