三十分鐘掌握Webpack效能優化

superMaryyy發表於2019-03-04

Webpack是現在主流的功能強大的模組化打包工具,在使用Webpack時,如果不注意效能優化,有非常大的可能會產生效能問題,效能問題主要分為開發時打包構建速度慢、開發除錯時的重複性工作、以及輸出檔案質量不高等,因此效能優化也主要從這些方面來分析。本文主要是根據自己的理解對《深入淺出Webpack》這本書進行總結,涵蓋了大部分的優化方法,可以作為Webpack效能優化時的參考和檢查清單。基於Webpack3.4版本,閱讀本文需要您熟悉Webpack基本使用方法,讀完大約需要三十分鐘。

by MaryTien from supermaryy.com

一、優化構建速度

Webpack在啟動後會根據Entry配置的入口出發,遞迴地解析所依賴的檔案。這個過程分為搜尋檔案和把匹配的檔案進行分析、轉化的兩個過程,因此可以從這兩個角度來進行優化配置。

1.1 縮小檔案的搜尋範圍

搜尋過程優化方式包括:

  1. resolve欄位告訴webpack怎麼去搜尋檔案,所以首先要重視resolve欄位的配置:

    1. 設定resolve.modules:[path.resolve(__dirname, `node_modules`)]避免層層查詢。

      resolve.modules告訴webpack去哪些目錄下尋找第三方模組,預設值為[`node_modules`],會依次查詢./node_modules、../node_modules、../../node_modules。

    2. 設定resolve.mainFields:[`main`],設定儘量少的值可以減少入口檔案的搜尋步驟

      第三方模組為了適應不同的使用環境,會定義多個入口檔案,mainFields定義使用第三方模組的哪個入口檔案,由於大多數第三方模組都使用main欄位描述入口檔案的位置,所以可以設定單獨一個main值,減少搜尋

    3. 對龐大的第三方模組設定resolve.alias, 使webpack直接使用庫的min檔案,避免庫內解析

      如對於react:

      resolve.alias:{
      	`react`:patch.resolve(__dirname, `./node_modules/react/dist/react.min.js`)
      }
      複製程式碼

      這樣會影響Tree-Shaking,適合對整體性比較強的庫使用,如果是像lodash這類工具類的比較分散的庫,比較適合Tree-Shaking,避免使用這種方式。

    4. 合理配置resolve.extensions,減少檔案查詢

      預設值:extensions:[`.js`, `.json`],當匯入語句沒帶檔案字尾時,Webpack會根據extensions定義的字尾列表進行檔案查詢,所以:

      • 列表值儘量少
      • 頻率高的檔案型別的字尾寫在前面
      • 原始碼中的匯入語句儘可能的寫上檔案字尾,如require(./data)要寫成require(./data.json)
  2. module.noParse欄位告訴Webpack不必解析哪些檔案,可以用來排除對非模組化庫檔案的解析

    如jQuery、ChartJS,另外如果使用resolve.alias配置了react.min.js,則也應該排除解析,因為react.min.js經過構建,已經是可以直接執行在瀏覽器的、非模組化的檔案了。noParse值可以是RegExp、[RegExp]、function

    module:{ noParse:[/jquery|chartjs/, /react.min.js$/] }

  3. 配置loader時,通過test、exclude、include縮小搜尋範圍

1.2 使用DllPlugin減少基礎模組編譯次數

DllPlugin動態連結庫外掛,其原理是把網頁依賴的基礎模組抽離出來打包到dll檔案中,當需要匯入的模組存在於某個dll中時,這個模組不再被打包,而是去dll中獲取。**為什麼會提升構建速度呢?**原因在於dll中大多包含的是常用的第三方模組,如react、react-dom,所以只要這些模組版本不升級,就只需被編譯一次。我認為這樣做和配置resolve.alias和module.noParse的效果有異曲同工的效果。

