在webpack中使用code splitting 實現按需加載


隨着移動設備的升級、網絡速度的提高,用戶對於web應用的要求越來越高,web應用要提供的功能越來越。功能的增加導致的最直觀的后果就是資源文件越來越大。為了維護越來越龐大的客戶端代碼,提出了模塊化的概念來組織代碼。webpack作為一種模塊化打包工具,隨着react的流行也越來越流行。

webpack在官方文檔上解釋為什么又做一個模塊打包工具的時候,是這樣說的:

The most pressing reason for developing another module bundler was Code Splitting and that static assets should fit seamlessly together through modularization.

開發一個新的模塊打包工具最重要的原因就是Code Splitting,並且還要保證靜態資源也可以無縫集成到模塊化中。其中Code Splitting是webpack提供的一個重要功能,通過這個功能可以實現按需加載,減少首次加載時間。

 

Code Splitting

翻譯一下官方文檔對於Code Splitting的介紹:

對於大型的web 應用而言,把所有的代碼放到一個文件的做法效率很差,特別是在加載了一些只有在特定環境下才會使用到的阻塞的代碼的時候。Webpack有個功能會把你的代碼分離成Chunk,后者可以按需加載。這個功能就是Code Spliiting

Code Spliting的具體做法就是一個分離點,在分離點中依賴的模塊會被打包到一起,可以異步加載。一個分離點會產生一個打包文件。 

例如下面使用CommonJS風格的require.ensure作為分離點的代碼:

 

 

除了這樣的寫法,還可以在配置文件中使用CommonChunkPlugin合並文件

問題

現在進入正題,本文不會針對React或者Vue做示例,因為這兩個框架有很成熟的按需加載方案。 
下面這個例子用Backbone Router做路由,但是其中提到的按需加載方式可以用到大多數路由系統中。 

假設應用有三個路由:

  • 主頁
  • 關於
  • 支付

開始時的代碼(index.js):

 

 

這里有三個url路徑:index, about, pay,對應了三個很簡單的handler。這樣的代碼量的時候,這樣寫是沒問題的。 
但是隨着功能的增加,handler里的內容會越來越多,所以要先把handler分離到不同的模塊里。

邏輯分離

把about的handler放在新的目錄下(about/index.js):

 

 

index,pay也按照同樣的辦法分離出去。 
在index.js中修改一下代碼:

 

 

這樣分離之后對於開發而言減輕了痛苦,模塊化的好處顯而易見。但是分離出去的文件最終還是需要再引入的,最終生成的打包文件還是會非常大,用戶從而不得去花很長時間加載一整個大文件。 

打開瀏覽器的主頁,可以看到請求了一個bundle.js文件,里面包含了這個應用的全部模塊。 
也就是說這樣只是減少了開發的痛苦,對用戶而言不會有改善。

使用Code Splitting進行第一次優化

為了不讓用戶一次加載整個大文件,稍微好點的做法是讓用戶分開一次一次加載文件。 
正好Code Splitting可以把在分離點中依賴的模塊會被打包到一起,然后異步加載。 
修改一下index.js

 

 

 

因為require.ensure會生成一個小的打包文件,這樣可以保證用戶不一次加載全部文件,而是先加載bundle.js,再加載兩個小的js文件。 
打開瀏覽器可以看到加載了三個js文件 

現在瀏覽器要加載三個文件,增加了http請求數量。但是對於訪問頻率比較高的主頁而言,因為主頁的內容是直接打包的,會首先加載,用戶看到主頁的速度變快了。對於訪問about和pay的用戶而言,因為http請求數量變多,理論上會更慢的看到內容。是否分割代碼應該根據實際情況來分析,因為這篇文章主要說的是代碼分割,所以就先假設分離開之后對用戶訪問更有利。
然而類似about和pay這兩個頁面用戶不會每次都訪問,在打開主頁的時候就加載about和pay頁面的handler是一種浪費,應該等到用戶訪問about和pay鏈接的時候再加載對應的js文件。

第二次優化

想法很簡單:初始時只規定主頁的路由,而對於about和pay這種訪問頻率比較低的路由就動態加載。動態加載的方式:在處理未定義路由的handler中,通過匹配當前的路徑,增加router,然后重新解析頁面。 
首先增加一個新路由:'*AllMissing': 'pathFinder' 
pathFinder函數的思路是:先定義好about和pay頁面和路由和入口,然后把路由解析成正則表達式,通過正則表達式可以判斷出來當前的路徑符合哪條路由,然后增加新路由。 
routes.js

 

 

router.js,具體的思路在代碼注釋中:

 

 

然后在index.js引入router.js,路由就可以工作了 

在我們看來,路由現在是動態解析,動態加載文件的。打開瀏覽器,再看一下網絡面板。 

打開主頁,只請求了bundle.js,文件內容也是只包含了主頁的代碼。 

再打開about頁面,請求了一個1.bundle.js,看一下1.bundle.js的內容就會發現,里面包含了about和pay兩個頁面的內容。這是webpack強大的地方,前文提到過,一個分離點會產生一個打包文件,而我們因為只有一個require.ensure,所以webpack通過自己的分析就只產生了一個打包文件,精准的包含了我們需要的內容。不得不說,webpack分析代碼的功能有點厲害。

直接使用require.ensure是不能保證完全按需加載了,好在有loader可以幫助解決這個問題:bundle-loader. 
只用改變一點點就可以按需加載了:

 

 

bundle-loader是一個用來在運行時異步加載模塊的loader,使用了bundle-loader加載的文件可以動態的加載。
例如下面的官方示例:

 

 

因為webpack在編譯階段會遍歷到所有可能會使用到的文件,而bundle-loader就是在所有文件的外層加了一層wraper:

 

 

這樣,在require文件的時候只是引入了wraper,而且因為每個文件都會產生一個分離點,導致產生了多個打包文件,而打包文件的載入只有在條件命中的情況下才產生,也就可以按需加載。

經過這樣的修改,瀏覽器就可以在不同的路徑下加載不同的依賴文件了

總結

在單頁應用中使用這樣的方式按需加載文件,對於路由庫的要求也很簡單: 

  • 建立從路由到正則表達式的映射,如果沒有的話,自己寫也可以 
  • 能夠動態的添加路由 
  • 能夠加載指定的路由 

大多數路由庫都可以做到上面三點,所以這篇文章提出的是比較普遍的辦法。當然,如果你用React或者Vue,他們配套的路由會比這個優化的更全面。

 

注:這篇文章的內容參考了https://medium.com/@somebody32/how-to-split-your-apps-by-routes-with-webpack-36b7a8a6231#.ncyca72ms,但是最后作者提出的方案也比較復雜,所以就自己寫了一篇,最后的辦法比較簡單。

 

原創文章轉載請注明:

轉載自AlloyTeam:http://www.alloyteam.com/2016/02/code-split-by-routes/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM