基于本文回答

播面 播面

文解图解播客视频,多维讲透八股文
0
评论

如何使用 splitChunks 将第三方库(node_modules)与业务代码分离打包?

在 Webpack 中,使用 optimization.splitChunks 将第三方库(node_modules)与业务代码分离打包是非常常见的优化手段。这样可以利用浏览器的缓存机制:当业务代码频繁更新时,第三方库的代码文件名保持不变,用户无需重新下载大体积的第三方库。

以下是实现这一功能的详细配置步骤和最佳实践:

1. 基础配置(推荐)

webpack.config.js 中添加 optimization.splitChunks 配置:

javascript
const path = require('path');

module.exports = {
  // ... 其他配置 (entry, output 等)

  output: {
    path: path.resolve(__dirname, 'dist'),
    // 使用 [contenthash] 保证只有内容变更时文件名才会改变,利于长缓存
    filename: 'js/[name].[contenthash:8].js',
    chunkFilename: 'js/[name].[contenthash:8].chunk.js',
    clean: true, // Webpack 5 清理 dist
  },

  optimization: {
    // 提取 webpack 运行时的引导代码 (manifest),防止业务代码修改导致 vendor 元素的 hash 改变
    runtimeChunk: 'single', 
    
    splitChunks: {
      // initial: 只处理同步代码;async: 只处理异步加载代码(import());all: 处理所有代码(推荐)
      chunks: 'all', 
      
      cacheGroups: {
        // 自定义抽离 node_modules 的缓存组
        vendor: {
          test: /[\\/]node_modules[\\/]/, // 匹配 node_modules 目录下的模块
          name: 'vendors',                // 打包后的 chunk 名称 (如 vendors.js)
          priority: 10,                   // 优先级,数字越大优先级越高
          enforce: true,                  // 强制执行,忽略 minSize, minChunks 等限制
        },
        // 抽离公共的业务代码(被多次引用的业务模块)
        common: {
          name: 'common',
          minChunks: 2,                   // 至少被引用 2 次才抽离
          priority: 0,
          reuseExistingChunk: true,       // 如果该模块已经被打包过,则复用
        },
      },
    },
  },
};

2. 关键参数详解

  • chunks: 'all':默认是 'async'(只对动态导入 import() 的代码生效)。设为 'all' 可以确保无论是同步 import React from 'react' 还是异步导入的 node_modules 都能被抽离。
  • test: /[\\/]node_modules[\\/]/:正则表达式,用于匹配模块路径。使用 [\\/] 是为了兼容 Windows (\) 和 macOS/Linux (/) 的路径分隔符。
  • priority (优先级):一个模块可能同时符合多个缓存组规则,Webpack 会根据 priority 的大小决定将其放入哪个组。vendor 的优先级通常要高于 common
  • runtimeChunk: 'single'极为重要! 它将 Webpack 管理模块依赖的运行时代码单独抽取为一个 runtime.js。如果不加这个,业务代码的修改会导致注入的 runtime 改变,从而破坏 vendors.jscontenthash 缓存策略。

3. 进阶优化:拆分超大第三方库(多 Vendor 策略)

如果你的 node_modules 非常大(例如包含了 React、Vue、Element-UI、ECharts 等),把它们全部打包进一个 vendors.js 会导致首屏下载单文件过大。

你可以进一步将大的第三方库独立拆分

javascript
optimization: {
  runtimeChunk: 'single',
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      // 1. 拆分框架核心库 (以 React 为例)
      reactFramework: {
        test: /[\\/]node_modules[\\/](react|react-dom|react-router-dom)[\\/]/,
        name: 'framework-react',
        priority: 20, // 优先级高于通用 vendor
      },
      // 2. 拆分 UI 组件库 (以 Element Plus 为例)
      elementPlus: {
        test: /[\\/]node_modules[\\/]element-plus[\\/]/,
        name: 'ui-element-plus',
        priority: 15,
      },
      // 3. 剩下的 node_modules 打包进 vendors
      vendor: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendors',
        priority: 10,
        reuseExistingChunk: true,
      },
    },
  },
}

4. 效果对比

配置前打包结果:

  • main.js (2.5 MB) —— 包含所有业务逻辑 + React + Lodash + 工具库

配置后打包结果:

  • runtime.a1b2c3.js (2 KB) —— Webpack 运行时
  • vendors.d4e5f6.js (1.8 MB) —— 包含所有第三方库(基本不怎么变,利于缓存)
  • main.7g8h9i.js (700 KB) —— 纯业务代码(更新频繁,文件小,下载快)

5. 注意事项

  1. Webpack 5 的 name 属性:在生产环境中,Webpack 5 推荐不要将 name 设为固定字符串(或者设为 false),让 Webpack 根据 Chunk ID 自动生成名称,这样可以获得更好的长期缓存(Long-term Caching)效果。
  2. HTML 引入顺序:如果使用的是 html-webpack-plugin,它会自动按正确的依赖顺序注入 <script> 标签(通常是 runtime -> vendors -> main)。无需手动干预。