Webpack是個很流行的打包工具,但其打包速度卻一直被吐槽著 如果不用上一些打包的優化建議,單單打包兩三個文件就能花上好幾秒,放上幾十個入口文件依賴幾百上千個包的話,幾分鐘十幾分鐘妥妥的 本文整理了常見的一些方法,部分使用之後就看到了很大改善,部分沒什麼明顯的變化,也可能是項目規模還不夠大,先記錄 ...
Webpack是個很流行的打包工具,但其打包速度卻一直被吐槽著
如果不用上一些打包的優化建議,單單打包兩三個文件就能花上好幾秒,放上幾十個入口文件依賴幾百上千個包的話,幾分鐘十幾分鐘妥妥的
本文整理了常見的一些方法,部分使用之後就看到了很大改善,部分沒什麼明顯的變化,也可能是項目規模還不夠大,先記錄一下方法也好
一、使用監聽模式或熱更新熱替換
webpack支持監聽模式,此時需要重新編譯時就可以進行增量構建,增量構建是很快的,基本不到一秒或幾秒之內就能重新編譯好
註意區分一下開發環境和線上環境,開發環境啟用熱更新替換
// 開發環境設置本地伺服器,實現熱更新 devServer: { contentBase: path.resolve(__dirname, 'static'), // 提供給外部訪問 host: '0.0.0.0', port: 8388, // 允許開發伺服器訪問本地伺服器的包JSON文件,防止跨域 headers: { 'Access-Control-Allow-Origin': '*' }, // 設置熱替換 hot: true, // 設置頁面引入 inline: true }, // 文件輸出配置 output: { // 設置路徑,防止訪問本地伺服器相關資源時,被開發伺服器認為是相對其的路徑 publicPath: 'http://localhost:8188/dist/js/', }, // 插件配置 plugins: [ // 熱更新替換 new webpack.HotModuleReplacementPlugin() ]
線上環境的編譯,加個 --watch 參數就可以了
二、開發環境不做無意義的操作
很多配置,在開發階段是不需要去做的,我們可以區分出開發和線上的兩套配置,這樣在需要上線的時候再全量編譯即可
比如說 代碼壓縮、目錄內容清理、計算文件hash、提取CSS文件等
三、選擇一個合適的devtool屬性值
配置devtool可以支持使用sourceMap,但有些是耗時嚴重的,這個得多試試
四、代碼壓縮用ParallelUglifyPlugin代替自帶的 UglifyJsPlugin插件
自帶的JS壓縮插件是單線程執行的,而webpack-parallel-uglify-plugin可以並行的執行,在我的小demo中使用後,速度直接從25s變成了14s
new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } }), ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin') new ParallelUglifyPlugin({ cacheDir: '.cache/', uglifyJS:{ output: { comments: false }, compress: { warnings: false } } }),
五、css-loader使用0.15.0以下的版本
聽聞這個版本以上的速度會慢許多,不過在我的小demo中還沒看到明顯變化
六、使用fast-sass-loader代替sass-loader
fast-sass-loader可以並行地處理sass,在提交構建之前會先組織好代碼,速度也會快一些
七、babel-loader開啟緩存
babel-loader在執行的時候,可能會產生一些運行期間重覆的公共文件,造成代碼體積大冗餘,同時也會減慢編譯效率
可以加上cacheDirectory參數或使用 transform-runtime 插件試試
// webpack.config.js use: [{ loader: 'babel-loader', options: { cacheDirectory: true }] // .bablerc { "presets": [ "env", "react" ], "plugins": ["transform-runtime"] }
八、不需要打包編譯的插件庫換成全局<script>標簽引入的方式
比如jQuery插件,react, react-dom等,代碼量是很多的,打包起來可能會很耗時
可以直接用標簽引入,然後在webpack配置里使用 expose-loader 或 externals 或 ProvidePlugin 提供給模塊內部使用相應的變數
// @1 use: [{ loader: 'expose-loader', options: '$' }, { loader: 'expose-loader', options: 'jQuery' }] // @2 externals: { jquery: 'jQuery' }, // @3 new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', 'window.jQuery': 'jquery' }),
九、使用 DllPlugin 和 DllReferencePlugin
這種方式其實和externals是類似的,主要用於有些模塊沒有可以在<script>標簽中引入的資源(純npm包)
Dll是動態鏈接庫的意思,實際上就是將這些npm打包生成一個JSON文件,這個文件里包含了npm包的路徑對應信息
這兩個插件要一起用
首先,新建一個dll.config.js配置文件,先用webpack來打包這個文件
const webpack = require('webpack'); const path = require('path'); module.exports = { output: { // 將會生成./ddl/lib.js文件 path: path.resolve(__dirname, 'ddl'), filename: '[name].js', library: '[name]', }, entry: { "lib": [ 'react', 'react-dom', 'jquery' // ...其它庫 ], }, plugins: [ new webpack.DllPlugin({ // 生成的映射關係文件 path: 'manifest.json', name: '[name]', context: __dirname, }), ], };
在manifest.json文件中就是相應的包對應的信息
然後在我們的項目配置文件中配置DllReferencePlugin 使用這個清單文件
// 插件配置 plugins: [ new webpack.DllReferencePlugin({ context: __dirname, manifest: require('./manifest.json') }),
十、提取公共代碼
使用CommonsChunkPlugin提取公共的模塊,可以減少文件體積,也有助於瀏覽器層的文件緩存,還是比較推薦的
// 提取公共模塊文件 new webpack.optimize.CommonsChunkPlugin({ chunks: ['home', 'detail'], // 開發環境下需要使用熱更新替換,而此時common用chunkhash會出錯,可以直接不用hash filename: '[name].js' + (isProduction ? '?[chunkhash:8]' : ''), name: 'common' }), // 切合公共模塊的提取規則,有時後你需要明確指定預設放到公共文件的模塊 // 文件入口配置 entry: { home: './src/js/home', detail: './src/js/detail', // 提取jquery入公共文件 common: ['jquery', 'react', 'react-dom'] },
十一、使用HappyPack來加速構建
HappyPack會採用多進程去打包構建,使用方式還是蠻簡單的,但並不是支持所有的loader
首先引入,定義一下這個插件所開啟的線程,推薦是四個,其實也可以直接使用預設的就行了
HappyPack = require('happypack'), os = require('os'), happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
然後在module的規則里改動一下,引入它,其中 id是一個標識符
{ test: /\.jsx?$/, // 編譯js或jsx文件,使用babel-loader轉換es6為es5 exclude: /node_modules/, loader: 'HappyPack/loader?id=js' // use: [{ // loader: 'babel-loader', // options: { // } // }] }
然後我們調用插件,設置匹配的id,然後相關的配置可以直接把use:的規則部分套在loaders上
new HappyPack({ id: 'js', loaders: [{ loader: 'babel-loader', options: { // cacheDirectory: true } }] }),
要註意的第一點是,它對file-loader和url-loader支持不好,所以這兩個loader就不需要換成happypack了,其他loader可以類似地換一下
要註意的第二點是,使用ExtractTextWebpackPlugin提取css文件也不是完全就能轉換過來,所以需要小小的改動一下,比如
module: { rules: [{ test: /\.css$/, // loader: 'HappyPack/loader?id=css' // 提取CSS文件 use: cssExtractor.extract({ // 如果配置成不提取,則此類文件使用style-loader插入到<head>標簽中 fallback: 'style-loader', use: 'HappyPack/loader?id=css' // use: [{ // loader: 'css-loader', // options: { // // url: false, // minimize: true // } // }, // // 'postcss-loader' // ] }) }, { test: /\.scss$/, // loader: 'HappyPack/loader?id=scss' // 編譯Sass文件 提取CSS文件 use: sassExtractor.extract({ // 如果配置成不提取,則此類文件使用style-loader插入到<head>標簽中 fallback: 'style-loader', use: 'HappyPack/loader?id=scss' // use: [ // 'css-loader', // // 'postcss-loader', // { // loader: 'sass-loader', // options: { // sourceMap: true, // outputStyle: 'compressed' // } // } // ] }) }
因為它是直接函數調用的,我們就放到裡層的use規則就行了,然後配置插件即可
plugins: [ new HappyPack({ id: 'css', loaders: [{ loader: 'css-loader', options: { // url: false, minimize: true } }] }), new HappyPack({ id: 'scss', loaders: [{ 'loader': 'css-loader' }, { loader: 'fast-sass-loader', options: { sourceMap: true, outputStyle: 'compressed' } }] }),
十二、優化構建時的搜索路徑
在webpack打包時,會有各種各樣的路徑要去查詢搜索,我們可以加上一些配置,讓它搜索地更快
比如說,方便改成絕對路徑的模塊路徑就改一下,以純模塊名來引入的可以加上一些目錄路徑
還可以善於用下resolve alias別名 這個欄位來配置
還有exclude等的配置,避免多餘查找的文件,比如使用babel別忘了剔除不需要遍歷的
{ test: /\.jsx?$/, // 編譯js或jsx文件,使用babel-loader轉換es6為es5 exclude: /node_modules/, use: [{ loader: 'babel-loader', options: { } }] }
十三、(導出編譯JSON文件)理一下打包構建涉及的模塊,分析看有哪些包是不需要打包的,只打包需要的模塊
檢查一下代碼,看看是不是有不需要引入的模塊出現在代碼里
webpack編譯時加上參數 --json > stat.json 後,可以上傳到 webpack-analyse 、webpack-visualizer 等分析站點上,看看打包的模塊信息
十四、使用ModuleConcatenationPlugin插件來加快JS執行速度
這是webpack3的新特性(Scope Hoisting),其實是借鑒了Rollup打包工具來的,它將一些有聯繫的模塊,放到一個閉包函數裡面去,通過減少閉包函數數量從而加快JS的執行速度
new webpack.optimize.ModuleConcatenationPlugin({ })
十五、使用noParse
webpack打包的時候,有時不需要解析某些模塊的依賴(這些模塊並沒有依賴,或者並根本就沒有模塊化),我們可以直接加上這個參數,直接跳過這種解析
module: { noParse: /node_modules\/(jquey\.js)/ }
十六、使用非同步的模塊載入
這個算是可以減小模塊的體積吧,在一定程度上也是為用戶考慮的,使用require.ensure來設置哪些模塊需要非同步載入,webpack會將它打包到一個獨立的chunk中,
在某個時刻(比如用戶點擊了查看)才非同步地載入這個模塊來執行
$('.bg-input').click(() => { console.log('clicked, loading async.js') require.ensure([], require => { require('./components/async2').log(); require('./components/async1').log(); console.log('loading async.js done'); }); });
十七、以模塊化來引入
有些模塊是可以以模塊化來引入的,就是說可以只引入其中的一部分,比如說lodash
// 原來的引入方式 import {debounce} from 'lodash'; //按模塊化的引入方式 import debounce from 'lodash/debounce';
主要是整理過來的,試用了幾個方法,首次編譯的速度可以從之前半分多鐘減小到十秒左右了,當然,開啟了熱更新替換後簡直美不可言
當然還有很多方法沒整理出,這些方法是有使用場景的,並不是每個都需要用,需要在自己的項目中嘗試,結合配置它的複雜性和帶來的效應來權衡。