-
速度分析
- webpack有時侯打包很慢,而我們在項目中可能用了很多的
plugin
和loader
,想知道那個環節慢,下面這個插件可以計算plugin
和loader
的耗時
yarn add -D speed-measure-webpack-plugin
- webpack有時侯打包很慢,而我們在項目中可能用了很多的
-
體積分析
- 打包后的體積優化是一個很重要的點,譬如引入的一些第三方組件庫過大,這是就要考慮是否需要尋找替代品了。這里可以采用
webpack-bundle-analyzer
,它可以用交互式可縮放樹形圖顯示webpack
輸出文件的大小。
yarn add -D webpack-bundle-analyzer
- 安裝完后,配置
webpcak.config.js
,配置好后,yarn run dev
啟動后,會默認起一個端口號為8888的本地服務器。這樣就能看到每個模塊的的文件大小,進行針對性的優化。
- 打包后的體積優化是一個很重要的點,譬如引入的一些第三方組件庫過大,這是就要考慮是否需要尋找替代品了。這里可以采用
-
多線程/多實例構建
-
大家都知道
webpack
是運行在node
環境中,二node
,是單線程的。webpack
的打包過程是io
密集和計算機密集型操作,如果能fork
,多個進程并行處理各個任務,將會有效縮短構建時間。一般使用較多的兩個是thread-loader
和HappyPack
。- thread-loader(官方推薦)
yarn add -D thread-loader
thread-loader
會將你的loader
放置在一個worker
池里面運行,以達到多線程構建。
注:放置在這個thread-loader
之后的loader會在一個單獨的worker
池(worker pool
)中運行。- HappyPack(該插件作者不在維護)
yarn add - D happypack
HappyPack可以讓Webpack同一時間處理多個任務,發揮多喝CPU的能力,將任務分解給多個子進程去并發的執行,子進程處理完成后,再把結果發送給主進程。通過多進程模型,來加速代碼的構建。
-
-
多進程并行壓縮代碼
- 通常我們在開發環境,代碼構建時間比較快,而構建用于發布到線上的代碼時會添加壓縮代碼這一流程,則會道濟計算量大耗時多
-
webpack
默認提供了UglifyJS
插件來壓縮JS
代碼,但是使用的是單線程壓縮代碼,也就是說多個js文件需要被壓縮,它需要一個個文件進行壓縮。所以說在正式環境打包壓縮代碼速度非常慢(因為壓縮JS
代碼需要先把代碼解析成用Object
抽象表示的AST
語法樹,再應用各種規則分析處理AST
,導致這個過程耗時非常大)。所以我們要對壓縮代碼這里一步驟進行優化,常用的做法是多進程并行壓縮,目前主要有三種主流的壓縮方案。paralle-uglify-plugin
uglifyjs-webpack-plugin
-
terser-webpack-plugin
-
parallel-uglify-plugin
上面介紹的HappyPack
的思想是使用多個子進程去解析和編譯JS
,CSS
等,這樣就可以并行處理多個子任務,多個子任務完成后,再將結果發到主進程中,有了這個思想后,ParallelUglifyPlugin
插件就產生了。當
webpack
有多個JS
文件需要輸出和壓縮時,原來會使用UglifyJS
去一個個壓縮并且輸出,而ParallelUglifyPlugin
插件則會開啟多個子進程,把對多個文件壓縮的工作分給多個子進程去完成,但是每個子進程還是通過UglifyJS
去壓縮代碼。并行壓縮可以顯著的提升效率yarn add -D webpack-parallel-uglify-plugin
注:
webpack-parallel-uglify-plugin
已不再維護,這里不推薦使用 -
uglifyjs-webpack-plugin
yarn add -D uglifyjs-webpack-plugin
注:其實它和上面的
parallel-uglify-plugin
類似,也可通過設置parallel: true
開啟多進程壓縮。 terser-webpack-plugin
不知道你有沒有發現:webpack4 已經默認支持 ES6語法的壓縮。而這離不開terser-webpack-plugin.
yarn add -D uglifyjs-webpack-plugin
注:其實它和上面的
parallel-uglify-plugin
類似,也可通過設置parallel: true
開啟多進程壓縮。 -
-
預編譯資源模塊
- 什么是預編譯資源模塊?
在使用webpack
進行打包時候,對于依賴的第三方庫,比如vue
,vuex
等這些不會修改的依賴,我們可以讓它和我們自己編寫的代碼分開打包,這樣做的好處是每次更改我本地代碼的文件的時候,webpack
只需要打包我項目本身的文件代碼,而不會再去編譯第三方庫。那么第三方庫在第一次打包的時候只打包一次,以后只要我們不升級第三方包的時候,那么webpack
就不會對這些庫去打包,這樣的可以快速的提高打包的速度。其實也就是預編譯資源模塊。webpack
中,我們可以結合DllPlugin
和DllReferencePlugin
插件來實現。 - DllPlugin是什么?
它能把第三方庫代碼分離開,并且每次文件更改的時候,它只會打包該項目自身的代碼。所以打包速度會更快。DLLPlugin
插件是在一個額外獨立的webpack
設置中創建一個只有dll
的bundle
,也就是說我們在項目根目錄下除了有webpack.config.js
,還會新建一個webpack.dll.js
文件。webpack.dll.js
的作用是把所有的第三方庫依賴打包到一個bundle
的dll
文件里面,還會生成一個名為manifest.json
文件。該manifest.json
的作用是用來讓DllReferencePlugin
映射到相關的依賴上去的。 - DllReferencePlugin又是什么?
這個插件是在webpack.config.js
中使用的,該插件的作用是把剛剛在webpack.dll.js
中打包生成的dll
文件引用到需要的預編譯的依賴上來。什么意思呢?就是說在webpack.dll.js
中打包后比如會生成vendor.dll.js
文件和vendor-manifest.json
文件,vendor.dll.js
文件包含了所有的第三方庫文件,vendor-manifest.json
文件會包含所有庫代碼的一個索引,當在使用webpack.config.js
文件打包DllReferencePlugin
插件的時候,會使用該DllReferencePlugin
插件讀取vendor-manifest.json
文件,看看是否有該第三方庫。vendor-manifest.json
文件就是一個第三方庫的映射而已。 - 這么在項目中使用
- 上面說了這么多,主要是為了方便大家對于預編譯資源模塊和
DllPlugin
和DllReferencePlugin
插件作用的理解。先來看下完成的項目目錄結構,主要在兩塊配置,分別是webpack.dll.js
和webpack.config.js
(對應這里我是webpack.base.js
)
image.png - webpack.dll.js
const path = require('path'); const webpack = require('webpack'); module.exports = { mode: 'production', entry: { vendors: ['lodash', 'jquery'], react: ['react', 'react-dom'] }, output: { filename: '[name].dll.js', path: path.resolve(__dirname, './dll'), library: '[name]' }, plugins: [ new webpack.DllPlugin({ name: '[name]', path: path.resolve(__dirname, './dll/[name].manifest.json') }) ] }
這里我拆了兩部分:
vendors
(存放了lodash
、jquery
等)和react
(存放了react
相關的庫,react
、react-dom
等)- webpack.config.js(對應我這里就是webpack.base.js)
const path = require("path"); const fs = require('fs'); // ... const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin'); const webpack = require('webpack'); const plugins = [ // ... ]; const files = fs.readdirSync(path.resolve(__dirname, './dll')); files.forEach(file => { if(/.*\.dll.js/.test(file)) { plugins.push(new AddAssetHtmlWebpackPlugin({ filepath: path.resolve(__dirname, './dll', file) })) } if(/.*\.manifest.json/.test(file)) { plugins.push(new webpack.DllReferencePlugin({ manifest: path.resolve(__dirname, './dll', file) })) } }) module.exports = { entry: { main: "./src/index.js" }, module: { rules: [] }, plugins, output: { // publicPath: "./", path: path.resolve(__dirname, "dist") } }
由于上面我把第三方庫做了一個拆分,所以對應生成也就會是多個文件,這里讀取了一下文件,做了一層遍歷。最后在
package.json
里面再添加一條腳本就可以了:"scripts": { "build:dll": "webpack --config ./webpack.dll.js", },
運行
yarn build:dll
就會生成本小節開頭貼的那張項目結構圖了~ - 什么是預編譯資源模塊?
-
利用緩存提升二次構建速度
- 一般來說,對于靜態資源,我們都希望瀏覽器能夠進行緩存,那樣以后進入頁面就可以直接使用緩存資源,頁面打開速度會顯著加快,既提高了用戶的體驗也節省了寬帶資源。當然瀏覽器緩存方法有很多種,這里只簡單討論下在
webpack
中如何利用緩存來提升二次構建速度。在webpack
中利用緩存一般有以下幾種思路:-
babel-loader
開啟緩存 - 使用
cache-loader
- 使用
hard-source-webpack-plugin
-
- babel-loader
-
babel-loader
在執行的時候,可能會產生一些運行期間重復的公共文件,造成代碼體積冗余,同時也會減慢編譯效率??梢约由?code>cacheDirectory參數開啟緩存:
{ test: /\.js$/, exclude: /node_modules/, use: [{ loader: "babel-loader", options: { cacheDirectory: true } }], },
-
- cache-loader
- 在一些性能開銷較大的
loader
之前添加此loader
,以將結果緩存到磁盤里。
yarn add -D cache-loader
- 使用
cache-loader
的配置很簡單,放在其他loader
之前即可。修改Webpack
的配置如下:
注:請注意,保存和讀取這些緩存文件會有一些時間開銷,所以請只對性能開銷較大的// webpack.config.js module.exports = { module: { rules: [ { test: /\.ext$/, use: [ 'cache-loader', ...loaders ], include: path.resolve('src') } ] } }
loader
使用此loader
。 - 在一些性能開銷較大的
- hard-source-webpack-plugin
-
HardSourceWebpackPlugin
為模塊提供了中間緩存,緩存默認的存放路徑是:node_modules/.cache/hard-source
。配置hard-source-webpack-plugin
后,首次構建時間并不會有太大的變化,但是從第二次開始,構建時間大約可以減少 80%左右。yarn add -D hard-source-webpack-plugin
- 使用
// webpack.config.js var HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); module.exports = { entry: // ... output: // ... plugins: [ new HardSourceWebpackPlugin() ] }
webpack5
中會內置hard-source-webpack-plugin
。 -
- 一般來說,對于靜態資源,我們都希望瀏覽器能夠進行緩存,那樣以后進入頁面就可以直接使用緩存資源,頁面打開速度會顯著加快,既提高了用戶的體驗也節省了寬帶資源。當然瀏覽器緩存方法有很多種,這里只簡單討論下在
-
縮小構建目標
- 有時候我們的項目中會用到很多模塊,但有些模塊其實是不需要被解析的。這時我們就可以通過縮小構建目標或者減少文件搜索范圍的方式來對構建做適當的優化。
- 主要是exclude 與 include的使用:
- exclude: 不需要被解析的模塊
- include: 需要被解析的模塊
這里const path = require('path'); module.exports = { ... module: { rules: [ { test: /\.js$/, exclude: /node_modules/, // include: path.resolve('src'), use: ['babel-loader'] } ] }
babel-loader
就會排除對node_modules
下對應js
的解析,提升構建速度。
-
減少文件搜索范圍
- 這個主要是resolve相關的配置,用來設置模塊如何被解析。通過resolve的配置,可以幫助Webpack快速查找依賴,也可以替換對應的依賴。
- resolve.modules:告訴
webpack
解析模塊時應該搜索的目錄 - resolve.mainFields:當從
npm
包中導入模塊時(例如,import * as React from 'react'
),此選項將 決定在package.json
中使用哪個字段導入模塊。根據webpack
配置中指定的target
不同,默認值也會有所不同 - resolve.mainFiles:解析目錄時要使用的文件名,默認是
index
- resolve.extensions:文件擴展名
// webpack.config.js const path = require('path'); module.exports = { ... resolve: { alias: { react: path.resolve(__dirname, './node_modules/react/umd/react.production.min.js') }, //直接指定react搜索模塊,不設置默認會一層層的搜尋 modules: [path.resolve(__dirname, 'node_modules')], //限定模塊路徑 extensions: ['.js'], //限定文件擴展名 mainFields: ['main'] //限定模塊入口文件名 } ... }
- resolve.modules:告訴
- 這個主要是resolve相關的配置,用來設置模塊如何被解析。通過resolve的配置,可以幫助Webpack快速查找依賴,也可以替換對應的依賴。
-
動態 Polyfill 服務
- 什么是
babel-polyfill
?
babel
只負責語法轉換,比如將ES6
的語法轉換成ES5
。但如果有些對象、方法,瀏覽器本身不支持,比如:- 全局對象:
Promise
、WeakMap
等。 - 全局靜態函數:
Array.from
、Object.assign
等。 - 實例方法:比如
Array.prototype.includes
等。
此時,需要引入babel-polyfill
來模擬實現這些對象、方法。這種一般也稱為墊片
。
- 全局對象:
- 怎么使用
babel-polyfill
?- 使用也非常簡單,在webpack.config.js文件作如下配置就可以了:
module.exports = { entry: ["@babel/polyfill", "./app/js"], };
- 使用也非常簡單,在webpack.config.js文件作如下配置就可以了:
- 動態
Polyfill
服務
image.png
每次打開頁面,瀏覽器都會向Polyfill Service發送請求,Polyfill Service識別 User Agent,下發不同的 Polyfill,做到按需加載Polyfill的效果。 - 怎么使用動態
Polyfill
服務?//訪問url,根據User Agent 直接返回瀏覽器所需的 polyfills https://polyfill.io/v3/polyfill.min.js
- 什么是
-
Scope Hoisting
什么是Scope Hoisting?
Scope hoisting
直譯過來就是「作用域提升」。熟悉JavaScript
都應該知道「函數提升」和「變量提升」,JavaScript
會把函數和變量聲明提升到當前作用域的頂部。「作用域提升」也類似于此,webpack
會把引入的js
文件“提升到”它的引入者頂部。Scope Hoisting
可以讓Webpack
打包出來的代碼文件更小、運行的更快。-
啟用
Scope Hoisting
- 要在
Webpack
中使用Scope Hoisting
非常簡單,因為這是Webpack
內置的功能,只需要配置一個插件,相關代碼如下:
// webpack.config.js const webpack = require('webpack') module.exports = mode => { if (mode === 'production') { return {} } return { devtool: 'source-map', plugins: [new webpack.optimize.ModuleConcatenationPlugin()], } }
- 好處
- 代碼體積更小,因為函數申明語句會產生大量代碼;
- 代碼在運行時因為創建的函數作用域更少了,內存開銷也隨之變小。
Scope Hoisting
的實現原理其實很簡單:分析出模塊之間的依賴關系,盡可能的把打散的模塊合并到一個函數中去,但前提是不能造成代碼冗余。因此只有那些被引用了一次的模塊才能被合并。注意:由于
Scope Hoisting
需要分析出模塊之間的依賴關系,因此源碼必須采用ES6
模塊化語句,不然它將無法生效。 - 要在
webpack優化策略
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
- 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
- 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
- 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...