webpack4搭建現代Hybird-h5工程

BUG給我滾發表於2019-03-03

一、前言

這篇文章會分享一下我是如何針對混合開發(Hybird h5端)搭建構建環境

內容涉及到如下幾點:

(1)混合開發h5端頁面特點以及需求

(2)如何利用webpack4進行多入口的程式碼分割以達到較優的快取利用率

(3)如何針對首屏渲染進行優化

(4)使用webpack4如何同時輸出ES next(現代)與es5(向後相容)的包

(5)客戶端打包h5資源到包內策略

程式碼倉庫webpack-esnext-cli,大家也可以邊看程式碼邊理解,當然能自己動手搭建一次是最好的

webpack4搭建現代Hybird-h5工程

二、混合開發h5端頁面特點以及需求

混合開發的h5端頁面有什麼特點?

入口繁雜、部分頁面僅文案、需要較快的首屏渲染速度、部分頁面的使用率不高,依賴網路的速度快慢

(1)入口繁雜

入口繁雜其實意味著你的前端工程搭建必須是以多入口為起點搭建的,如webpack你可以配置entry,自行寫一個指令碼在構建時獲取每一個頁面的js入口,而多入口意味著你必須考慮頁面之間共享的模組應該如何抽取以達到一個較優的模組利用率,這點我們在文章下一節詳細講。

(2)部分頁面僅文案,且利用率不高

其實有做過混合開發你自然會發現有一些頁面是隻有文案,沒有任何js互動的,這類的頁面,類似使用者協議、常見問題之類的文案類頁面,使用率不高,對首屏又有要求的我們完全可以將html寫在html檔案中,不需要用vue這類的框架去寫虛擬DOM依賴框架去渲染,這樣可以節省掉請求指令碼以及框架執行渲染的時間,保證文字可以快速出現在頁面當中。

(3)較快的首屏渲染以及網路的快慢

因為我們的h5頁面是比較依賴手機網路的,它不像客戶端資源都是大包到本地的,現在雖然4g普及了,但是使用者很多時候網路其實並不好,那在弱網的情況下對頁面的開啟速度就有了一定的挑戰了,從工程角度上考慮,我們可不可以像客戶端那樣把h5需要的資源也打包進本地呢?答案當然是可以的,後面我會陸續講到。

三、如何利用webpack4進行程式碼分割

根據上面第二點我們提到的頁面特點和需求,我們的多頁面共享的模組應該是下面這樣的:

1、每個入口基本都需要用到的包應該長期快取(hash值不變)

2、共享的chunk可自由分割

3、共享的chunk可在頁面配置引用

那我們先來看看在webpack4中我們可以通過什麼手段保證如上三點

例如在vue的工程當中,我們的每個入口基本都依賴於vue.js,而在我們打出的包中vue所佔的資源大小比重也是比較大的,而這部分就是我們需要長期快取的。關於這個點我在我的另一篇文章中講過。這裡我們在webpack4中會單獨將vue打包成vendor作為專案的基礎包供頁面引入(當然你也可以將其餘的模組打包進來)

...

optimization: {
    splitChunks: {
        ...
        
        `vendor`: {
            test: /node_modules/vue/,
            name: `vendor`,
            chunks: `all`,
            enforce: true,
            priority: 2
          },
          
        ...
    }
},
plugins: [
    // 穩定moduleId,避免引入了一個新模組後,導致模組ID變更使得vender和common的hash變化後快取失效
    new webpack.HashedModuleIdsPlugin(),
]

...
複製程式碼

單獨將vue打包進vendor後,我們就保證了一個基礎模組是穩定的,但我們還需要一些靈活性,比如我們有一些複雜的頁面可能會做成單頁面,這時候我們就需要引入vue-router、vuex這類的工具。

你可能會說為什麼不把這些包打包進vendor中?因為混合開發中頁面的入口是非常繁雜的,如果使用者開啟一個普通的頁面,僅依賴vue就可以了,但是因為你抽取的時候把vue-router也打包進去了,導致使用者下載了一個它可能用不到的又比較大的檔案,同時這樣也會影響到其他頁面的渲染速度,因為js包太大了,導致下載時間過長,而分開打包能起到一個增量下載的作用。

這時候我們在splitChunks中增加一項spa-vendor配置:

optimization: {
    splitChunks: {
        ...
        
        // 專案基礎包
        `vendor`: {
            test: /node_modules/vue/,
            name: `vendor`,
            chunks: `all`,
            enforce: true,
            priority: 2
        },
        // 單頁面需要引入vue-router, vuex,這裡單獨分割出來
        `spa-vendor`: {
            test: /node_modules/vue-router/g,
            name: `spa-vendor`,
            chunks: `all`,
            enforce: true, 
            priority: 10
        },
          
        ...
    }
},
複製程式碼

好了,到這裡,我們已經把專案一些比較大的,不常變更的包獨立分割出來並且做到持久快取了,那剩餘的大小不那麼大的包我們就可以讓webpack根據大小和引用率去自動打包了,這裡我們加一個commons包的配置

optimization: {
    splitChunks: {
        ...
        
        // 專案基礎包
        `vendor`: {
            test: /node_modules/vue/,
            name: `vendor`,
            chunks: `all`,
            enforce: true,
            priority: 2
        },
        // 單頁面需要引入vue-router, vuex,這裡單獨分割出來
        `spa-vendor`: {
            test: /node_modules/vue-router/g,
            name: `spa-vendor`,
            chunks: `all`,
            enforce: true, 
            priority: 10
        },
        // 剩餘chunk自動分割
        `commons`: {
            name: `commons`,
            minChunks: 5, // 引用次數大於5則打包進commons
            minSize: 3000, // chunk大小大於這個值才允許打包進commons
            chunks: `all`,
            enforce: true,
            priority: 1
        }
        ...
    }
},
複製程式碼

