為什麼我寫了路由懶載入但程式碼卻沒有分割?

語言: CN / TW / HK

事情的起因是這樣的,最近有相當一部分的精力都在做專案的效能優化上,之前有一個專案出現了一個老大難的問題糾結很久了,一直沒時間去看,正好一併解決一下。這個問題很簡單:我用vue-cli建立的專案,按照vue的路由懶載入寫法,打包後卻發現程式碼並沒有分割,全部都打包到app.js中了,導致app.js體積過大,且沒有路由的按需載入了。

找出問題的原因

我開始思考問題原因可能是以下幾點造成的:

  1. 路由懶載入寫法不對;
  2. vue-cli版本問題;
  3. 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);