We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chunk 是指最终被打包出来的代码块(构建产物每个文件就是一个 chunk),code splitting 则是指如何生成这些代码块。做 code splitting 有如下四个方法:
其中前面两种是基本的方法,它们将 chunk 分为 initial 和 async 两种类型。
import()
后面两个方法是指通过 webpack 的配置,可以按照一定的规范来划分 chunk,也就是从上面两种基本的 chunk 中提取出更多 chunk,达到提高缓存命中率,降低冗余代码等优化,从而实现期望的优化目的。
一句话来说:webpack 会 以 entry 和 import 为切割点划分文件,然后按照 optimization.splitChunks 配置来做公共 chunk 的提取。
optimization.splitChunks
Since webpack v4, the CommonsChunkPlugin was removed in favor of optimization.splitChunks.
webpack 核心通过 entry 还有 import 实现了按照切割点做基本的划分,SplitChunksPlugin 主要是实现基于基础划分之上的一个优化,主要就是通过提取更多通用的 chunk 来做到 Prevent Duplication。
官方配置文档:https://webpack.js.org/configuration/optimization/#optimizationsplitchunks
其实就是 SplitChunksPlugin 插件的配置 https://webpack.js.org/plugins/split-chunks-plugin/
umi(该文章写的时候是 2.5.5) 的默认配置是 { chunks: 'async', name: 'vendors' }。
{ chunks: 'async', name: 'vendors' }
if (!opts.disableDynamicImport && !process.env.__FROM_UMI_TEST) { webpackConfig.optimization .splitChunks({ chunks: 'async', name: 'vendors', }) .runtimeChunk(false); }
这里我觉得 umi 的默认配置不是很合理,这样会导致实际上所有提取出来的模块最后都被合并了。
webpack 原生的默认规则是:
具体默认配置:
module.exports = { //... optimization: { splitChunks: { chunks: 'async', // 提取 chunks 的时候从哪里提取,如果为 all 那么不管是不是 async 的都可能被抽出 chunk,为 initial 则会从非 async 里面提取。 minSize: 30000, // byte, == 30 kb,越大那么单个文件越大,chunk 数就会变少(针对于提取公共 chunk 的时候,不管再大也不会把动态加载的模块合并到初始化模块中)当这个值很大的时候就不会做公共部分的抽取了 maxSize: 0, // 文件的最大尺寸,优先级:maxInitialRequest/maxAsyncRequests < maxSize < minSize,需要注意的是这个如果配置了,umi.js 就可能被拆开,最后构建出来的 chunkMap 中可能就找不到 umi.js 了。 minChunks: 1, // 被提取的一个模块至少需要在几个 chunk 中被引用,这个值越大,抽取出来的文件就越小 maxAsyncRequests: 5, // 在做一次按需加载的时候最多有多少个异步请求,为 1 的时候就不会抽取公共 chunk 了 maxInitialRequests: 3, // 针对一个 entry 做初始化模块分隔的时候的最大文件数,优先级高于 cacheGroup,所以为 1 的时候就不会抽取 initial common 了。 automaticNameDelimiter: '~', // 文件名分隔符 name: true, // chunk 的名称,如果设置为固定的字符串那么所有的 chunk 都会被合并成一个,这就是为什么 umi 默认只有一个 vendors.async.js。 cacheGroups: { // 自定义规则,会继承和覆盖上面的配置 vendors: { test: /[\\/]node_modules[\\/]/, // test 符合这个规则的才会加到对应的 group 中 priority: -10 // 一个模块可能属于多个 chunkGroup,这里是优先级,自定义的 group 是 0 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true // 如果该chunk包含的modules都已经另一个被分割的chunk中存在,那么直接引用已存在的chunk,不会再重新产生一个 }, common: { // 这个不是默认的,我自己加的 filename: '[name].bundle.js', // chunks 为 initial 时有效。在 manifest 中最后会是 '[name].js': [name].bundle.js。在 umi 中该项默认值是 [name].async.js,webpack 默认值是 [name].js。 name: 'common', // 和 filename 的作用类似 chunks: 'initial', minChunks: 1, enforce: true, // 不管 maxInitialRequest maxAsyncRequests maxSize minSize 怎么样都会生成这个 chunk } } } } };
遵循 [type]~[filename] 这样的规则。
[type]~[filename]
其中 filename 对于动态加载分割出来的是 output.chunkFilename,其它的是 output.filename。
output.chunkFilename
output.filename
filename 和 chunkFilename 的格式类似 [name].async.js,其中 [name] 可以指定,也可以使用自动生成的。对于动态加载的模块,可以配置 webpackChunkName 这个 inline directives 来设置 name。
[name].async.js
[name]
// 示例 webpackChunkName function getComponent() { return import(/* webpackChunkName: "lodash" */ 'lodash').then(({ default: _ }) => { var element = document.createElement('div'); element.innerHTML = _.join(['Hello', 'webpack'], ' '); return element; }).catch(error => 'An error occurred while loading the component'); } getComponent().then(component => { document.body.appendChild(component); });
公共的 chunk 的 name 是多个模块的 name 合并在一起(但是有长度限制):
类似:vendors~p__dashboard__routers__App~p__dashboard__routers__Books~p__dashboard__routers__Collaborative~c1537219.async.js。
vendors~p__dashboard__routers__App~p__dashboard__routers__Books~p__dashboard__routers__Collaborative~c1537219.async.js
优化思路:
import(/* webpackPrefetch: true */ 'LoginModal');
会在 HTML 中添加:
<link rel="prefetch" href="login-modal-chunk.js">
和语雀做的预加载不同,这个预加载是预加载未来可能出现的请求。prefetch 是新的页面中可能需要加载的文件,preload 是当前页面可能需要加载的文件。
请求数和大小之间的权衡。HTTP2 普及后倾向于多文件,单个文件更小,提高缓存命中率和总体资源大小。
个人的优化建议:
一些特别大的库可以加上 external,比如 data-set,g2 这些。
比起 optimization.splitChunks 来说,optimization.runtimeChunk 更简单。
这个值默认是 false,当 runtimeChunk 为 true 时,会将 webpack 生成的 runtime 作为独立 chunk ,runtime 包含在模块交互时,模块所需的加载和解析逻辑。这部分的大小通常来说很小,在 ant-design-pro 的项目中尝试了一下,设置为 true 之后打包出来的 runtime~umi.js 只有 36.0 KB(未压缩和 gzip 前)。
如果配置了改项,那么你需要在你的页面中提前引入相关的 runtime 的 js。
config.optimization.splitChunks({ cacheGroups: { commons: { name: 'commons', // 没有 name 的话会按照规则生成多个 chunk。name 相同使得 chunk 都被合并到一起了,不再受到其它规则的约束。一般 chunks 不是 sync 的 cacheGroups 都要指定 name,因为 initial 的 chunk 中抽取出来的模块需要手动引入(当然也可以按照构建信息来自动处理,不过当前 umi 中没有这个逻辑,umi 默认是 ['umi'] 这一个 chunk) chunks: 'initial', // async 的模块将不参与这个逻辑 minChunks: 2, // 至少被两个入口 chunk 复用的模块才会被提取 }, }, });
config.optimization.splitChunks({ cacheGroups: { vendors: { name: 'vendors', // 没有 name 的话会按照规则生成多个 chunk。name 相同使得 chunk 都被合并到一起了,不再受到其它规则的约束。 chunks: 'all', test: /[\\/]node_modules[\\/]/, }, }, });
这个示例只是一个演示,通常你的项目不应该这么做,这样会导致请求数过多。
config.optimization.splitChunks({ maxAsyncRequests: 10000, chunks: 'async', // 这里只是针对 async 的 chunk,因为 async 的 chunk 都是自动的异步加载的,分多少个都没关系,但是对于 initial 的 chunk,需要手动引入 minChunks: 1, minSize: 0, });
上面这个配置试了下在 pro 的 dashboard 页面能够拆出 57 个 js。
config.optimization.splitChunks({ maxAsyncRequests: 1, // 每个 async 的 chunk 抽取过后还是只有一个 // minSize 这里就不管用了,虽然 minSize 优先级最高,但是再高也没法再削减入口的 JS 的大小。 });
require.enture
The text was updated successfully, but these errors were encountered:
No branches or pull requests
什么是 code splitting
为什么要做 code splitting
chunk 概述
chunk 是指最终被打包出来的代码块(构建产物每个文件就是一个 chunk),code splitting 则是指如何生成这些代码块。做 code splitting 有如下四个方法:
其中前面两种是基本的方法,它们将 chunk 分为 initial 和 async 两种类型。
import()
来加载的部分。后面两个方法是指通过 webpack 的配置,可以按照一定的规范来划分 chunk,也就是从上面两种基本的 chunk 中提取出更多 chunk,达到提高缓存命中率,降低冗余代码等优化,从而实现期望的优化目的。
一句话来说:webpack 会 以 entry 和 import 为切割点划分文件,然后按照
optimization.splitChunks
配置来做公共 chunk 的提取。解析 SplitChunksPlugin (optimization.splitChunks)
webpack 核心通过 entry 还有 import 实现了按照切割点做基本的划分,SplitChunksPlugin 主要是实现基于基础划分之上的一个优化,主要就是通过提取更多通用的 chunk 来做到 Prevent Duplication。
配置说明
官方配置文档:https://webpack.js.org/configuration/optimization/#optimizationsplitchunks
其实就是 SplitChunksPlugin 插件的配置 https://webpack.js.org/plugins/split-chunks-plugin/
umi(该文章写的时候是 2.5.5) 的默认配置是
{ chunks: 'async', name: 'vendors' }
。这里我觉得 umi 的默认配置不是很合理,这样会导致实际上所有提取出来的模块最后都被合并了。
webpack 原生的默认规则是:
具体默认配置:
构建产物生成规则
遵循
[type]~[filename]
这样的规则。其中 filename 对于动态加载分割出来的是
output.chunkFilename
,其它的是output.filename
。filename 和 chunkFilename 的格式类似
[name].async.js
,其中[name]
可以指定,也可以使用自动生成的。对于动态加载的模块,可以配置 webpackChunkName 这个 inline directives 来设置 name。公共的 chunk 的 name 是多个模块的 name 合并在一起(但是有长度限制):
类似:
vendors~p__dashboard__routers__App~p__dashboard__routers__Books~p__dashboard__routers__Collaborative~c1537219.async.js
。性能优化
优化思路:
预加载
会在 HTML 中添加:
和语雀做的预加载不同,这个预加载是预加载未来可能出现的请求。prefetch 是新的页面中可能需要加载的文件,preload 是当前页面可能需要加载的文件。
splitChunk 配置策略
请求数和大小之间的权衡。HTTP2 普及后倾向于多文件,单个文件更小,提高缓存命中率和总体资源大小。
个人的优化建议:
external
一些特别大的库可以加上 external,比如 data-set,g2 这些。
optimization.runtimeChunk
比起 optimization.splitChunks 来说,optimization.runtimeChunk 更简单。
这个值默认是 false,当 runtimeChunk 为 true 时,会将 webpack 生成的 runtime 作为独立 chunk ,runtime 包含在模块交互时,模块所需的加载和解析逻辑。这部分的大小通常来说很小,在 ant-design-pro 的项目中尝试了一下,设置为 true 之后打包出来的 runtime~umi.js 只有 36.0 KB(未压缩和 gzip 前)。
如果配置了改项,那么你需要在你的页面中提前引入相关的 runtime 的 js。
示例
将多个 entry 共享的代码提取到一个 chunk 里面
把 node_modules 中的内容全部打到一个 chunk 里面
尽可能的多文件,每个模块一个 chunk
这个示例只是一个演示,通常你的项目不应该这么做,这样会导致请求数过多。
上面这个配置试了下在 pro 的 dashboard 页面能够拆出 57 个 js。
async 不提取 common,每个动态加载的模块一个 chunk
其它一些有趣的点
require.enture
来做动态加载,它是 node 的一个未能正式发布的特性。参考文章
The text was updated successfully, but these errors were encountered: