通常為了開發效率,我們會使用 vue-cli 創建項目,這樣創建的項目默認情況下編譯是會對代碼進行分割的。但是如果是自行配置的 webpack 環境的話,還是很有必要熟悉代碼分割的相關知識的。
為什麼要做代碼分割
在配置 webpack 的過程中,很多時候我們的 webpack 入口只寫了一個 entry: '${sourceDir}/index.js’,默認情況下只會生成一個 bundle 文件,包含了第三方庫、公共代碼及不同頁面所用到的業務邏輯,這必然會造成該 bundle 文件體積過大,影響頁面首次的加載速度,因此我們需要對代碼進行分割,加快首次進入頁面的速度。
代碼分割思路
首先把第三方庫、公共代碼抽離出來,因為這些代碼變動的頻率小,可以打包成一個文件,這樣每次上線文件都不發生變化,可以充分利用網絡緩存加快文件下載速度,分割的細的話就是,第三方庫為一個 js 文件, 公共代碼為一個 js 文件。
然後,按照路由(頁面)進行代碼分割,每個頁面生成一個 js 文件,這樣每次首次進入就只加載公共代碼和本頁面用的的 js 文件, 而不用加載其它頁面無關的代碼。
最後,再進行精細分割的話,就是根據組件使用情況進行分割,來實現組件的懶加載,比如:頁面中的不同 tab,可以根據 tab 的展示情況進行分割,把需要點擊或者用户主動操作才能呈現的組件進行懶加載,這樣就在頁面級又進行了更細粒度的代碼分割。
代碼分割實戰
第三方庫及公共代碼分割
第一步我們進行第三方庫的分割,比如 vue、vue-router、vuex、axios 等三方庫,把它們放到 vender.js 中,然後 utils、common 文件等放在 common.js 中。這些通過 webpack 的 entry 及 splitChunk 配置即可實現。
修改 entry 配置:
{
// ...
entry: {
// 把公共代碼放到 common 裏
common: [`${sourceDir}/utils/index.js`],
main: `${sourceDir}/index.js`,
},
// ...
}
splitChunk 配置:
{
optimization: {
// splitChunks 配置
splitChunks: {
cacheGroups: {
default: {
name: 'vendor',
// 把第三方庫放到 vendor 裏,包括 vue, vue-router, vuex 等
// 因為他們都是從 node_modules 里加載的,這裏直接正則匹配
test: /[\\/]node_modules[\\/]/,
chunks: 'initial',
// 調整優先級,優先處理
priority: 10,
},
common: {
chunks: 'all',
name: 'common',
// 匹配 entry 裏的 common 配置
test: 'common',
},
},
},
// runtime 代碼放在 runtime 文件中
runtimeChunk: {
name: 'runtime',
},
}
}
另外就是 output 配置了,[name] 表示讓 chunk 名稱作為文件名, [chunkhash:8] 表示加上 hash,上線後不走緩存加載最新的代碼。
{
output: {
path: path.join(__dirname, './dist'),
filename: 'static/[name].[chunkhash:8].bundle.js',
chunkFilename: 'static/[name].[chunkhash:8].bundle.js',
},
}
做完第三方庫及公共代碼分割,打包後生成的文件如下:
assets by path static/*.js 138 KiB
asset static/vendor.4391b08b.bundle.js 133 KiB [emitted] [immutable] [minimized] (name: vendor) (id hint: default)
asset static/main.0d6dab3a.bundle.js 3.9 KiB [emitted] [immutable] [minimized] (name: main)
asset static/runtime.bdaa3432.bundle.js 1.1 KiB [emitted] [immutable] [minimized] (name: runtime)
asset static/common.3f62940b.bundle.js 204 bytes [emitted] [immutable] [minimized] (name: common)
asset index.html 537 bytes [emitted]
asset static/main.acdc2841.bundle.css 127 bytes [emitted] [immutable] [minimized] (name: main)
我們可以看到代碼分割到了不同的文件中,vender.js 包含了所有的第三方庫,main.js 包含了我們各個頁面的業務邏輯,公共代碼在 common 中,runtime 包含了運行時代碼,這樣代碼就分散到了不同的文件中,各司其職,且有利於同時進行加載。
但是 main.js 還是包含了多個頁面的代碼,如果只是進入首頁的話,其它頁面的代碼就是多餘的,接下來再進行優化。
按路由分割
這一個比較容易處理,只需改變下路由配置即可,以 () => import(path) 的方式加載頁面組件:
const routes = [
{
path: '/',
// component: Home,
component: () => import('./pages/Home'),
},
{
path: '/todos',
// component: Todos,
component: () => import('./pages/Todos'),
},
{
path: '/about',
// component: About,
component: () => import('./pages/About'),
},
{
path: '/404',
// component: NotFound,
component: () => import('./pages/NotFound'),
},
{
path: '*',
redirect: '/404',
},
];
此時打包會看到多了很多文件,這是把不同頁面的代碼分割到了不同的 JS 文件中,只有訪問對應的頁面才會加載相關的代碼。
assets by path static/*.js 142 KiB
asset static/vendor.4391b08b.bundle.js 133 KiB [emitted] [immutable] [minimized] (name: vendor) (id hint: default)
asset static/runtime.07c35c52.bundle.js 3.99 KiB [emitted] [immutable] [minimized] (name: runtime)
asset static/821.7ba5112d.bundle.js 1.89 KiB [emitted] [immutable] [minimized]
asset static/main.1697fd27.bundle.js 1.68 KiB [emitted] [immutable] [minimized] (name: main)
asset static/820.de28fd7b.bundle.js 562 bytes [emitted] [immutable] [minimized]
asset static/646.a902d0eb.bundle.js 406 bytes [emitted] [immutable] [minimized]
asset static/114.26876aa2.bundle.js 402 bytes [emitted] [immutable] [minimized]
asset static/common.3f62940b.bundle.js 204 bytes [emitted] [immutable] [minimized] (name: common)
assets by path static/*.css 127 bytes
asset static/main.beb1183a.bundle.css 75 bytes [emitted] [immutable] [minimized] (name: main)
asset static/821.cd9a22a5.bundle.css 52 bytes [emitted] [immutable] [minimized]
asset index.html 537 bytes [emitted]
當然,這個地方可能會有爭議,爭議的地方就是:「頁面進入時就把所有頁面的代碼都下載下來,再進入其它頁面不是更快嗎?」。這就取決於項目情況了,看是着重於頁面秒開,還是着重於頁面切換體驗。如果着重於秒開的話,配合 SSR 處理效果會更好。
更細粒度的分割
如果對於頁面打開速度或性能有更高的要求,還可以做更細粒度的代碼分割,比如頁面中功能模塊的懶加載。
這裏以一個點擊按鈕時加載相應的組件為例,進行代碼演示:
這裏有一個 Load Lazy Demo 按鈕,點擊時才加載 LazyComponent 組件,LazyComponent 組件並沒有什麼特別之處,寫法跟普通組件一樣。
<template>
<button @click="loadLazyDemo">Load Lazy Demo</button>
<template v-if="showLazyComponent">
<lazy-component />
</template>
</template>
這裏通過一個 showLazyComponent 控制組件的顯示,當點擊按鈕時,把 showLazyComponent 置為 true,然後就加載 LazyComponent 對應的代碼了。其實關鍵還是通過 () => import(path) 的方式引入組件。
<script>
export default {
data() {
return {
showLazyComponent: false,
};
},
methods: {
loadLazyDemo() {
this.showLazyComponent = true;
},
},
components: {
'lazy-component': () => import('../components/LazyComponent'),
},
};
</script>
OK,以上就是我在 Vue 項目中做的代碼分割的相關內容。