使用方法:

  1. 使用DllPlugin配置一個webpack_dll.config.js來構建dll檔案:

    // webpack_dll.config.js
    const path = require(`path`);
    const DllPlugin = require(`webpack/lib/DllPlugin`);
    module.exports = {
     entry:{
         react:[`react`,`react-dom`],
         polyfill:[`core-js/fn/promise`,`whatwg-fetch`]
     },
     output:{
         filename:`[name].dll.js`,
         path:path.resolve(__dirname, `dist`),
         library:`_dll_[name]`,  //dll的全域性變數名
     },
     plugins:[
         new DllPlugin({
             name:`_dll_[name]`,  //dll的全域性變數名
             path:path.join(__dirname,`dist`,`[name].manifest.json`),//描述生成的manifest檔案
         })
     ]
    }
    複製程式碼

    需要注意DllPlugin的引數中name值必須和output.library值保持一致,並且生成的manifest檔案中會引用output.library值。

    最終構建出的檔案:

     |-- polyfill.dll.js
     |-- polyfill.manifest.json
     |-- react.dll.js
     └── react.manifest.json
    複製程式碼

    其中xx.dll.js包含打包的n多模組,這些模組存在一個陣列裡,並以陣列索引作為ID,通過一個變數假設為_xx_dll暴露在全域性中,可以通過window._xx_dll訪問這些模組。xx.manifest.json檔案描述dll檔案包含哪些模組、每個模組的路徑和ID。然後再在專案的主config檔案裡使用DllReferencePlugin外掛引入xx.manifest.json檔案。

  2. 在主config檔案裡使用DllReferencePlugin外掛引入xx.manifest.json檔案:

    //webpack.config.json
    const path = require(`path`);
    const DllReferencePlugin = require(`webpack/lib/DllReferencePlugin`);
    module.exports = {
        entry:{ main:`./main.js` },
        //... 省略output、loader等的配置
        plugins:[
            new DllReferencePlugin({
                manifest:require(`./dist/react.manifest.json`)
            }),
            new DllReferenctPlugin({
                manifest:require(`./dist/polyfill.manifest.json`)
            })
        ]
    }
    複製程式碼

    最終構建生成main.js

1.3 使用HappyPack開啟多程式Loader轉換

在整個構建流程中,最耗時的就是Loader對檔案的轉換操作了,而執行在Node.js之上的Webpack是單執行緒模型的,也就是隻能一個一個檔案進行處理,不能並行處理。HappyPack可以將任務分解給多個子程式,最後將結果發給主程式。JS是單執行緒模型,只能通過這種多程式的方式提高效能。

HappyPack使用如下:

npm i -D happypack
// webpack.config.json
const path = require(`path`);
const HappyPack = require(`happypack`);

module.exports = {
    //...
    module:{
        rules:[{
                test:/.js$/,
                use:[`happypack/loader?id=babel`]
                exclude:path.resolve(__dirname, `node_modules`)
            },{
                test:/.css/,
                use:[`happypack/loader?id=css`]
            }],
        plugins:[
            new HappyPack({
                id:`babel`,
                loaders:[`babel-loader?cacheDirectory`]
            }),
            new HappyPack({
                id:`css`,
                loaders:[`css-loader`]
            })
        ]
    }
}
複製程式碼

除了id和loaders,HappyPack還支援這三個引數:threads、verbose、threadpool,threadpool代表共享程式池,即多個HappyPack例項都用同個程式池中的子程式處理任務,以防資源佔用過多。

1.4 使用ParallelUglifyPlugin開啟多程式壓縮JS檔案

使用UglifyJS外掛壓縮JS程式碼時,需要先將程式碼解析成Object表示的AST(抽象語法樹),再去應用各種規則去分析和處理AST,所以這個過程計算量大耗時較多。ParallelUglifyPlugin可以開啟多個子程式,每個子程式使用UglifyJS壓縮程式碼,可以並行執行,能顯著縮短壓縮時間。

使用也很簡單,把原來的UglifyJS外掛換成本外掛即可,使用如下:

npm i -D webpack-parallel-uglify-plugin