大家看到這裡會看到splitChunk中每個chunk的priority(優先順序)是不一樣的,commons的優先順序是最低的,因為要等到spa-vendor和vendor抽取完成後才會到commons抽取

完成後,打出的包是下面這樣的

vendor(60k):

webpack4搭建現代Hybird-h5工程

spa-vendor(23k,還是比較大的):

webpack4搭建現代Hybird-h5工程

commons:

webpack4搭建現代Hybird-h5工程

自由分割和長期快取我們已經做到了,那剩下的就是chunk在頁面中自由引入了。在我寫的webpack-esnext-cli中,我是用了nunjucks模板引擎去做頁面的資源引入的,利用webpack4和webpack-manifest-plugin外掛在打包後輸出的資源表,對資源進行頁面的自由配置

比如,需要引入spa-vendor的單頁,我們會引入manifest、vendor、spa-vendor、commons包、頁面入口js(業務檔案)預設引入

<!DOCTYPE html>
<html lang="en" bgc-f7f7f7>
<head>
  ...
</head>
<body>
  <div id="app"></div>
  <!-- 以註釋的方式新增模板語法,addAssets方法可以注入對應模組組(按順序) -->
  <!-- {{ `js` | addAssets([`manifest`, `vendor`, `spa-vendor`, `commons`]) }} -->
</body>
</html>
複製程式碼

如果僅僅是普通的只需要基於vue的,我們會引入manifest、vendor、commons(這裡的addAssets方法傳入空陣列預設引入這幾個chunk),這樣我們在頁面開啟時,就不需要載入spa-vendor了,達到一個模組冗餘的作用

<!DOCTYPE html>
<html lang="en" bgc-f7f7f7>
<head>
  ...
</head>
<body>
  <div id="app"></div>
  <!-- 以註釋的方式新增模板語法,addAssets方法可以注入對應模組組(按順序) -->
  <!-- {{ `js` | addAssets([]) }} -->
</body>
</html>
複製程式碼

當然你可以更細緻的區分你的chunk應該如何去分割,這裡只是演示一下。

四、使用webpack4輸出ES next語法的包

PHILIP WALTON這篇文章講解了輸出es next的原理,那時候看得我是激情澎湃,所以就自己花了時間去實現了一套

這裡就講一下思路吧,程式碼實現大家可以自行去看倉庫程式碼

在打包的時候,我們需要輸出兩套包

webpack4搭建現代Hybird-h5工程

原理其實就是通過改變broswerList讓babel編譯出不同語法的包

modern(es6):

webpack4搭建現代Hybird-h5工程

legacy(es5):

webpack4搭建現代Hybird-h5工程

構建es6語法的包後我們需要輸出es5的入口檔案,為了避免輸出的資源表重疊的情況需要給es5的入口重新命名

webpack4搭建現代Hybird-h5工程

其中a.js為我們的es6構建入口,指令碼建立的a-legacy.js為我們的es5包構建入口,內容如下:

// a-legacy.js

import `./a.js`
複製程式碼

打包時,我們根據js入口生成對應的html檔案後,根據每次打包生成的資源表選擇資源進行插入

資源表:

webpack4搭建現代Hybird-h5工程
webpack4搭建現代Hybird-h5工程

如上圖,資源表對應了原本的路徑與輸出後的資源路徑,在輸出html時根據路徑去做資源匹配就可以了

輸出後的html如下(modern包的manifest檔案內聯了):

webpack4搭建現代Hybird-h5工程

其中支援type=module語法的瀏覽器就會自動載入es6語法的包,不支援則載入es5的向後相容包

這麼做其實效益是非常大的,下面引用兩張PHILIP WALTON文章的圖

webpack4搭建現代Hybird-h5工程
webpack4搭建現代Hybird-h5工程

可見輸出的es6的包不管在size還是解析速度都是優於es5語法的包的,對於移動端加速效果還是非常大的

五、加速首屏與將h5資源打包進客戶端

加速首屏要做什麼?就是讓內容儘快的出現啊。

在之前我們已經通過模組分割和輸出es next包讓我們的資源利用率和大小,還有程式碼解析的速度都得到了提升了,接下來我們應該要考慮一下如何利用客戶端的能力進行優化了。

加速首屏,無非就是加快webview的啟動速度,和減少包的下載時間嘛,因為首屏的速度很大一部分原因是因為資源下載導致頁面阻塞。

設想一下,如果webview可以攔截我們的資源請求,那我們是不是就可以把我們頁面的js與css等靜態資源一起打包到客戶端中,在客戶端開啟webview後,通過攔截url,對本地資源進行url的匹配,命中則讀取本地檔案,檔案過期則再次從伺服器上拉取,甚至可以讓服務端做一個推送服務更新資原始檔,能做到這樣h5頁面在客戶端基本能達到秒開了,也能減輕伺服器的壓力,在弱網的情況下優化非常明顯。

當然首屏你還可以通過構建預渲染一部分html到html檔案中,我的另一篇文章中有講–如何在webpack中做預渲染降低首屏空白時間,這裡就不再說了

六、總結

Hybird h5的工程搭建,其實更多是根據需求去做的,使用webpack只是一種方式,更重要的我覺得是對資源載入、快取的理解和運用。不說了,不說了,該時候搬磚了。有什麼疑問或者建議歡迎提出。

相關文章