home > tools > Bundler > webpack >

再談webpack性能優化

author:[email protected]    hits:

webpack打包優化的重點回顧,一些零星知識的整理,也歡迎大家補充。

優化構建速度

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

減少目錄檢索范圍

1)resolve字段告訴webpack怎么去搜索文件,所以首先要重視resolve字段的配置

  • 設置resolve.modules:[path.resolve(__dirname, 'node_modules')]避免層層查找

    resolve.modules告訴webpack去哪些目錄下尋找第三方模塊,默認值為['node_modules'],會依次查找./node_modules、../node_modules、../../node_modules

  • 設置resolve.mainFields:['main'],設置盡量少的值可以減少入口文件的搜索步驟

    第三方模塊為了適應不同的使用環境,會定義多個入口文件,mainFields定義使用第三方模塊的哪個入口文件,由于大多數第三方模塊都使用main字段描述入口文件的位置,所以可以設置單獨一個main值,減少搜索

  • 對龐大的第三方模塊設置resolve.alias, 使webpack直接使用庫的min文件,避免庫內解析

    如對于react:resolve.alias:{'react':patch.resolve(__dirname, './node_modules/react/dist/react.min.js')}

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

  • 合理配置resolve.extensions,減少文件查找

    默認值:extensions:['.js', '.json'],當導入語句沒帶文件后綴時,Webpack會根據extensions定義的后綴列表進行文件查找,所以:

    1. 列表值盡量少

    2. 頻率高的文件類型的后綴寫在前面

    3. 源碼中的導入語句盡可能的寫上文件后綴,如require(./data)要寫成require(./data.json)

resolve.alias 可以配置 webpack 模塊解析的別名,對于比較深的解析路徑,可以對其配置 alias. 可以提升 webpack 的構建速度。

alias: {

Utilities: path.resolve(__dirname, 'src/utilities/'),

Templates:path.resolve(__dirname, 'src/templates/')}

在實際項目開發過程中,我們并不需要實時調試各種庫的源碼,這時候就可以考慮使用external選項

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縮小搜索范圍,減少 loader 遍歷的目錄范圍,從而加快 Webpack 編譯速度。

比如指定 babel-loader 只處理業務代碼:

{ test: /\.js$/,use: ['babel-loader'],include: path.join(__dirname, 'app')}

推薦閱讀《webpack之前端性能優化


利用 DllPlugin 和 DllReferencePlugin 預編譯資源模塊

把改變頻率比較小的第三方庫等依賴單獨打包構建,在打包整個項目的時候,如果解析到了通過 Dll 形式進行打包的依賴,會在正常的打包過程中跳過,同時把對這些依賴的引入導入到 Dll 模塊上去。 這樣會大大提升在對業務代碼進行打包時候的速度。

  • 使用DllPlugin配置一個webpack_dll.config.js來構建dll文件:

  • 在主config文件里使用DllReferencePlugin插件引入xx.manifest.json文件:

相對于externals,dllPlugin有如下幾點優勢:

  1. dll預編譯出來的模塊可以作為靜態資源鏈接庫可被重復使用,尤其適合多個項目之間的資源共享,如同一個站點pc和手機版等;

  2. dll資源能有效地解決資源循環依賴的問題,部分依賴庫如:react-addons-css-transition-group這種原先從react核心庫中抽取的資源包,整個代碼只有一句話:

    module.exports = require('react/lib/ReactCSSTransitionGroup');

  3. 卻因為重新指向了react/lib中,這也會導致在通過externals引入的資源只能識別react,尋址解析react/lib則會出現無法被正確索引的情況。

  4. 由于externals的配置項需要對每個依賴庫進行逐個定制,所以每次增加一個組件都需要手動修改,略微繁瑣,而通過dllPlugin則能完全通過配置讀取,減少維護的成本;

利用多線程優化編譯速度

webpack中為了方便各種資源和類型的加載,設計了以loader加載器的形式讀取資源,但是受限于node的編程模型影響,所有的loader雖然以async的形式來并發調用,但是還是運行在單個 node的進程以及在同一個事件循環中,這就直接導致了當我們需要同時讀取多個loader文件資源時,比如babel-loader需要transform各種jsx,es6的資源文件。在這種同步計算同時需要大量耗費cpu運算的過程中,node的單進程模型就無優勢了,那么happypack就針對解決此類問題而生。

  • 開啟happypack的線程池——推薦閱讀《happypack 原理解析

  • 使用ParallelUglifyPlugin開啟多進程壓縮JS文件

自動刷新優化

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

module.exports = {

    watch: true,

    watchOptions: {

        ignored: /node_modules/,//設置不監聽的目錄,排除node_modules后可以顯著減少Webpack消耗的內存

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

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

    }

}

DevServer刷新瀏覽器有兩種方式:

  • 向網頁中注入代理客戶端代碼,通過客戶端發起刷新

  • 向網頁裝入一個iframe,通過刷新iframe實現刷新效果


開啟模塊熱替換HMR

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


webpack-dev-server --hot

使用HotModuleReplacementPlugin,比較麻煩

開啟后如果修改子模塊就可以實現局部刷新,但如果修改的是根JS文件,會整頁刷新,原因在于,子模塊更新時,事件一層層向上傳遞,直到某層的文件接收了當前變化的模塊,然后執行回調函數。如果一層層向外拋直到最外層都沒有文件接收,就會刷新整頁。


使用 NamedModulesPlugin 可以使控制臺打印出被替換的模塊的名稱而非數字ID,另外同webpack監聽,忽略node_modules目錄的文件可以提升性能。

Build Cache

Webpack 和一些 Plugin/Loader 都有 Cache 選項。開啟 Cache 選項,有利用提高構建性能。

比如:使用 babel-loader 的時候開啟 cacheDirectory 選項,會較為明顯的提升構建速度

選擇合適的 Devtool 版本

webpack 的 devtool 配置,決定了在構建過程中怎樣生成 sourceMap 文件。通常來說eval的性能最高,但是不能生成的 sourceMap 文件解析出來的代碼,和源代碼差異較大。 source-map 的性能較差,但是可以生成原始版本的代碼。 在大多數 Development 場景下 cheap-module-eval-source-map 是最佳的選擇。


合理配置 CommonsChunkPlugin

webpack的資源入口通常是以entry為單元進行編譯提取,那么當多entry共存的時候,CommonsChunkPlugin的作用就會發揮出來,對所有依賴的chunk進行公共部分的提取,但是在這里可能很多人會誤認為抽取公共部分指的是能抽取某個代碼片段,其實并非如此,它是以module為單位進行提取。


假設我們的頁面中存在entry1,entry2,entry3三個入口,這些入口中可能都會引用如utils,loadash,fetch等這些通用模塊,那么就可以考慮對這部分的共用部分機提取。通常提取方式有如下四種實現:

1、傳入字符串參數,由chunkplugin自動計算提取

new webpack.optimize.CommonsChunkPlugin('common.js')

這種做法默認會把所有入口節點的公共代碼提取出來, 生成一個common.js

2、有選擇的提取公共代碼

new webpack.optimize.CommonsChunkPlugin('common.js',['entry1','entry2']);

只提取entry1節點和entry2中的共用部分模塊, 生成一個common.js

3、將entry下所有的模塊的公共部分(可指定引用次數)提取到一個通用的chunk中

new webpack.optimize.CommonsChunkPlugin({

    name: 'vendors',

    minChunks: function (module, count) {

       return (

          module.resource &&

          /\.js$/.test(module.resource) &&

          module.resource.indexOf(

            path.join(__dirname, '../node_modules')

          ) === 0

       )

    }

});

提取所有node_modules中的模塊至vendors中,也可以指定minChunks中的最小引用數;

4、抽取enry中的一些lib抽取到vendors中

entry = {

    vendors: ['fetch', 'loadash']

};

new webpack.optimize.CommonsChunkPlugin({

    name: "vendors",

    minChunks: Infinity

});

添加一個entry名叫為vendors,并把vendors設置為所需要的資源庫,CommonsChunk會自動提取指定庫至vendors中。

配置profile:true,用于分析是什么原因導致構建性能不佳

其次,使用 webpack-visualizerwebpack-bundle-analyzer進行分析

推薦閱讀《三十分鐘掌握Webpack性能優化


使用Scope Hoisting

通過分析模塊間的依賴關系,盡可能將被打散的模塊合并到一個函數中,但不能造成代碼冗余,所以只有被引用一次的模塊才能被合并。由于需要分析模塊間的依賴關系,所以源碼必須是采用了ES6模塊化的,否則Webpack會降級處理不采用Scope Hoisting。

使用方法

const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');

//...

plugins:[

    new ModuleConcatenationPlugin();

]

拆分頁面

webpack4不僅可以生成單個html文件,也可以生成多個,并且給每個html文件配置不同的JS,具體配置如下:

 plugins: [

        new HtmlWebpackPlugin({

            filename : 'index.html', //生成的文件名稱

            chunks : ['index'], //加入的js文件,若無此屬性,則默認為所有js

            hash : true, //生成hash數值,避免產生緩存

            title : '實際標題', //html的title標簽值

            template : './src/index.html' //模板文件路徑

        }),

        new HtmlWebpackPlugin({

            filename : 'main.html',

            hash : true,

            title : '實際標題',

            template : './src/index.html'

        })

    ]


參考文章:

優化Webpack構建性能的幾點建議

webpack 構建性能優化策略小結



轉載本站文章《再談webpack性能優化》, 請注明出處:http://www.qsexmk.tw/html/tools/Bundler/webpack/2016_0218_7492.html