为什么要分包?

我们通常打包完整个项目丢到服务器上,然后第一次打开发现特别慢,控制台一看,好家伙,chunk_vender 大的一批,这时候分包就可以派上用场了。当然,可以通过懒加载、Gzip 压缩、external 等等去做首屏优化,今天的主角主要是分包。

如何配置

// 配置代码分割
config.optimization.splitChunks = {
/**
* 'all':表示所有模块都将被分割。这是默认值,会将所有满足条件的模块进行分割。
* 'async':表示只分割异步加载的模块。这些模块通常是通过动态导入(例如使用 import())或按需加载的。
* 'initial':表示只分割初始加载的模块,即入口模块。这些模块是应用程序的入口点,最初由浏览器加载的模快 */
chunks: "all",
minSize: 30000, // 分割的模块最小大小,小于该大小的模块不会被分割
maxSize: 500000, // 分割的模块最大大小,超过该大小的模块会被拆分成更小的模块
minChunks: 1, // 表示至少被引用 1 次的模块才会被分割
maxAsyncRequests: 30, // 表示同时加载的异步模块最大数量。
maxInitialRequests: 30, // 表示入口文件中并行请求的最大数量。
automaticNameDelimiter: "~", // 表示生成的文件名中的连接符
cacheGroups: {
// cacheGroups如果不设置maxSize,那么会将所有的模块都打包到一个文件中,如果设置了maxSize,那么会将超过maxSize的模块进行拆分,拆分成多个文件,每个文件的大小不超过maxSize
//配置具体的分割规则
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "node_initial_",
chunks: "initial",
priority: -10,
minSize: 30000, // 分割的模块最小大小,小于该大小的模块不会被分割
maxSize: 500000, // 分割的模块最大大小,超过该大小的模块会被拆分成更小的模块
},
common: {
name: "common",
/**
* 模块被引用的最小次数。如果一个模块的引用次数达到或超过该值,才会被分割到对应的缓存组中。在上述配置中,common 缓存组要求模块被引用至少 2 次
*
* */
minChunks: 2,
chunks: "all",
priority: -20,
/** 是否复用已经存在的模块块。如果设置为 true,当一个模块已经被分割到其他缓存组中时,将会重复使用该模块,
* 而不会创建一个新的分割块。在上述配置中,common 缓存组设置为 true,以确保重复使用已经分割的模块。 */
reuseExistingChunk: true,
},
},
};

配置的当方式和写法有很多种,也有很多属性的配置,大家可以根据项目的实际情况去配置。

属性描述

chunks

  1. all‘:表示所有模块都将被分割。这是默认值,会将所有满足条件的模块进行分割

​ *在大型项目中,使用 a11 选项可以将所有资源分割为多个较小的包,从而提高加载速度和性能.

  1. async‘:表示只分割异步加载的模块。这些模块通常是通过动态导入(例如使用 import())或按需加载的。

​ *当项目中使用了代码分割和懒加载(例如:路由懒加载、模块懒加载等),你想让异步加载的模块分开打包以优化性能,可以使用async选项。

  1. initial‘:表示只分割初始加载的模块,即入口模块。这些模块是应用程序的入口点,最初由浏览器加载的模快

​ *当页面加载时希望尽可能减少首次加载的时间,可将同步加载的模块进行分包,从而达到按需加载和懒加载的效果。这样能够优化初始渲染速度,提高用户体验。

minSize

分割的模块最小大小,小于该大小的模块不会被分割

maxSize

分割的模块最大大小,超过该大小的模块会被拆分成更小的模块

cacheGroups 如果不设置 maxSize,那么会将所有的模块都打包到一个文件中,如果设置了 maxSize,那么会将超过 maxSize 的模块进行拆分,拆分成多个文件,每个文件的大小不超过 maxSize

cacheGroups

用于在splitChunks插件中自定义代码分割的缓存组。通过配置cacheGroups,你可以更细致地控制分割代码的构建过程,以优化项目的性能和加载速度。

cacheGroups 允许你根据自定义的条件将模块分类到不同的缓存组。你可以分别为每个缓存组定义不同的分包策略、优先级、名称等。在splitChunks插件中,cacheGroups的默认配置已经包含了两个缓存组:vendors和 default

vendors缓存组负责将node_modules中的模块打包成一个独立的代码块(即vendors文件)。而default缓存组负责处理项目中重复引用的模块

cacheGroups: {
//配置具体的分割规则
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'node_initial_',
chunks: 'initial',
priority: -10,
minSize: 30000, // 分割的模块最小大小,小于该大小的模块不会被分割
maxSize: 500000 // 分割的模块最大大小,超过该大小的模块会被拆分成更小的模块
},
common: {
name: 'common',
/**
* 模块被引用的最小次数。如果一个模块的引用次数达到或超过该值,才会被分割到对应的缓存组中。在上述配置中,common 缓存组要求模块被引用至少 2 次
*
* */
minChunks: 2,
chunks: 'all',
priority: -20,
/** 是否复用已经存在的模块块。如果设置为 true,当一个模块已经被分割到其他缓存组中时,将会重复使用该模块,
* 而不会创建一个新的分割块。在上述配置中,common 缓存组设置为 true,以确保重复使用已经分割的模块。 */
reuseExistingChunk: true
}
}

其中