// webpack.config.json
const ParallelUglifyPlugin = require(`wbepack-parallel-uglify-plugin`);
//...
plugins: [
    new ParallelUglifyPlugin({
        uglifyJS:{
            //...這裡放uglifyJS的引數
        },
        //...其他ParallelUglifyPlugin的引數,設定cacheDir可以開啟快取,加快構建速度
    })
]
複製程式碼

二、優化開發體驗

開發過程中修改原始碼後,需要自動構建和重新整理瀏覽器,以檢視效果。這個過程可以使用Webpack實現自動化,Webpack負責監聽檔案的變化,DevServer負責重新整理瀏覽器。

2.1 使用自動重新整理

2.1.1 Webpack監聽檔案

Webpack可以使用兩種方式開啟監聽:1. 啟動webpack時加上–watch引數;2. 在配置檔案中設定watch:true。此外還有如下配置引數。合理設定watchOptions可以優化監聽體驗。

module.exports = {
    watch: true,
    watchOptions: {
        ignored: /node_modules/,
        aggregateTimeout: 300,  //檔案變動後多久發起構建,越大越好
        poll: 1000,  //每秒詢問次數,越小越好
    }
}
複製程式碼

ignored:設定不監聽的目錄,排除node_modules後可以顯著減少Webpack消耗的記憶體

aggregateTimeout:檔案變動後多久發起構建,避免檔案更新太快而造成的頻繁編譯以至卡死,越大越好

poll:通過向系統輪詢檔案是否變化來判斷檔案是否改變,poll為每秒詢問次數,越小越好

2.1.2 DevServer重新整理瀏覽器

DevServer重新整理瀏覽器有兩種方式

  1. 向網頁中注入代理客戶端程式碼,通過客戶端發起重新整理
  2. 向網頁裝入一個iframe,通過重新整理iframe實現重新整理效果

預設情況下,以及 devserver: {inline:true} 都是採用第一種方式重新整理頁面。第一種方式DevServer因為不知道網頁依賴哪些Chunk,所以會向每個chunk中都注入客戶端程式碼,當要輸出很多chunk時,會導致構建變慢。而一個頁面只需要一個客戶端,所以關閉inline模式可以減少構建時間,chunk越多提升月明顯。關閉方式:

  1. 啟動時使用webpack-dev-server –inline false
  2. 配置 devserver:{inline:false}

關閉inline後入口網址變為http://localhost:8080/webpack-dev-server/

另外devServer.compress 引數可配置是否採用Gzip壓縮,預設為false

2.2 開啟模組熱替換HMR

模組熱替換不重新整理整個網頁而只重新編譯發生變化的模組,並用新模組替換老模組,所以預覽反應更快,等待時間更少,同時不重新整理頁面能保留當前網頁的執行狀態。原理也是向每一個chunk中注入代理客戶端來連線DevServer和網頁。開啟方式:

  1. webpack-dev-server –hot
  2. 使用HotModuleReplacementPlugin,比較麻煩

開啟後如果修改子模組就可以實現區域性重新整理,但如果修改的是根JS檔案,會整頁重新整理,原因在於,子模組更新時,事件一層層向上傳遞,直到某層的檔案接收了當前變化的模組,然後執行回撥函式。如果一層層向外拋直到最外層都沒有檔案接收,就會重新整理整頁。

使用 NamedModulesPlugin 可以使控制檯列印出被替換的模組的名稱而非數字ID,另外同webpack監聽,忽略node_modules目錄的檔案可以提升效能。

三、優化輸出質量-壓縮檔案體積

3.1 區分環境–減小生產環境程式碼體積

程式碼執行環境分為開發環境和生產環境,程式碼需要根據不同環境做不同的操作,許多第三方庫中也有大量的根據開發環境判斷的if else程式碼,構建也需要根據不同環境輸出不同的程式碼,所以需要一套機制可以在原始碼中區分環境,區分環境之後可以使輸出的生產環境的程式碼體積減小。Webpack中使用DefinePlugin外掛來定義配置檔案適用的環境。

const DefinePlugin = require(`webpack/lib/DefinePlugin`);
//...
plugins:[
    new DefinePlugin({
        `process.env`: {
            NODE_ENV: JSON.stringify(`production`)
        }
    })
]
複製程式碼

注意,JSON.stringify(`production`) 的原因是,環境變數值需要一個雙引號包裹的字串,而stringify後的值是`"production"`

然後就可以在原始碼中使用定義的環境:

if(process.env.NODE_ENV === `production`){
    console.log(`你在生產環境`)
    doSth();
}else{
    console.log(`你在開發環境`)
    doSthElse();
}
複製程式碼

當程式碼中使用了process時,Webpack會自動打包進process模組的程式碼以支援非Node.js的執行環境,這個模組的作用是模擬Node.js中的process,以支援process.env.NODE_ENV === `production` 語句。

3.2 壓縮程式碼-JS、ES、CSS

  1. 壓縮JS:Webpack內建UglifyJS外掛、ParallelUglifyPlugin

    會分析JS程式碼語法樹,理解程式碼的含義,從而做到去掉無效程式碼、去掉日誌輸入程式碼、縮短變數名等優化。常用配置引數如下:

    const UglifyJSPlugin = require(`webpack/lib/optimize/UglifyJsPlugin`);
    //...
    plugins: [
        new UglifyJSPlugin({
            compress: {
                warnings: false,  //刪除無用程式碼時不輸出警告
                drop_console: true,  //刪除所有console語句,可以相容IE
                collapse_vars: true,  //內嵌已定義但只使用一次的變數
                reduce_vars: true,  //提取使用多次但沒定義的靜態值到變數
            },
            output: {
                beautify: false, //最緊湊的輸出,不保留空格和製表符
                comments: false, //刪除所有註釋
            }
        })
    ]
    複製程式碼

    使用webpack --optimize-minimize 啟動webpack,可以注入預設配置的UglifyJSPlugin

  2. 壓縮ES6:第三方UglifyJS外掛

    隨著越來越多的瀏覽器支援直接執行ES6程式碼,應儘可能的執行原生ES6,這樣比起轉換後的ES5程式碼,程式碼量更少,且ES6程式碼效能更好。直接執行ES6程式碼時,也需要程式碼壓縮,第三方的uglify-webpack-plugin提供了壓縮ES6程式碼的功能:

    npm i -D uglify-webpack-plugin@beta //要使用最新版本的外掛
    //webpack.config.json
    const UglifyESPlugin = require(`uglify-webpack-plugin`);
    //...
    plugins:[
        new UglifyESPlugin({
            uglifyOptions: {  //比UglifyJS多巢狀一層
                compress: {
                    warnings: false,
                    drop_console: true,
                    collapse_vars: true,
                    reduce_vars: true
                },
                output: {
                    beautify: false,
                    comments: false
                }
            }
        })
    ]
    複製程式碼

    另外要防止babel-loader轉換ES6程式碼,要在.babelrc中去掉babel-preset-env,因為正是babel-preset-env負責把ES6轉換為ES5。

  3. 壓縮CSS:css-loader?minimize、PurifyCSSPlugin

    cssnano基於PostCSS,不僅是刪掉空格,還能理解程式碼含義,例如把color:#ff0000 轉換成 color:red,css-loader內建了cssnano,只需要使用 css-loader?minimize 就可以開啟cssnano壓縮。

    另外一種壓縮CSS的方式是使用PurifyCSSPlugin,需要配合 extract-text-webpack-plugin 使用,它主要的作用是可以去除沒有用到的CSS程式碼,類似JS的Tree Shaking。

3.3 使用Tree Shaking剔除JS死程式碼

Tree Shaking可以剔除用不上的死程式碼,它依賴ES6的import、export的模組化語法,最先在Rollup中出現,Webpack 2.0將其引入。適合用於Lodash、utils.js等工具類較分散的檔案。它正常工作的前提是程式碼必須採用ES6的模組化語法,因為ES6模組化語法是靜態的(在匯入、匯出語句中的路徑必須是靜態字串,且不能放入其他程式碼塊中)。如果採用了ES5中的模組化,例如module.export = {…}、require( x+y )、if (x) { require( `./util` ) },則Webpack無法分析出可以剔除哪些程式碼。

啟用Tree Shaking:

  1. 修改.babelrc以保留ES6模組化語句:

    {
        "presets": [
            [
                "env", 
                { "module": false },   //關閉Babel的模組轉換功能,保留ES6模組化語法
            ]
        ]
    }
    複製程式碼
  2. 啟動webpack時帶上 –display-used-exports可以在shell列印出關於程式碼剔除的提示

  3. 使用UglifyJSPlugin,或者啟動時使用–optimize-minimize

  4. 在使用第三方庫時,需要配置 resolve.mainFields: [`jsnext:main`, `main`] 以指明解析第三方庫程式碼時,採用ES6模組化的程式碼入口

四、優化輸出質量–加速網路請求

4.1 使用CDN加速靜態資源載入

  1. CND加速的原理

    CDN通過將資源部署到世界各地,使得使用者可以就近訪問資源,加快訪問速度。要接入CDN,需要把網頁的靜態資源上傳到CDN服務上,在訪問這些資源時,使用CDN服務提供的URL。

    由於CDN會為資源開啟長時間的快取,例如使用者從CDN上獲取了index.html,即使之後替換了CDN上的index.html,使用者那邊仍會在使用之前的版本直到快取時間過期。業界做法:

    • HTML檔案:放在自己的伺服器上且關閉快取,不接入CDN
    • 靜態的JS、CSS、圖片等資源:開啟CDN和快取,同時檔名帶上由內容計算出的Hash值,這樣只要內容變化hash就會變化,檔名就會變化,就會被重新下載而不論快取時間多長。

    另外,HTTP1.x版本的協議下,瀏覽器會對於向同一域名並行發起的請求數限制在4~8個。那麼把所有靜態資源放在同一域名下的CDN服務上就會遇到這種限制,所以可以把他們分散放在不同的CDN服務上,例如JS檔案放在js.cdn.com下,將CSS檔案放在css.cdn.com下等。這樣又會帶來一個新的問題:增加了域名解析時間,這個可以通過dns-prefetch來解決 <link rel=`dns-prefetch` href=`//js.cdn.com`> 來縮減域名解析的時間。形如**//xx.com 這樣的URL省略了協議**,這樣做的好處是,瀏覽器在訪問資源時會自動根據當前URL採用的模式來決定使用HTTP還是HTTPS協議。

  2. 總之,構建需要滿足以下幾點:

    • 靜態資源匯入的URL要變成指向CDN服務的絕對路徑的URL
    • 靜態資源的檔名需要帶上根據內容計算出的Hash值
    • 不同型別資源放在不同域名的CDN上
  3. 最終配置:

    const ExtractTextPlugin = require(`extract-text-webpack-plugin`);
    const {WebPlugin} = require(`web-webpack-plugin`);
    //...
    output:{
     filename: `[name]_[chunkhash:8].js`,
     path: path.resolve(__dirname, `dist`),
     publicPatch: `//js.cdn.com/id/`, //指定存放JS檔案的CDN地址
    },
    module:{
     rules:[{
         test: /.css/,
         use: ExtractTextPlugin.extract({
             use: [`css-loader?minimize`],
             publicPatch: `//img.cdn.com/id/`, //指定css檔案中匯入的圖片等資源存放的cdn地址
         }),
     },{
        test: /.png/,
        use: [`file-loader?name=[name]_[hash:8].[ext]`], //為輸出的PNG檔名加上Hash值 
     }]
    },
    plugins:[
      new WebPlugin({
         template: `./template.html`,
         filename: `index.html`,
         stylePublicPath: `//css.cdn.com/id/`, //指定存放CSS檔案的CDN地址
      }),
     new ExtractTextPlugin({
         filename:`[name]_[contenthash:8].css`, //為輸出的CSS檔案加上Hash
     })
    ]
    複製程式碼

4.2 多頁面應用提取頁面間公共程式碼,以利用快取

  1. 原理

    大型網站通常由多個頁面組成,每個頁面都是一個獨立的單頁應用,多個頁面間肯定會依賴同樣的樣式檔案、技術棧等。如果不把這些公共檔案提取出來,那麼每個單頁打包出來的chunk中都會包含公共程式碼,相當於要傳輸n份重複程式碼。如果把公共檔案提取出一個檔案,那麼當使用者訪問了一個網頁,載入了這個公共檔案,再訪問其他依賴公共檔案的網頁時,就直接使用檔案在瀏覽器的快取,這樣公共檔案就只用被傳輸一次。

  2. 應用方法

    1. 把多個頁面依賴的公共程式碼提取到common.js中,此時common.js包含基礎庫的程式碼

      const CommonsChunkPlugin = require(`webpack/lib/optimize/CommonsChunkPlugin`);
      //...
      plugins:[
          new CommonsChunkPlugin({
              chunks:[`a`,`b`], //從哪些chunk中提取
              name:`common`,  // 提取出的公共部分形成一個新的chunk
          })
      ]
      複製程式碼
    2. 找出依賴的基礎庫,寫一個base.js檔案,再與common.js提取公共程式碼到base中,common.js就剔除了基礎庫程式碼,而base.js保持不變

      //base.js
      import `react`;
      import `react-dom`;
      import `./base.css`;
      //webpack.config.json
      entry:{
          base: `./base.js`
      },
      plugins:[
          new CommonsChunkPlugin({
              chunks:[`base`,`common`],
              name:`base`,
              //minChunks:2, 表示檔案要被提取出來需要在指定的chunks中出現的最小次數,防止common.js中沒有程式碼的情況
          })        
      ]
      複製程式碼
    3. 得到基礎庫程式碼base.js,不含基礎庫的公共程式碼common.js,和頁面各自的程式碼檔案xx.js。

      頁面引用順序如下:base.js–> common.js–> xx.js

4.3 分割程式碼以按需載入

  1. 原理

    單頁應用的一個問題在於使用一個頁面承載複雜的功能,要載入的檔案體積很大,不進行優化的話會導致首屏載入時間過長,影響使用者體驗。做按需載入可以解決這個問題。具體方法如下:

    1. 將網站功能按照相關程度劃分成幾類
    2. 每一類合併成一個Chunk,按需載入對應的Chunk
    3. 例如,只把首屏相關的功能放入執行入口所在的Chunk,這樣首次載入少量的程式碼,其他程式碼要用到的時候再去載入。最好提前預估使用者接下來的操作,提前載入對應程式碼,讓使用者感知不到網路載入
  2. 做法

    一個最簡單的例子:網頁首次只載入main.js,網頁展示一個按鈕,點選按鈕時載入分割出去的show.js,載入成功後執行show.js裡的函式

    //main.js
    document.getElementById(`btn`).addEventListener(`click`,function(){
        import(/* webpackChunkName:"show" */ `./show`).then((show)=>{
            show(`Webpack`);
        })
    })
    //show.js
    module.exports = function (content) {
        window.alert(`Hello ` + content);
    }
    複製程式碼

    import(/* webpackChunkName:show */ `./show`).then() 是實現按需載入的關鍵,Webpack內建對import( *)語句的支援,Webpack會以./show.js為入口重新生成一個Chunk。程式碼在瀏覽器上執行時只有點選了按鈕才會開始載入show.js,且import語句會返回一個Promise,載入成功後可以在then方法中獲取載入的內容。這要求瀏覽器支援Promise API,對於不支援的瀏覽器,需要注入Promise polyfill。/* webpackChunkName:show */ 是定義動態生成的Chunk的名稱,預設名稱是[id].js,定義名稱方便除錯程式碼。為了正確輸出這個配置的ChunkName,還需要配置Webpack:

    //...
    output:{
        filename:`[name].js`,
        chunkFilename:`[name].js`, //指定動態生成的Chunk在輸出時的檔名稱
    }
    複製程式碼

    書中另外提供了更復雜的React-Router中非同步載入元件的實戰場景。P212

