Open kangyana opened 1 year ago
为什么需要进行分包,一个大的 bundle.js 不好吗?
bundle.js
极其不建议,可从两方面进行考虑:
webpack(或其他构建工具) 运行时代码不容易变更,需要单独抽离出来,比如 webpack.runtime.js。 由于其体积小,必要时可注入 index.html 中,减少 HTTP 请求数,优化关键请求路径
webpack.runtime.js
index.html
React(Vue) 运行时代码不容易变更,且每个组件都会依赖它,可单独抽离出来 framework.runtime.js。 请且注意,务必将 React 及其所有依赖(react-dom/object-assign)共同抽离出来,否则有可能造成性能损耗,见下示例
framework.runtime.js
假设仅仅抽离 React 运行时(不包含其依赖)为单独 Chunk,且每个路由页面为单独 Chunk。某页面不依赖任何第三方库,则该页面会加载以下 Chunk
因 webpack 依赖其 object-assign,而 object-assign 将被打入共同依赖 vendor.chunk.js,因此此时它必回加载,但是该页面并不依赖任何第三方库,完全没有必要全部加载 vendor.chunk.js
将 React 运行时及其所有依赖,共同打包,修复结果如下,拥有了更完美的打包方案。
一个模块被 N(2 个以上) 个 Chunk 引用,可称为公共模块,可把公共模块给抽离出来,形成 vendor.js。
vendor.js
问:那如果一个模块被用了多次 (2 次以上),但是该模块体积过大(1MB),每个页面都会加载它(但是无必要,因为不是每个页面都依赖它),导致性能变差,此时如何分包?
答:如果一个模块虽是公共模块,但是该模块体积过大,可直接 import() 引入,异步加载,单独分包,比如 echarts 等
import()
echarts
问:如果公共模块数量多,导致 vendor.js 体积过大(1MB),每个页面都会加载它,导致性能变差,此时如何分包
答:有以下两个思路
verdor-A.js
vendor-B.js
vendor-C.js
vendor-XXX.js
在 webpack 中可以使用 SplitChunksPlugin (opens new window)进行分包,它需要满足三个条件:
minChunks
maxInitialRequests/maxAsyncRequests
minSize/maxSize
以下是 next.js 的默认配置,可视作最佳实践
源码位置: next/build/webpack-config.ts
{ // Keep main and _app chunks unsplitted in webpack 5 // as we don't need a separate vendor chunk from that // and all other chunk depend on them so there is no // duplication that need to be pulled out. chunks: (chunk) => !/^(polyfills|main|pages\/_app)$/.test(chunk.name) && !MIDDLEWARE_ROUTE.test(chunk.name), cacheGroups: { framework: { chunks: (chunk: webpack.compilation.Chunk) => !chunk.name?.match(MIDDLEWARE_ROUTE), name: 'framework', test(module) { const resource = module.nameForCondition && module.nameForCondition() if (!resource) { return false } return topLevelFrameworkPaths.some((packagePath) => resource.startsWith(packagePath) ) }, priority: 40, // Don't let webpack eliminate this chunk (prevents this chunk from // becoming a part of the commons chunk) enforce: true, }, lib: { test(module: { size: Function nameForCondition: Function }): boolean { return ( module.size() > 160000 && /node_modules[/\\]/.test(module.nameForCondition() || '') ) }, name(module: { type: string libIdent?: Function updateHash: (hash: crypto.Hash) => void }): string { const hash = crypto.createHash('sha1') if (isModuleCSS(module)) { module.updateHash(hash) } else { if (!module.libIdent) { throw new Error( `Encountered unknown module type: ${module.type}. Please open an issue.` ) } hash.update(module.libIdent({ context: dir })) } return hash.digest('hex').substring(0, 8) }, priority: 30, minChunks: 1, reuseExistingChunk: true, }, commons: { name: 'commons', minChunks: totalPages, priority: 20, }, middleware: { chunks: (chunk: webpack.compilation.Chunk) => chunk.name?.match(MIDDLEWARE_ROUTE), filename: 'server/middleware-chunks/[name].js', minChunks: 2, enforce: true, }, }, maxInitialRequests: 25, minSize: 20000, }
1. 为什么需要分包?
为什么需要进行分包,一个大的
bundle.js
不好吗?极其不建议,可从两方面进行考虑:
bundle.js
的缓存失效bundle.js
中 1/N 的代码,剩下代码属于其它页面,完全没有必要加载2. 如何更好的分包?
打包工具运行时
前端框架运行时
React(Vue) 运行时代码不容易变更,且每个组件都会依赖它,可单独抽离出来
framework.runtime.js
。 请且注意,务必将 React 及其所有依赖(react-dom/object-assign)共同抽离出来,否则有可能造成性能损耗,见下示例假设仅仅抽离 React 运行时(不包含其依赖)为单独 Chunk,且每个路由页面为单独 Chunk。某页面不依赖任何第三方库,则该页面会加载以下 Chunk
因 webpack 依赖其 object-assign,而 object-assign 将被打入共同依赖 vendor.chunk.js,因此此时它必回加载,但是该页面并不依赖任何第三方库,完全没有必要全部加载 vendor.chunk.js
将 React 运行时及其所有依赖,共同打包,修复结果如下,拥有了更完美的打包方案。
高频库
一个模块被 N(2 个以上) 个 Chunk 引用,可称为公共模块,可把公共模块给抽离出来,形成
vendor.js
。问:那如果一个模块被用了多次 (2 次以上),但是该模块体积过大(1MB),每个页面都会加载它(但是无必要,因为不是每个页面都依赖它),导致性能变差,此时如何分包?
答:如果一个模块虽是公共模块,但是该模块体积过大,可直接
import()
引入,异步加载,单独分包,比如echarts
等问:如果公共模块数量多,导致
vendor.js
体积过大(1MB),每个页面都会加载它,导致性能变差,此时如何分包答:有以下两个思路
vendor.js
改变策略,比如被引用了十次以上,被当做公共模块抽离成verdor-A.js
,五次的抽离为vendor-B.js
,两次的抽离为vendor-C.js
vendor.js
的体积,当大于 100KB 时,再次进行分包,多分几个vendor-XXX.js
,但每个vendor.js
都不超过 100KB3. 使用 webpack 分包
在 webpack 中可以使用 SplitChunksPlugin (opens new window)进行分包,它需要满足三个条件:
minChunks
:一个模块是否最少被 minChunks 个 chunk 所引用maxInitialRequests/maxAsyncRequests
:最多只能有 maxInitialRequests/maxAsyncRequests 个 chunk 需要同时加载 (如一个 Chunk 依赖 VendorChunk 才可正常工作,此时同时加载 chunk 数为 2)minSize/maxSize
:chunk 的体积必须介于 (minSize, maxSize) 之间以下是 next.js 的默认配置,可视作最佳实践