1. test:命中规则,可以是正则表达式或者函数。

函数:

test: (module) => {
return module.context && module.context.includes("node_modules");
};

2.name:分包代码块(chunk)的名称,可以是函数:

(module, chunks) => {
// 创建一个基于模块名和引用模块数量的名称
// 示例:common-2
return `common-${chunks.length}`;
};

3.priority:优先级,数值越大越高,当模块同时命中两个或多个代码块匹配规则时,分到优先级高的代码块。

4.minchunks:模块被引用的最小次数。如果一个模块的引用次数达到或超过该值,才会被分割到对应的缓存组中,也是一种匹配规则,可以是函数:

minChunks: (module) => {
// 根据模块的特性来动态设置`minChunks`
return module.resource && /common/.test(module.resource);
};

5.reuseExistingChunk:是否复用已经存在的模块块。如果设置为 true,当一个模块已经被分割到其他缓存组中时,将会重复使用该模块,而不会创建一个新的分割块。在上述配置中,common 缓存组设置为 true,以确保重复使用已经分割的模块。

tips

  1. 如果 chunkname 没有包含 hash 的话,一些按需加载的如 vue 等等,需要额外处理,加上 hash 避免被浏览器缓存

    chunkFileNames({ name }) {
    // cache- 缓存标记 不使用hash启用浏览器缓存
    if (name.startsWith('cache-')) return 'static/js/[name].js'
    return 'static/js/[name]-[hash].js'
    },
    manualChunks(id) {
    if (id.includes('node_modules')) {
    const pkgName = id.toString().split('node_modules/')[1].split('/')[0]
    // 首屏需要加载的第三方包 一些需要按需加载的第三方包不能使用缓存
    if (
    ['vue', '@vue', 'vue-router', 'vuex', 'axios', 'ant-design-vue', '@ant-design', 'nprogress'].includes(
    pkgName
    )
    ) {
    return 'vendor'
    } else {
    // 异步按需加载的第三方包 浏览器缓存标记
    return `cache-${pkgName}`
    }
    }
    }

chunk 和 cacheGroup 的关系

chunk(代码块)是将应用程序的代码和依赖关系划分为多个文件块的结果。这些代码块可以按需加载或预获取,从而提高加载和执行性能。cacheGroup(缓存组)是splitChunks插件的配置选项,用于定义如何将 module 分组为较大的 chunks。换句话说,cacheGroup 允许我们实现代码分割的策略,以将较大的 chunks 进一步细分为较小的 chunks。

你可以把一个 chunk 理解为一个输出文件。在 webpack 中,chunk 通常对应于一个或多个输入源文件(如 JavaScript、CSS 等)经过加载器和插件处理后生成的一个输出文件(如一个.js.css文件等)。

webpack 会在构建过程中根据配置选项和依赖关系把源代码分割成多个 chunk,每个 chunk 中包含了一个或多个相关的模块。然后它会将这些 chunk 分别生成为一个独立的输出文件,以便浏览器可以按需加载和执行。这有助于提高应用程序的性能,因为用户无需一次性加载整个应用程序的所有代码,只需按需加载所需功能的代码即可。

filename 和 chunkFilename 的区别

// 配置打包出口名字
config.output.filename = "static/js/[name].[contenthash:8].js";
config.output.chunkFilename = "static/js/[name].[chunkhash:8].js";

filename:入口文件 页面组件也属于入口文件走 filename,

chunkfilename:

非入口文件 主要用于定义那些非入口文件(Non-entry chunks)的文件名模式,即那些由代码分割、按需加载、LazyLoading 和 require.ensure 等动态导入功能生成的文件

hash、chunkhash、contenthash 的区别

hash:每次构建都会生成一个唯一的 hash 值 根据整个项目的构建来定义 hash,只要项目文件有改动,整个项目构建的 hash 值就会更改

chunkhash:根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值 根绝 chunk 的内容来定义 hash,chunk 内容不变,则 chunkhash 不变

contenthash:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变 根据文件内容来定义 hash,文件内容不变,则 contenthash 不变

Uniapp 分包刨坑

昨晚一系列操作后,通过webpack-bundle-analyzer一看,芜湖~大功告成!打包效果杠杠的,然后部署一看,怎么打开网页是空白的???可真是首屏优化到极致了

后面排查到,原来是,分包后的 chunk 没有自动被注入 index.html 也就是我们的模板文件,应该时 uniapp 做了什么修改?还想着用 new HtmlWebpackPlugin 来解决,但其实 uniapp 已经内置了这玩意了,而且你需要在 plugin(‘html-index’).tap 上加,下面是代码

chainWebpack: (config) => {
config.plugin("html-index").tap((args) => {
/*
这样是不行的!!!!
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, './public/index.html'),
// templateParameters: {
// BASE_URL: `/`
// },
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
}
}),
*/
args[0].template = path.resolve(__dirname, "./public/index.html");
args[0].filename = "index.html";
args[0].chunksSortMode = "none";
args[0].chunks = "all";
args[0].minify = {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true,
inifyCss: true, //压缩css
};
return args;
});
};

**args[0].chunks = all **解决了问题,生成的 HTML 文件将会包含所有生成的 chunks。这将确保 HTML 文件加载和使用所有从入口文件生成的 chunks(即所有 JavaScript 和 CSS 文件)

完!