为什么要分包
当搭建的利用重视用户关上速度时,正当的分包策略有助于缩小用户首屏加载利用时加载的资源数量,并且对于局部资源能够复用,防止反复加载,从而晋升用户体验。
举例来说,应用 webpack 进行正当的分包能够达到如下成果,比方:
- 让我的项目的分包体积更小,充分利用浏览器并行加载的能力,防止加载过大的 chunk。
- 以后我的项目是基于 vue 的,如果依照默认的分包策略,我的项目每次更新后都会生成新的 main.js,main.js 中又蕴含了 vue 的代码,相当于每次我的项目更新,用户都要从新下载一遍 vue 的代码,这是没有必要的。通过改写分包策略,能够将 vue 相干的代码独自生成一个包,将其缓存到客户端后,后续的每次利用更新就不须要客户端下载反复的代码。
- 剥离 ElementPlus 相干的代码,使得在 SSR 时能够预加载 ElementPlus 的款式,防止款式闪动。
应用动静导入
webpack 提供了 动静导入 (dynamic import) 性能来实现了能够在利用运行时懒加载一些 JS 代码。
示例 1:懒加载 SDK
比方,当咱们应用一个比拟大的第三方 SDK 时,如百度云的 Bos 文件上传 SDK,如果咱们不做任何优化,那么 webpack 会将这个 SDK 打包到利用的主包中,那么就会导致用户无论用户是否有用到文件上传的性能,在页面加载时都会去下载百度云的文件上传 SDK,那么这个下载行为既会节约带宽,又会阻塞页面的渲染,使用户的白屏工夫加长。
为了防止这个问题,咱们就能够通过应用 webpack 的动静导入性能,让用户点击『上传』按钮时,再去加载文件上传的 SDK,这样就真正坐到了按需加载,示例代码如下:
const loadBaiduCloudSdk = () => import(/* webpackChunkName: "baiducloud" */ '@baiducloud/sdk');
uploadBtnEl.addEventListener('click', async () => {const {BosClient} = await loadBaiduCloudSdk();
new BosClient({// ... ...})
})
这里咱们应用了 magic comment 的 webpackChunkName 来显式指定了 webpack 打包的 JS 代码块的名称,当构建实现后,如果咱们的 chunkFilename
定义的为 [name].[contenthash:8].js
,那么咱们就会发现构建产出中为 Bos SDK 独自生成了一个 baiducloud.xxxxxxxx.js
的文件。在 Chrome DevTool 中的 network 面板中能够看到,当用户点击上传按钮后浏览器才会下载这个 chunk:
示例 2:Vue 按需加载 i18n 语言包
如果利用须要多语言性能,那么只有当用户拜访对应语言版本的网站时才须要加载这个网站的语言包,而不是一次性加载所有的语言包,利用 webpack 动静导入咱们能够实现这一点。
咱们的文件构造如下:
lang
├── en-US.ts
├── zh-CN.ts
├── ar.ts
├── ur.ts
└── ... ...
// en-US.ts
export default {
hello: 'Hello',
word: 'Word'
}
咱们编写一个 loadLang
函数:
// en-US 不进行懒加载,因为其作为 FALLBACK_LANG 是必须加载的
import messageSchema from './lang/en-US';
const FALLBACK_LANG = 'en-US';
export async function loadLang(i18n: I18n, lang: string) {const messages = await import(/* webpackChunkName: "locale-[request]" */ `./lang/${lang}.ts`);
// set locale and locale message
i18n.global.setLocaleMessage(locale, messages.default);
// set fallback langs
i18n.global.setLocaleMessage(FALLBACK_LANG, messageSchema);
}
当页面加载时,咱们依照页面门路来为用户按需加载语言:
// app.ts
const i18n = createI18n({locale, legacy: false, fallbackLocale: FALLBACK_LANG});
// 以 vue router 的路由守卫为示例,在加载页面前去下载对应的语言包
router.beforeEach(async (to, _from, next) => {
const pathname = window.location.pathname;
let lang = pathname.split('/')[1];
// set i18n
await loadLocaleMessages(i18n, lang);
setI18nLanguage(i18n, lang);
return next();});
app.use(i18n)
咱们能够通过 webpack-bundle-analyzer 看出,所有的语言都被 webpack 独自打包为了一个独立的 JS,如:
当用户拜访对应的语言时(除了 en-US,因为其作为 FALLBACK_LANG 会始终被加载),对应的语言包才会被加载。
示例 3:vue-router 路由懒加载
vue-router 的路由懒加载实际上也是动静导入的一种利用:
// 将
// import UserDetails from './views/UserDetails.vue'
// 替换成
const UserDetails = () => import('./views/UserDetails.vue')
const router = createRouter({
// ...
routes: [{path: '/users/:id', component: UserDetails}],
})
如果应用了 webpack,能够应用命名 chunk:
const UserDetails = () =>
import(/* webpackChunkName: "group-user" */ './UserDetails.vue')
const UserDashboard = () =>
import(/* webpackChunkName: "group-user" */ './UserDashboard.vue')
const UserProfileEdit = () =>
import(/* webpackChunkName: "group-user" */ './UserProfileEdit.vue')
应用 optimization.splitChunks
Webpack 提供了 optimization.splitChunks
选项来提供给开发编写一些自定义的分包策略。对于一般的开发者来说,Webpack 的默认分包策略曾经足够,其默认分包策略为:
- 新的 chunk 能够被共享,或者模块来自于
node_modules
文件夹 - 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积)
- 当按需加载 chunks 时,并行申请的最大数量小于或等于 30
- 当加载初始化页面时,并发申请的最大数量小于或等于 30
借助这个配置项,咱们能够更细化的配置我的项目的产出。
示例 1:独自打包 vue 文件
假如咱们在应用 webpack 编写一个 vue 我的项目,那么通常 vue 的版本在每次迭代利用版本后通常是不会产生扭转的,如果咱们能够将 vue 相干的代码打包成一个包,并利用浏览器缓存缓存起来这个包,那么在利用每次迭代后,客户端就能尽可能的少产生新的文件变更,网站加载就不会因为频繁迭代上线而让客户端须要频繁下载之前曾经缓存好的资源。
利用 splitChunks
咱们利用文件名匹配的形式来获取到 vue 相干的代码,并将其打包成一个 vue-bundle.[hash].js
这样的文件:
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000,
maxAsyncRequests: 5,
cacheGroups: {
vue: {
// 优先级
priority: 20,
test: /[\\/]node_modules[\\/](vue|vue-router|vuex)[\\/]/,
name: 'vue',
chunks: 'all'
},
// ... ...
},
},
},
optimization.splitChunks.chunks
其默认值为async
,即只为应用了异步导入形式(即动静导入)引入的包才会被拆分为一个独自的 js。设置为all
后,webpack 会尝试对所有的代码块进行拆分,包含同步引入的代码,即便是单入口文件,只有文件超出肯定的体积、被多个文件援用肯定次数或其余限定条件时,就会被拆分成子包。
示例 2:独自打包 ElementPlus 的 CSS 款式
在 web 利用加载时,如果遇到 CSS 文件会阻塞页面的渲染,尤其是对于一个应用了 Vue 或 React 框架的我的项目来说,在页面加载时,通常会加载一个 runtime 文件来获取以后页面的依赖,而后再去拉取以后页面须要 JS 和 CSS,这样就使得页面白屏工夫更长了。
那么假使咱们可能提前加载好某些 CSS,整个页面的白屏时长必定会缩小一些,尤其是对于 SSR 的我的项目来说,提前加载 CSS 是十分有必要的。
以 ElementPlus 为例,咱们能够独自将 ElementPlus 的款式给打包成一个 CSS 文件,并将其写入到 HTML 模板中,这样在页面加载时,并且在 runtime 执行前就能提前加载 ElementPlus 组件的款式了,缩小了资源加载的期待时长。
如果我的项目在入口就援用到了 ElementPlus,那么
html-webpack-plugin
生成的 HTML 文件中就会主动加上 ElementPlus 的 css 文件。
代码示例如下:
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30000,
maxAsyncRequests: 5,
cacheGroups: {
elementPlus: {
// 优先级
priority: 20,
test: /[\\/]node_modules[\\/]element-plus(.*)/,
name: 'element-plus',
chunks: 'all',
// 指定这条策略只对 css 失效
type: "css/mini-extract",
enforce: true,
}
// ... ...
},
},
},
留神:如果要只独自打包 css,是须要借助 mini-css-extract-plugin
插件来实现的,因为这个插件是用于将引入的 css 进行拆分并打包成独自的 css。只有应用了这个插件,cacheGroups
中的 type
才会有 css/mini-extract
这个值(参考)。
除了 type 为
css/mini-extract
之外,还能够设置auto/javascript
来将 cacheGorup 规定独自利用为 js 文件上,而 CSS 走默认的规定。