五、優化輸出質量–提升程式碼執行時的效率

5.1 使用Prepack提前求值

  1. 原理:

    Prepack是一個部分求值器,編譯程式碼時提前將計算結果放到編譯後的程式碼中,而不是在程式碼執行時才去求值。通過在便一階段預先執行原始碼來得到執行結果,再直接將執行結果輸出以提升效能。但是現在Prepack還不夠成熟,用於線上環境還為時過早。

  2. 使用方法

    const PrepackWebpackPlugin = require(`prepack-webpack-plugin`).default;
    module.exports = {
        plugins:[
            new PrepackWebpackPlugin()
        ]
    }
    複製程式碼

5.2 使用Scope Hoisting

  1. 原理

    譯作“作用域提升”,是在Webpack3中推出的功能,它分析模組間的依賴關係,儘可能將被打散的模組合併到一個函式中,但不能造成程式碼冗餘,所以只有被引用一次的模組才能被合併。由於需要分析模組間的依賴關係,所以原始碼必須是採用了ES6模組化的,否則Webpack會降級處理不採用Scope Hoisting。

  2. 使用方法

    const ModuleConcatenationPlugin = require(`webpack/lib/optimize/ModuleConcatenationPlugin`);
    //...
    plugins:[
        new ModuleConcatenationPlugin();
    ],
    resolve:{
    	mainFields:[`jsnext:main`,`browser`,`main`]
    }
    複製程式碼

    webpack --display-optimization-bailout 輸出日誌中會提示哪個檔案導致了降級處理

六、使用輸出分析工具

啟動Webpack時帶上這兩個引數可以生成一個json檔案,輸出分析工具大多依賴該檔案進行分析:

webpack --profile --json > stats.json 其中 --profile 記錄構建過程中的耗時資訊,--json 以JSON的格式輸出構建結果,>stats.json 是UNIX / Linux系統中的管道命令,含義是將內容通過管道輸出到stats.json檔案中。

  1. 官方工具Webpack Analyse

    開啟該工具的官網http://webpack.github.io/analyse/上傳stats.json,就可以得到分析結果

  2. webpack-bundle-analyzer

    視覺化分析工具,比Webapck Analyse更直觀。使用也很簡單:

    1. npm i -g webpack-bundle-analyzer安裝到全域性
    2. 按照上面方法生成stats.json檔案
    3. 在專案根目錄執行webpack-bundle-analyzer ,瀏覽器會自動開啟結果分析頁面。

七、其他Tips

  1. 配置babel-loader時,use: [‘babel-loader?cacheDirectory’] cacheDirectory用於快取babel的編譯結果,加快重新編譯的速度。另外注意排除node_modules資料夾,因為檔案都使用了ES5的語法,沒必要再使用Babel轉換。

  2. 配置externals,排除因為已使用<script>標籤引入而不用打包的程式碼,noParse是排除沒使用模組化語句的程式碼。

  3. 配置performance引數可以輸出檔案的效能檢查配置。

  4. 配置profile:true,是否捕捉Webpack構建的效能資訊,用於分析是什麼原因導致構建效能不佳。

  5. 配置cache:true,是否啟用快取來提升構建速度。

  6. 可以使用url-loader把小圖片轉換成base64嵌入到JS或CSS中,減少載入次數。

  7. 通過imagemin-webpack-plugin壓縮圖片,通過webpack-spritesmith製作雪碧圖。

  8. 開發環境下將devtool設定為cheap-module-eval-source-map,因為生成這種source map的速度最快,能加速構建。在生產環境下將devtool設定為hidden-source-map

相關文章