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 内置模块,通过以下选项配置:

{
  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 输出结果缓存:

{
  rules: [
    {
      test: /\.js$/,
      use: ["babel-loader"],
      options: {
        cache: true,
      },
    },
  ];
}

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

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

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

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

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

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

{
    output: {
        name: "[name][contentHash].js",
        path: path.resolve(__dirname, 'dist'),
        clean: true,
    }
}

第二步,提取 runtime 和 manifest:

{
    output: {
        name: "[name][contentHash].js",
        path: path.resolve(__dirname, 'dist'),
        clean: true,
    },
    optimization: {
        runtimeChunk: 'single',
    }
}

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

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

End of entry