事情的起因是這樣的,最近有相當一部分的精力都在做專案的效能優化上,之前有一個專案出現了一個老大難的問題糾結很久了,一直沒時間去看,正好一併解決一下。這個問題很簡單:我用vue-cli建立的專案,按照vue的路由懶載入寫法,打包後卻發現程式碼並沒有分割,全部都打包到app.js中了,導致app.js體積過大,且沒有路由的按需載入了。
找出問題的原因
我開始思考問題原因可能是以下幾點造成的:
- 路由懶載入寫法不對;
- vue-cli版本問題;
- vue-cli的配置問題。
但是這三個可能得原因很快排除了,因為有一個專案上面三個都一樣,程式碼分割正常,那隻能是程式碼問題了。但是那麼多檔案總不能全部review一遍吧,毫無頭緒之下只能採用樸素但實用二分法的方式定位問題檔案了。一番體力活下來終於讓我找到了兩個罪魁禍首,通過觀察這兩個檔案發現都用了同一種的檔案引用方式,類似程式碼如下:
let form = null;
let cpnName = this.template.name;
this.$options.components[cpnName] = require('@/' + this.template.path).default;
form = <cpnName />
return (
<div>{form}</div>
)
元件通過拼接入參的路徑來動態引入元件,其實看到這裡我心裡大概就知道什麼原因了,因為是動態路徑,webpack打包時是靜態解析依賴,根本無法確認檔案的具體地址,所以導致程式碼全部都打到app.js中。為了證明我的想法,我到webpack的github issue中也找到了跟我類似的場景:
這個老哥是想根據傳入的圖片名稱來動態引入圖片,但是打包時候發現其他目錄的圖片也都被打包進來了,webpack的維護者也回答了說,這就是require的工作機制,它不知道你會用哪個資源,它就把它們全部都打包了。
驗證問題
為了驗證這個問題,我建立了一個專案,來複現一下問題:
動態引入的元件程式碼如下:
// src/components/common/DynamicRequireCpn.vue
<script>
export default {
name: 'DynamicRequireCpn',
props: {
template: Object
},
render () {
let form = null;
let cpnName = this.template.name;
this.$options.components[cpnName] = require('@/' + this.template.path).default;
form = <cpnName />
return (
<div>{form}</div>
)
}
}
</script>
路由程式碼如下:
// src/router/index.js
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
];
打包結果如下:
發現程式碼還是都打包到一起了,about元件並沒有分割出來。而且我還在app.js中發現了沒有引用的程式碼。也就是說這種情況下,webpack把src目錄下所有的檔案都打包了。
解決問題的方案
按照上面的實驗和require的工作原理,我想通過縮小require的查到範圍是不是能解決問題呢?
<script>
export default {
name: 'DynamicRequireCpn',
props: {
template: Object
},
render () {
let form = null;
let cpnName = this.template.name;
this.$options.components[cpnName] = require('@/components/common/' + this.template.path).default;
form = <cpnName />
return (
<div>{form}</div>
)
}
}
</script>
這下我把require的動態路徑精確到@/components/common/
,重新打包看看:
Bingo!看到了about元件對應的分割檔案,而且搜尋app.js檔案,也沒有發現未引用的程式碼了,問題解決了!
總結
在使用webpack時,應該儘量減少資源的動態路徑引入,如果必須這樣引入的話,那也要儘量傳入更短的檔案路徑,或者將要動態引入的檔案放到一個目錄下面,防止webpack找到非目標目錄下面。
GOOD
require('@/components/common/' + this.template.path);
BAD
require('@' + this.template.path);