關於webpack
,相信如今的前端開發人員必定不會陌生,由於它已經成爲前端開發人員必不可少的一項技能,它的官方介紹以下:javascript
webpack 是一個模塊打包器。webpack的主要目標是將 JavaScript 文件打包在一塊兒,打包後的文件用於在瀏覽器中使用,但它也可以勝任轉換(transform)、打包(bundle)或包裹(package)任何資源(resource or asset)。css
在平常開發工做中,咱們除了會使用webpack
以及會編寫它的配置文件以外,咱們還須要瞭解一些關於webpack
性能優化的方法,這樣在實際工做就可以如虎添翼,加強自身的競爭力。html
關於webpack
優化的方法我將其分爲兩大類,以下:前端
webpack
打包速度,減小打包時間的優化方法Webpack
打出來的包體積更小的優化方法OK,廢話很少說,接下來咱們就來分別瞭解一下優化方法。vue
對於 Loader
來講,影響打包效率首當其衝必屬 Babel
了。由於 Babel
會將代碼轉爲字符串生成 AST
,而後對 AST
繼續進行轉變最後再生成新的代碼,項目越大,轉換代碼越多,效率就越低。固然了,咱們是有辦法優化的。java
首先咱們能夠優化 Loader 的文件搜索範圍,在使用loader
時,咱們能夠指定哪些文件不經過loader
處理,或者指定哪些文件經過loader
處理。node
module.exports = { module: { rules: [ { // js 文件才使用 babel test: /\.js$/, use: ['babel-loader'], // 只處理src文件夾下面的文件 include: path.resolve('src'), // 不處理node_modules下面的文件 exclude: /node_modules/ } ] } }
對於 Babel
來講,咱們確定是但願只做用在 JS
代碼上的,而後 node_modules
中使用的代碼都是編譯過的,因此咱們也徹底沒有必要再去處理一遍。react
另外,對於babel-loader
,咱們還能夠將 Babe
l 編譯過的文件緩存起來,下次只須要編譯更改過的代碼文件便可,這樣能夠大幅度加快打包時間。jquery
loader: 'babel-loader?cacheDirectory=true'
在一些性能開銷較大的 loader
以前添加 cache-loader
,以將處理結果緩存到磁盤裏,這樣下次打包能夠直接使用緩存結果而不須要從新打包。webpack
module.exports = { module: { rules: [ { // js 文件才使用 babel test: /\.js$/, use: [ 'cache-loader', ...loaders ], } ] } }
那這麼說的話,我給每一個loder
前面都加上cache-loader
,然而凡事物極必反,保存和讀取這些緩存文件會有一些時間開銷,因此請只對性能開銷較大的 loader
使用 cache-loader
。關於這個cache-loader
更詳細的使用方法請參照這裏cache-loader
受限於Node
是單線程運行的,因此 Webpack
在打包的過程當中也是單線程的,特別是在執行 Loader
的時候,長時間編譯的任務不少,這樣就會致使等待的狀況。那麼咱們可使用一些方法將 Loader
的同步執行轉換爲並行,這樣就能充分利用系統資源來提升打包速度了。
happypack ,快樂的打包。人如其名,就是可以讓Webpack
把打包任務分解給多個子線程去併發的執行,子線程處理完後再把結果發送給主線程。
module: { rules: [ { test: /\.js$/, // 把對 .js 文件的處理轉交給 id 爲 babel 的 HappyPack 實例 use: ['happypack/loader?id=babel'], exclude: path.resolve(__dirname, 'node_modules'), }, { test: /\.css$/, // 把對 .css 文件的處理轉交給 id 爲 css 的 HappyPack 實例 use: ['happypack/loader?id=css'] } ] }, plugins: [ new HappyPack({ id: 'js', //ID是標識符的意思,ID用來代理當前的happypack是用來處理一類特定的文件的 threads: 4, //你要開啓多少個子進程去處理這一類型的文件 loaders: [ 'babel-loader' ] }), new HappyPack({ id: 'css', threads: 2, loaders: [ 'style-loader', 'css-loader' ] }) ]
thread-loader ,在worker
池(worker pool)中運行加載器loader
。把thread-loader
放置在其餘 loader
以前, 放置在這個 thread-loader
以後的 loader
就會在一個單獨的 worker
池(worker pool)中運行。
module.exports = { module: { rules: [ { test: /\.js$/, include: path.resolve('src'), use: [ { loader: "thread-loader", // 有一樣配置的 loader 會共享一個 worker 池(worker pool) options: { // 產生的 worker 的數量,默認是 cpu 的核心數 workers: 2, // 一個 worker 進程中並行執行工做的數量 // 默認爲 20 workerParallelJobs: 50, // 額外的 node.js 參數 workerNodeArgs: ['--max-old-space-size', '1024'], // 閒置時定時刪除 worker 進程 // 默認爲 500ms // 能夠設置爲無窮大, 這樣在監視模式(--watch)下能夠保持 worker 持續存在 poolTimeout: 2000, // 池(pool)分配給 worker 的工做數量 // 默認爲 200 // 下降這個數值會下降整體的效率,可是會提高工做分佈更均一 poolParallelJobs: 50, // 池(pool)的名稱 // 能夠修更名稱來建立其他選項都同樣的池(pool) name: "my-pool" } }, { loader:'babel-loader' } ] } ] } }
一樣,thread-loader
也不是越多越好,也請只在耗時的loader
上使用。
在Webpack3
中,咱們通常使用 UglifyJS
來壓縮代碼,可是這個是單線程運行的,也就是說多個js
文件須要被壓縮,它須要一個個文件進行壓縮。因此說在正式環境打包壓縮代碼速度很是慢(由於壓縮JS
代碼須要先把代碼解析成AST
語法樹,再去應用各類規則分析和處理AST
,致使這個過程耗時很是大)。爲了加快效率,咱們可使用 webpack-parallel-uglify-plugin
插件,該插件會開啓多個子進程,把對多個文件壓縮的工做分別給多個子進程去完成,可是每一個子進程仍是經過UglifyJS
去壓縮代碼。無非就是變成了並行處理該壓縮了,並行處理多個子任務,提升打包效率。來並行運行 UglifyJS
,從而提升效率。
在 Webpack4
中,咱們就不須要以上這些操做了,只須要將 mode
設置爲 production
就能夠默認開啓以上功能。代碼壓縮也是咱們必作的性能優化方案,固然咱們不止能夠壓縮JS
代碼,還能夠壓縮HTML
、CSS
代碼,而且在壓縮 JS
代碼的過程當中,咱們還能夠經過配置實現好比刪除 console.log
這類代碼的功能。
let ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); module.exports = { module: {}, plugins: [ new ParallelUglifyPlugin({ workerCount:3,//開啓幾個子進程去併發的執行壓縮。默認是當前運行電腦的cPU核數減去1 uglifyJs:{ output:{ beautify:false,//不須要格式化 comments:false,//不保留註釋 }, compress:{ warnings:false,//在Uglify]s除沒有用到的代碼時不輸出警告 drop_console:true,//刪除全部的console語句,能夠兼容ie瀏覽器 collapse_vars:true,//內嵌定義了可是隻用到一次的變量 reduce_vars:true,//取出出現屢次可是沒有定義成變量去引用的靜態值 } }, }) ] }
關於該插件更加詳細的用法請參照這裏webpack-parallel-uglify-plugin
DllPlugin
能夠將特定的類庫提早打包成動態連接庫,在一個動態連接庫中能夠包含給其餘模塊調用的函數和數據,把基礎模塊獨立出來打包到單獨的動態鏈接庫裏,當須要導入的模塊在動態鏈接庫裏的時候,模塊不用再次被打包,而是去動態鏈接庫裏獲取。這種方式能夠極大的減小打包類庫的次數,只有當類庫更新版本纔有須要從新打包,而且也實現了將公共代碼抽離成單獨文件的優化方案。
這裏咱們能夠先將react
、react-dom
單獨打包成動態連接庫,首先新建一個新的webpack
配置文件:webpack.dll.js
const path = require('path'); const DllPlugin = require('webpack/lib/DllPlugin'); module.exports = { // 想統一打包的類庫 entry:['react','react-dom'], output:{ filename: '[name].dll.js', //輸出的動態連接庫的文件名稱,[name] 表明當前動態連接庫的名稱 path:path.resolve(__dirname,'dll'), // 輸出的文件都放到 dll 目錄下 library: '_dll_[name]',//存放動態連接庫的全局變量名稱,例如對應 react 來講就是 _dll_react }, plugins:[ new DllPlugin({ // 動態連接庫的全局變量名稱,須要和 output.library 中保持一致 // 該字段的值也就是輸出的 manifest.json 文件 中 name 字段的值 // 例如 react.manifest.json 中就有 "name": "_dll_react" name: '_dll_[name]', // 描述動態連接庫的 manifest.json 文件輸出時的文件名稱 path: path.join(__dirname, 'dll', '[name].manifest.json') }) ] }
而後咱們須要執行這個配置文件生成依賴文件:
webpack --config webpack.dll.js --mode development
接下來咱們須要使用 DllReferencePlugin
將依賴文件引入項目中
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin') module.exports = { // ...省略其餘配置 plugins: [ new DllReferencePlugin({ // manifest 就是以前打包出來的 json 文件 manifest:path.join(__dirname, 'dll', 'react.manifest.json') }) ] }
module.noParse
屬性,能夠用於配置那些模塊文件的內容不須要進行解析(即無依賴) 的第三方大型類庫(例如jquery
,lodash
)等,使用該屬性讓 Webpack
不掃描該文件,以提升總體的構建速度。
module.exports = { module: { noParse: /jquery|lodash/, // 正則表達式 // 或者使用函數 noParse(content) { return /jquery|lodash/.test(content) } } }
IgnorePlugin
用於忽略某些特定的模塊,讓webpack
不把這些指定的模塊打包進去。
module.exports = { // ...省略其餘配置 plugins: [ new webpack.IgnorePlugin(/^\.\/locale/,/moment$/) ] }
webpack.IgnorePlugin()
參數中第一個參數是匹配引入模塊路徑的正則表達式,第二個參數是匹配模塊的對應上下文,即所在目錄名。
webpack-bundle-analyzer
插件的功能是能夠生成代碼分析報告,幫助提高代碼質量和網站性能。
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin module.exports={ plugins: [ new BundleAnalyzerPlugin({ generateStatsFile: true, // 是否生成stats.json文件 }) // 默認配置的具體配置項 // new BundleAnalyzerPlugin({ // analyzerMode: 'server', // analyzerHost: '127.0.0.1', // analyzerPort: '8888', // reportFilename: 'report.html', // defaultSizes: 'parsed', // openAnalyzer: true, // generateStatsFile: false, // statsFilename: 'stats.json', // statsOptions: null, // excludeAssets: null, // logLevel: info // }) ] }
使用方式:
"generateAnalyzFile": "webpack --profile --json > stats.json", // 生成分析文件 "analyz": "webpack-bundle-analyzer --port 8888 ./dist/stats.json" // 啓動展現打包報告的http服務器
speed-measure-webpack-plugin
,打包速度測量插件。這個插件能夠測量webpack
構建速度,能夠測量打包過程當中每一步所消耗的時間,而後讓咱們能夠有針對的去優化代碼。
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin'); const smw = new SpeedMeasureWebpackPlugin(); // 用smw.wrap()包裹webpack的全部配置項 module.exports =smw.wrap({ module: {}, plugins: [] });
咱們還能夠經過一些小的優化點來加快打包速度
resolve.extensions
:用來代表文件後綴列表,默認查找順序是 ['.js', '.json']
,若是你的導入文件沒有添加後綴就會按照這個順序查找文件。咱們應該儘量減小後綴列表長度,而後將出現頻率高的後綴排在前面resolve.alias
:能夠經過別名的方式來映射一個路徑,能讓 Webpack
更快找到路徑module.exports ={ // ...省略其餘配置 resolve: { extensions: [".js",".jsx",".json",".css"], alias:{ "jquery":jquery } } };
image-webpack-loader這個loder
能夠幫助咱們對打包後的圖片進行壓縮和優化,例如下降圖片分辨率,壓縮圖片體積等。
module.exports ={ // ...省略其餘配置 module: { rules: [ { test: /\.(png|svg|jpg|gif|jpeg|ico)$/, use: [ 'file-loader', { loader: 'image-webpack-loader', options: { mozjpeg: { progressive: true, quality: 65 }, optipng: { enabled: false, }, pngquant: { quality: '65-90', speed: 4 }, gifsicle: { interlaced: false, }, webp: { quality: 75 } } } ] } ] } };
有時候一些時間久遠的項目,可能會存在一些CSS
樣式被迭代廢棄,須要將其剔除掉,此時就可使用purgecss-webpack-plugin
插件,該插件能夠去除未使用的CSS
,通常與 glob
、glob-all
配合使用。
注意:此插件必須和CSS
代碼抽離插件mini-css-extract-plugin
配合使用。
例如咱們有樣式文件style.css
:
body{ background: red } .class1{ background: red }
這裏的.class1
顯然是無用的,咱們能夠搜索src
目錄下的文件,刪除無用的樣式。
const glob = require('glob'); const PurgecssPlugin = require('purgecss-webpack-plugin'); module.exports ={ // ... plugins: [ // 須要配合mini-css-extract-plugin插件 new PurgecssPlugin({ paths: glob.sync(`${path.join(__dirname, 'src')}/**/*`, {nodir: true}), // 不匹配目錄,只匹配文件 }) }), ] }
咱們知道,通常經常使用的類庫都會發布在CDN
上,所以,咱們能夠在項目中以CDN
的方式加載資源,這樣咱們就不用對資源進行打包,能夠大大減小打包後的文件體積。
以CDN
方式加載資源須要使用到add-asset-html-cdn-webpack-plugin
插件。咱們以CDN
方式加載jquery
爲例:
const AddAssetHtmlCdnPlugin = require('add-asset-html-cdn-webpack-plugin') module.exports ={ // ... plugins: [ new AddAssetHtmlCdnPlugin(true,{ 'jquery':'https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js' }) ], //在配置文件中標註jquery是外部的,這樣打包時就不會將jquery進行打包了 externals:{ 'jquery':'$' } }
Tree-shaking
,搖晃樹。顧名思義就是當咱們搖晃樹的時候,樹上乾枯的沒用的葉子就會掉下來。類比到咱們的代碼中就是將沒用的代碼搖晃下來,從而實現刪除代碼中未被引用的代碼。
這個功能在webpack4
中,當咱們將mode
設置爲production
時,會自動進行tree-shaking
。
來看下面代碼:
main.js
import { minus } from "./calc"; console.log(minus(1,1));
calc.js
import {test} from './test'; export const sum = (a, b) => { return a + b + 'sum'; }; export const minus = (a, b) => { return a - b + 'minus'; };
test.js
export const test = ()=>{ console.log('hello') } console.log(test());
觀察上述代碼其實咱們主要使用minus
方法,test.js
代碼是有反作用的!所謂"反作用",官方文檔以下解釋:
「反作用」的定義是,在導入時會執行特殊行爲的代碼,而不是僅僅暴露一個 export 或多個 export。舉例說明,例如 polyfill,它影響全局做用域,而且一般不提供 export。
對上述代碼進行打包後發現'hello'
依然會被打印出來,這時候咱們須要在package.json
中配置配置不使用反作用:
{ "sideEffects": false }
若是這樣設置,默認就不會導入css
文件啦,由於咱們引入css
也是經過import './style.css'
這裏重點就來了,tree-shaking
主要針對es6模塊,咱們可使用require
語法導入css,可是這樣用起來有點格格不入,因此咱們能夠配置css
文件不是反作用,以下:
{ "sideEffects":[ "**/*.css" ] }
Scope Hoisting
可讓 Webpack
打包出來的代碼文件更小、運行的更快, 它又譯做 "做用域提高",是在 Webpack3
中新推出的功能。
因爲最初的webpack
轉換後的模塊會包裹上一層函數,import
會轉換成require
,由於函數會產生大量的做用域,運行時建立的函數做用域越多,內存開銷越大。而Scope Hoisting 會分析出模塊之間的依賴關係,儘量的把打包出來的模塊合併到一個函數中去,而後適當地重命名一些變量以防止命名衝突。這個功能在webpack4
中,當咱們將mode
設置爲production
時會自動開啓。
好比咱們但願打包兩個文件
let a = 1; let b = 2; let c = 3; let d = a+b+c export default d; // 引入d import d from './d'; console.log(d)
最終打包後的結果會變成 console.log(6)
,這樣的打包方式生成的代碼明顯比以前的少多了,而且減小多個函數後內存佔用也將減小。若是你但願在開發模式development
中開啓這個功能,只須要使用插件 webpack.optimize.ModuleConcatenationPlugin()
就能夠了。
module.exports = { // ... plugins: [ // 開啓 Scope Hoisting new webpack.optimize.ModuleConcatenationPlugin(), ] }
必你們在開發單頁面應用項目的時候,項目中都會存在十幾甚至更多的路由頁面。若是咱們將這些頁面所有打包進一個文件的話,雖然將多個請求合併了,可是一樣也加載了不少並不須要的代碼,耗費了更長的時間。那麼爲了首頁能更快地呈現給用戶,咱們確定是但願首頁能加載的文件體積越小越好,這時候咱們就可使用按需加載,將每一個路由頁面單獨打包爲一個文件。在給單頁應用作按需加載優化時,通常採用如下原則:
chunk
動態加載目前並無原生支持,須要babel
的插件:plugin-syntax-dynamic-import
。安裝此插件而且在.babelrc
中配置:
{ // 添加 "plugins": ["transform-vue-jsx", "transform-runtime"], }
例如以下示例:
index.js
let btn = document.createElement('button'); btn.innerHTML = '點擊加載視頻'; btn.addEventListener('click',()=>{ import(/* webpackChunkName: "video" */'./video').then(res=>{ console.log(res.default); }); }); document.body.appendChild(btn);
webpack.config.js
module.exports = { // ... output:{ chunkFilename:'[name].min.js' } }
這樣打包後的結果最終的文件就是 video.min.js
,而且剛啓動項目時不會加載該文件,只有當用戶點擊了按鈕時纔會動態加載該文件。
以上就是一些經常使用的webpack
優化手段,固然webpack
優化手段還有不少,而且用法也有不少。須要的話能夠閱讀官方文檔來深刻學習。
(完)