webpack4搭建現代Hybirdh5工程

NO IMAGE

一、前言

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

內容涉及到如下幾點:

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

(2)如何利用webpack4進行多入口的代碼分割以達到較優的緩存利用率

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

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

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

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

webpack4搭建現代Hybirdh5工程

二、混合開發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搭建現代Hybirdh5工程

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

webpack4搭建現代Hybirdh5工程

commons:

webpack4搭建現代Hybirdh5工程

自由分割和長期緩存我們已經做到了,那剩下的就是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搭建現代Hybirdh5工程

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

modern(es6):

webpack4搭建現代Hybirdh5工程

legacy(es5):

webpack4搭建現代Hybirdh5工程

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

webpack4搭建現代Hybirdh5工程

其中a.js為我們的es6構建入口,腳本創建的a-legacy.js為我們的es5包構建入口,內容如下:

// a-legacy.js

import './a.js'

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

資源表:

webpack4搭建現代Hybirdh5工程

webpack4搭建現代Hybirdh5工程

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

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

webpack4搭建現代Hybirdh5工程

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

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

webpack4搭建現代Hybirdh5工程

webpack4搭建現代Hybirdh5工程

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

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

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

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

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

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

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

六、總結

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

相關文章

package.json裡‘’^~“符號的意思

Angular與AngularJS概念區分

如何理解async/await

使用postcsswritesvg在retina屏繪製1px細線