Open shfshanyue opened 2 years 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
page-a.chunk.js
vendor.chunk.js
object-assign
将 React 运行时及其所有依赖,共同打包,修复结果如下,拥有了更完美的打包方案。
一个模块被 N(2个以上) 个 Chunk 引用,可称为公共模块,可把公共模块给抽离出来,形成 vendor.js。
vendor.js
问:那如果一个模块被用了多次 (2次以上),但是该模块体积过大(1MB),每个页面都会加载它(但是无必要,因为不是每个页面都依赖它),导致性能变差,此时如何分包?
答:如果一个模块虽是公共模块,但是该模块体积过大,可直接 import() 引入,异步加载,单独分包,比如 echarts 等
import()
echarts
问:如果公共模块数量多,导致 vendor.js 体积过大(1MB),每个页面都会加载它,导致性能变差,此时如何分包
答:有以下两个思路
在 webpack 中可以使用 SplitChunksPlugin 进行分包,它需要满足三个条件:
以下是 next.js 的默认配置,可视作最佳实践
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, }
如何正确地进行分包
为什么需要分包?
为什么需要进行分包,一个大的
bundle.js
不好吗?极其不建议,可从两方面进行考虑:
bundle.js
的缓存失效bundle.js
中 1/N 的代码,剩下代码属于其它页面,完全没有必要加载如何更好的分包?
打包工具运行时
前端框架运行时
假设仅仅抽离 React 运行时(不包含其依赖)为单独 Chunk,且每个路由页面为单独 Chunk。某页面不依赖任何第三方库,则该页面会加载以下 Chunk
webpack.runtime.js
5KB ✅framework.runtime.js
30KB ✅page-a.chunk.js
50KB ✅vendor.chunk.js
50KB ❌ (因 webpack 依赖其object-assign
,而object-assign
将被打入共同依赖vendor.chunk.js
,因此此时它必回加载,但是该页面并不依赖任何第三方库,完全没有必要全部加载vendor.chunk.js
)将 React 运行时及其所有依赖,共同打包,修复结果如下,拥有了更完美的打包方案。
webpack.runtime.js
5KB ✅framework.runtime.js
40KB ✅ (+10KB)page-a.chunk.js
50KB ✅高频库
一个模块被 N(2个以上) 个 Chunk 引用,可称为公共模块,可把公共模块给抽离出来,形成
vendor.js
。问:那如果一个模块被用了多次 (2次以上),但是该模块体积过大(1MB),每个页面都会加载它(但是无必要,因为不是每个页面都依赖它),导致性能变差,此时如何分包?
答:如果一个模块虽是公共模块,但是该模块体积过大,可直接
import()
引入,异步加载,单独分包,比如echarts
等问:如果公共模块数量多,导致 vendor.js 体积过大(1MB),每个页面都会加载它,导致性能变差,此时如何分包
答:有以下两个思路
使用 webpack 分包
在 webpack 中可以使用 SplitChunksPlugin 进行分包,它需要满足三个条件:
以下是
next.js
的默认配置,可视作最佳实践