進入公司之後,接手的便是前人留下來的一個大專案。慶幸的是整個專案擁有完善的產品功能文件,但是由於專案過於龐大,老舊。包含了打包過慢,冗餘檔案過多等諸多問題。想要快速的解決這些問題,想要完全把功能重構一遍的話,成本太高了。一個一個檔案來過,時間成本也比較大。因此在此篇文章中,我們介紹一下我是如何配合webpack一步步進行分析,將專案進行優化的。
同時我針對思路封裝了一個webpack-unused-files,用於查詢專案中的冗餘檔案,歡迎試用並star
原文連結
問題
首先,我們先大致看下我們都有什麼問題,然後一步步進行解決
- 專案頻繁進行修改,冗餘檔案過多
- 部分第三方依賴濫用,想去除但是不知道在哪個檔案中。或沒用,但是遺留在package.json裡,
- 專案龐大,打包的結果過大,時間過長
刪除冗餘檔案
由於專案的頻繁改動,有很多檔案已經不被使用並且沒有被刪除。由於專案的不斷擴大,只會影響我們定位功能和問題的速度,因此對冗餘檔案進行清理,是很重要的。但是我們單憑肉眼很難識別哪個檔案是否被依賴的,因此還要通過webpack來解決。
1.獲取專案依賴的所有檔案
我們來看一下webpack的輸出檔案格式:
1 2 3 4 5 6 7 8 9 10 |
{ ... chunks: [{ name: 'chunk-name', modules: [ // 每個chunk中所有的依賴檔案 ] }] ... }所以說,根據這個stats.json,我們可以拿到在整個專案中拿到的所有專案檔案: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** * 查詢依賴的模組 */ function findSrcModules () { return new Promise((resolve, reject) => { fs.readFile(statPath, (err, data) => { if (err) return const json = JSON.parse(data) const assetsList = json.chunks let ret = [] // 拿到所有chunk的所有依賴檔案 assetsList.forEach(chunk => { const modules = chunk.modules.map(item => item.name) ret = ret.concat(modules) }) // 去除node_modules中的檔案 ret = ret.filter(item => item.indexOf('node_modules') 通過這一步,我們可以拿到專案中,所有打包依賴的檔案。 |
2.獲取專案中所有的檔案
通過glob,我們可以獲取所有的檔案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/** * 查詢依賴的模組 */ function findSrcModules () { return new Promise((resolve, reject) => { fs.readFile(statPath, (err, data) => { if (err) return const json = JSON.parse(data) const assetsList = json.chunks let ret = [] // 拿到所有chunk的所有依賴檔案 assetsList.forEach(chunk => { const modules = chunk.modules.map(item => item.name) ret = ret.concat(modules) }) // 去除node_modules中的檔案 ret = ret.filter(item => item.indexOf('node_modules') < 0) resolve(ret) }) }) } |
3.將兩個檔案陣列進行對比,然後進行刪除等操作:
將兩個陣列進行對比,沒有出現在依賴中的檔案,就是冗餘檔案。我們可以一鍵刪除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/** * 查詢依賴的模組 */ function findSrcModules () { return new Promise((resolve, reject) => { fs.readFile(statPath, (err, data) => { if (err) return const json = JSON.parse(data) const assetsList = json.chunks let ret = [] // 拿到所有chunk的所有依賴檔案 assetsList.forEach(chunk => { const modules = chunk.modules.map(item => item.name) ret = ret.concat(modules) }) // 去除node_modules中的檔案 ret = ret.filter(item => item.indexOf('node_modules') < 0) resolve(ret) }) }) } |
分析第三方依賴
根據上述冗餘檔案的思路,我們同樣可以對第三方依賴進行處理,大致思路如下
- 獲取所有包含node_modules的依賴
- 將檔名進行擷取、去重。獲取到所有的依賴
- 與package.json進行對比,拿到沒有使用的依賴
- 將對比結果進行分析,將不想使用的依賴儲存下來
- 再次查詢stat.json,查詢該依賴的reson欄位,獲取再哪裡引用了該依賴,進行輸出
- 將依賴進行手動替換、刪除等操作
可以說,拿到了所有依賴及依賴關係,我們可以很靈活的對其進行處理,拿到我們想要的結果。
該功能後續也會更新到webpack-unused-files中去。
優化打包大小
讓人震驚的是,整個專案由於種種原因,打包後的大小有近20M的大小!雖然並不是TO C專案,並且針對頁面進行了程式碼拆分和懶載入,但是作為一個“合格的前端”,這種現象是一定要修改的(沒錯!)。該如何下手呢?一個個的翻程式碼,看看我們都引用了什麼大依賴,看哪些專案過大未免太複雜了。我們看看webpack給我嗎提供了什麼方案:
1.展示打包結果
我們知道,在webpack打包結束後,會自動在控制檯顯示打包結果。同時,他也提供了輸出依賴及大小的功能,我們執行以下引數, 便可將所有的依賴進行展示,並且看到他們的大小了。
1 |
webpack --display-modules --sort-modules-by size |
結果類似這樣:
我們可以很快的定位到排名前幾的js檔案或者第三方依賴,決定該如何對其進行處置。
2.視覺化分析依賴
webpack提供了一個功能,將打包的所有依賴檔案以及關係,以json格式進行輸出:
1 |
webpack --profile --json > stats.json |
這是我們整篇文章的一個基礎,很多人基於此封裝了不少視覺化分析的工具,可以直觀的看到各個
檔案、chunk之間的依賴關係以及大小等,快速定位到大檔案、大模組
webpack analyse
3.優化方案
通過以上兩種方法,我們可以很好的對內容檔案和依賴進行定位和分析,針對打包大小的優化方案網上已經有很多了,在此不再進行贅述,提供幾個思路及參考:
- CommonsChunkPlugin提取公共程式碼
- dll-plugin進行大檔案單獨打包,快取
- 刪除無用的依賴(後面會提到
- 選擇性的棄用一些依賴
- 程式碼壓縮
- babel-polyfill
- Scope Hoisting
優化打包時間
針對打包時間的優化的文章其實也很多了,我們在此僅提供一些思路。我們主要提一點,通過構建會發現,專案中引用了大量的svg圖示以及國旗圖示,每次在靜態資源處理中,打包時間就會變的特別慢。
我們在專案中使用的svg-sprite-loader,自動將各個svg圖示進行svg-spirte。但是我們知道,這些圖示一旦引用,我們很少進行修改。尤其是像國旗圖示這種,但是每次構建我們都需要進行重複打包。因此,我們可以提前把這些圖示進行svg-sprite。推薦一個網站,將各種svg圖示提前進行sprite並自動進行引用:
日常打包時間優化點
- externals 避免打包大的第三方依賴
- dll-plugin 預打包第三方依賴
- happypack 多程式處理,快取
- 快取與增量構建
- babel-loader?cacheDirectory
- webpack cache:true
- 減少構建搜尋或編譯路徑 alias resolve
- 具象打包的範圍 include exclude
總結
通過對webpack輸出依賴關係的json的分析,我們可以直觀的拿到以下資料:
- 所有依賴檔案及其大小
- 每個依賴檔案是被哪些檔案引用的
- 專案依賴的第三方依賴
通過這些資料,我們可以很方便的對現有專案進行優化。
生命不息,倒騰不止。讓我們對所有的噁心程式碼說再見!