Webpack
是一個模塊打包工具,在Webpack
裏一切文件皆模塊。經過loader
轉換文件,經過plugin
注入鉤子,最後輸出由多個模塊組合的文件。Webpack
專一構建模塊化項目。Webpack
能夠看做是模塊打包機:它作的事情是,分析你的項目結構,找到JavaScript
模塊以及其餘一些不能被瀏覽器直接運行的擴展語音(如:Scss
,TypeScript
等),並將其打包爲合適的格式以供瀏覽器使用。javascript
Grunt/Gulp
是一種可以優化前端開發流程的工具,而Webpack
是一種模塊化的解決方案。css
Grunt/Gulp
的工做方式是:在一個配置文件中,指明某些文件進行相似編譯/組合/壓縮等任務的具體步驟,以後工具能夠自動幫你完成這些任務Webpack
的工做方式是:把項目看成是一個總體,經過指定的入口文件,Webpack
會從這個入口文件開始找到項目全部的依賴文件,而後使用loader
處理它們,最後打包成一個或多個瀏覽器可以識別的JavaScript
文件Grunt/Gulp
須要將整個前端構建過程拆分紅多個task
,合理控制全部task
的調用關係Webpack
須要定義好入/出口,並須要清楚對於不一樣類型資源應該用什麼loader解析編譯Grunt/Gulp
是基於任務和流(task
和stream
)的。相似jQuery
,找到一個(或一類)文件,對其作一系列的鏈式操做,更新流上的數據,整條鏈式操做構成了一個任務,多個任務就構成了整個Web的構建流程。Webpack
是基於入口的。Webpack
會自動的遞歸解析入口所須要加載的全部資源文件,而後用不一樣的loader
來處理不一樣的文件,用pulgin
擴展Webpack
功能。html
Grunt/Gulp
更像是後端開發者的思路,須要對整個流程瞭如指掌。Webpack
更傾向於前端開發者的思路。前端
Webpack
的運行流程是一個串行的過程,從啓動到結束會依次執行如下步驟:java
shell
語句中讀取與合併參數,獲得最終參數;Compiler
對象,加載全部配置的插件,執行對象的run
方法開始執行編譯;entry
找出全部的入口文件;loader
對模塊進行編譯。再找出該模塊依賴的模塊,再遞歸本步驟,直到全部入口依賴的文件都通過本步驟的處理;loader
編譯完全部模塊後,獲得每一個模塊被編譯後的最終內容以及它們之間的依賴關係;Chunk
,再把每一個Chunk
轉換成一個單獨的文件加入到輸出列表,這步是能夠修改輸出內容的最後機會;在以上過程當中,Webpack
會在特定的的時間點廣播特定的事件,插件在監聽到感興趣的事件後會執行特定的邏輯。而且插件能夠調用Webpack
提供的API改變Webpack
的運行結果。node
Webpack
打包出來的文件webpack
在進行模塊的依賴分析的時候,代碼分割出來的代碼塊Webpack
中,一切皆模塊,一個模塊對應一個文件webpack
打包過程的方式,插件含有apply
屬性的JavaScript
對象,apply
屬性會被webpack compiler
調用,而且compiler
對象能夠在整個編譯生命週期內訪問loader
直譯爲「加載器",Webpack
將一切文件視爲模塊,可是Webpack
原生只能解析JavaScript
和JSON
類型文件。若是想加載解析其餘類型文件,就會用到loader
。因此loader
是讓Webpack
擁有加載和解析非JavaScript
文件的能力plugin
直譯爲」插件「,plugin
能夠擴展Webpack
的功能,讓Webpack
具備更多的靈活性。在Webpack
運行的生命週期中會廣播許多事件,plugin
能夠監聽這些事件,在合適的時機經過Webpack
提供的API
改變輸出結果loader
在module rules
中配置,也就說它做爲模塊解析規則存在。類型爲Array
,每一項都是一個Object
,裏面描述了什麼類型的文件(test),使用什麼加載(loader)和使用的參數(options)plugin
單獨在plugins
中單獨配置。類型爲Array
,每項都是一個plugin
的實例,參數是經過構造函數傳入base64
的方式將內容注入到代碼中ES6
轉成ES5
CSS
,支持模塊化/壓縮/文件導入等特性CSS
代碼注入到JavaScript
中,經過DOM
操做去加載CSS
ESlint
檢查JavaScript
代碼html
入口文件,並引用對應的外部資源JavaScript
代碼CSS
文件爲了使用tree shaking
,須要知足如下條件:webpack
import
和export
)package.json
文件中,添加sideEffects
入口這種方式是經過package.json
的sideEffects
屬性來實現的。nginx
{
"sideEffects": false }
「反作用」的定義是,在導入時會執行特殊行爲的代碼,而不是僅僅暴露一個export
或多個export
。舉例說明,例如polyfill
,它影響全局做用域,而且一般不提供export
。
注意,任何導入的文件都會受到tree shaking
的影響。這意味着,若是在項目中使用相似css-loader
並導入CSS
文件,則須要將其添加到 side effect 列表中,以避免在生產模式中無心中將它刪除:web
{
"sideEffects": ['*.css'] }
從 webpack 4 開始,也能夠經過 "mode" 配置選項輕鬆切換到壓縮輸出,只需設置爲 "production"。
也能夠在命令行接口中使用--optimize-minimize
標記,來使用UglifyjsPlugin
。shell
code splitting
的必要性
code splitting
,打包後單文件提交較大,加載時長較長,影響用戶體驗code splitting
,常常修改業務代碼,從新打包後,瀏覽器不能進行緩存,致使性能較差,影響用戶體驗import _ from 'lodash';
webpack.common.js
配置以下:
....
optimization: {
splitChunks: { chunks: 'all' } } ....
配置後,會將公用類庫進行打包,生成一個vendors~main.js
文件。
function getComponent() { return import('lodash').then(({ default: _ }) => { var element = document.createElement('div'); element.innerHTML = _.join(['Clear', 'love'], ''); return element; }) } getComponent().then(element => { document.body.appendChild(element); })
webpack-dev-server
使用內存來存儲Webpack
開發環境下打包的文件,而且可使用模塊熱更新,它比傳統的http
服務對開發更加簡單高效。
模塊熱更新是Webpack
是的一個功能,它可使得代碼修改之後不需刷新瀏覽器就能夠更新,是高級版的自動刷新瀏覽器。devServer
經過hot
屬性能夠控制模塊熱更替。
const webpack = require('webpack'); const path = require('path'); let env = process.env.NODE_ENV == "development" ? "development" : "production"; const config = { mode: env, devServer: { hot:true }, plugins: [ new webpack.HotModuleReplacementPlugin(), //熱加載插件 ] } module.exports = config;
"script": { "start": "NODE_EVN=development webpack-dev-server --config webpack-devlop-config.js --hot" }
Webpack
的熱更新有稱爲熱替換(Hot Module Replacement),縮寫爲HMR
。這個機制能夠實現不刷新瀏覽器而將新變動的模塊替換舊的模塊。原來以下:
server
端和client
端都作了哪些具體工做:Webpack
的watch
模式下,文件系統中某一個文件發生修改,Webpack
監聽到文件變化,根據配置文件對模塊從新編譯打包,並將打包後的代碼經過簡單的JavaScript
對象保存在內存中。Webpack-dev-server
和Webpack
之間的接口交互,而在這一步,主要是dev-server
的中間件Webpack-dev-middleware
和Webpack
之間的交互,Webpack-dev-middleware
調用Webpack
暴露的API
對代碼變化進行監控,而且告訴webpack
,將代碼打包到內存中。Webpack-dev-server
對文件變化的一個監控,這一步不一樣於第一步,並非監控代碼變化從新打包。當咱們在配置文件中配置了devServer.watchContentBase
爲true
的時候,Server
會監聽這些配置文件夾中靜態文件的變化,變化後會通知瀏覽器端對應用進行live reload
。注意,這兒是瀏覽器刷新,和HMR
是兩個概念。webpack-dev-server
代碼的工做,該步驟主要是經過sockjs
(webpack-dev-server 的依賴)在瀏覽器端和服務端之間創建一個websocket
長鏈接,將Webpack
編譯打包的各個階段的狀態信息告知瀏覽器端,同時也包括第三步中Server
監聽靜態文件變化的信息。瀏覽器端根據這些socket
消息進行不一樣的操做。固然服務端傳遞的最主要信息仍是新模塊的hash
值,後面的步驟根據這一hash
值來進行模塊熱替換。webpack-dev-server/client
端並不可以請求更新的代碼,也不會執行熱更模塊操做,而把這些工做又交回給了Webpack
,webpack/hot/dev-server
的工做就是根據webpack-dev-server/client
傳給它的信息以及dev-server
的配置決定是刷新瀏覽器呢仍是進行模塊熱更新。固然若是僅僅是刷新瀏覽器,也就沒有後面那些步驟了。HotModuleReplacement.runtime
是客戶端HMR
的中樞,它接收到上一步傳遞給他的新模塊的hash
值,它經過JsonpMainTemplate.runtime
向server
端發送Ajax
請求,服務端返回一個json
,該json
包含了全部要更新的模塊的hash
值,獲取到更新列表後,該模塊再次經過jsonp
請求,獲取到最新的模塊代碼。這就是上圖中 七、八、9 步驟。HMR
成功與否的關鍵步驟,在該步驟中,HotModulePlugin
將會對新舊模塊進行對比,決定是否更新模塊,在決定更新模塊後,檢查模塊之間的依賴關係,更新模塊的同時更新模塊間的依賴引用。HMR
失敗後,回退到live reload
操做,也就是進行瀏覽器刷新來獲取最新打包代碼。使用最新穩定版本的webpack
、node
、npm
等,較新的版本更夠創建更高效的模塊樹以及提升解析速度。
因爲loader
對文件的轉換操做很耗時,因此須要讓儘量少的文件被loader
處理。咱們能夠經過如下3方面優化loader
配置:
cacheDirectory
選項開啓緩存include
、exclude
來減小被處理的文件// webpack.common.js module: { rules: [ { test:/\.js$/, //babel-loader支持緩存轉換出的結果,經過cacheDirectory選項開啓 loader:'babel-loader?cacheDirectory', //只對項目根目錄下的src 目錄中的文件採用 babel-loader include: [path.resolve('src')], //排除 node_modules 目錄下的文件,node_modules 目錄下的文件都是採用的 ES5 語法,不必再經過 Babel 去轉換 exclude: path.resolve(__dirname, 'node_modules') } ] }
Webpack 4
移除了CommonsChunkPlugin
取而代之的是兩個新的配置項optimization.splitChunks
和optimization.runtimeChunk
來簡化代碼分割的配置。
經過設置 optimization.splitChunks.chunks: "all"
來啓動默認的代碼分割配置項。
當知足以下條件時,webpack 會自動打包 chunks:
node_modules
30kb
, 若是此模塊是按需加載,並行請求的最大數量小於等於5optimization: {
splitChunks: { chunks: 'async', // all async initial 是否對異步代碼進行的代碼分割 minSize: 30000, // 引入模塊大於30kb才進行代碼分割 maxSize: 0, // 引入模塊大於Xkb時,嘗試對引入模塊二次拆分引入 minChunks: 1, // 引入模塊至被使用X次後才進行代碼分割 maxAsyncRequests: 5, // maxInitialRequests: 3, automaticNameDelimiter: '~', // 模塊間的鏈接符,默認爲"~" name: true, cacheGroups: { vendors: { test: /[\\/]node_modules[\\/]/, priority: -10 // 優先級,越小優先級越高 }, default: { // 默認設置,可被重寫 minChunks: 2, priority: -20, reuseExistingChunk: true // 若是原本已經把代碼提取出來,則重用存在的而不是從新產生 } } } }
減小編譯的總體大小,以提升構建性能。儘可能保持chunks
小巧。
thread-loader
能夠將很是耗性能的loaders轉存到worker pool
中。
不要使用太多的workers
,由於Node.js
的runtime
和loader
有必定的啓動開銷。最小化workers
和主進程間的模塊傳輸。進程間通信(IPC)是很是消耗資源的。
對於一些性能開銷較大的loader
以前能夠添加cache-loader
,啓用持久化緩存。
使用package.json
中的postinstall
清楚緩存目錄。
使用DllPlugin
將更新不頻繁的代碼進行單獨編譯。這將改善引用程序的編譯速度。即便它增長了構建過程的複雜度。
利用DllPlugin
和DllReferencePlugin
預編譯資源模塊, 經過DllPlugin
來對那些咱們引用可是絕對不會修改的npm
包來進行預編譯,再經過DllReferencePlugin
將預編譯的模塊加載進來。
如下幾步能夠提升解析速度:
resolve.modules
、resolve.extensions
、resolve.mainFiles
、resolve.desciriptionsFiles
中類目的數量,由於它們會增長文件系統的調用次數。symlinks
,能夠設置resolve.symlinks: false
plugins
,而且沒有指定context
信息,能夠設置resolve.cacheWithContext: false
如下幾個實用的工具經過在內存中進行代碼的編譯和資源的提供,但並不寫入磁盤來提升性能:
webpack-dev-server
webpack-hot-middleware
webpack-dev-middleware
須要注意在不一樣的devtool
的設置,會致使不一樣的性能差別。
eval
具備最好的性能,但不能幫你轉義代碼cheap-source-map
選擇來提升性能eval-source-map
配置進行增量編譯 在大多數狀況下,cheap-module-eval-source-map
是最好的選擇。
某些實用工具,plugins
和loaders
都只能在構建生產環境時才使用。例如,在開發時使用UglifyJsPlugin
來壓縮和修改代碼是沒有意義的。如下這些工具在開發中一般被排除在外:
UglifyJsPlugin
ExtractTextPlugin
[hash]/[chunkhash]
AggressiveSplittingPlugin
AggressiveMergingPlugin
ModuleConcatenationPlugin
webpack
只會在文件系統中生成已更新的chunk
。應當在生成入口chunk
時,儘可能減小入口chunk
的體積,以提升性能。
不要爲了很是小的性能增益,犧牲了你應用程序的質量!!請注意,在大多數狀況下優化代碼質量,比構建性能更重要。
當進行多個編譯時,如下工具能夠幫助到你:
parallel-webpack
: 它容許編譯工做在woker
池中進行。cache-loader
: 緩存能夠在多個編譯之間共享。項目中的preset/plugins
數量最小化
fork-ts-checker-webpack-plugin
進行類型檢查loaders
時跳過類型檢查ts-loader
時,設置happyPackMode: true
以及 transpileOnly: true
node-sass
中有個來自Node.js
線程池的阻塞線程的bug。當使用thread-loader
時,須要設置workParallelJobs: 2
用Webpack
優化前端性能是指優化Webpack
輸出結果,讓打包的結果在瀏覽器運行快速高效。
Webpack
的UglifyJsPlugin
和ParallelUglifyPlugin
來壓縮JavaScript
代碼。利用css-loader?minimize
來壓縮CSS
imagemin-webpack-plugin
等圖片資源壓縮插件,對引用的圖片資源進行壓縮處理url-loader
加載解析圖片資源時,能夠經過配置options limit
參數,將較小的圖片資源轉換成base64
格式,減小http
請求CDN
加速。在構建過程當中,將引用的靜態資源路徑修改成CDN
上對應的路徑。能夠利用Webpack
對於output
參數和各個loader
的publicPath
參數來修改資源路徑Webpack
時追加參數--optimize-minimize
來實現CommonJS
模塊化規範,因此打包後的最後結果也要支持該規則npm
模塊使用者的環境是不肯定的,頗有可能並不支持ES6
,因此打包的最後結果應該是採用ES5
編寫的。而且若是ES5
是通過轉換的,請最好連同SourceMap
一同上傳npm
包大小應該是儘可能小(有些倉庫會限制包大小)CommonJS
模塊化規範的解決方案: 設置output.libraryTarget='commonjs2'
使輸出的代碼符合CommonJS2
模塊化規範,以供給其它模塊導入使用babel-loader
把ES6
代碼轉換成ES5
的代碼。再經過開啓devtool: 'cheap-module-eval-source-map'
輸出SourceMap以發佈調試npm
包大小盡可能小的解決方案:Babel
在把ES6
代碼轉換成ES5
代碼時會注入一些輔助函數,最終致使每一個輸出的文件中都包含這段輔助函數的代碼,形成了代碼的冗餘。解決方法是修改.babelrc
文件,爲其加入transform-runtime
插件npm
模塊中的解決方案:使用externals
配置項來告訴Webpack
哪些模塊不須要打包const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { module: { rules: [ { test: /\.css$/, use: ExtractTextPlugin.extract({ use: ['css-loader'] }) // 提取出chunk中的css到單獨的文件中 } ] }, plugins: [ new ExtractTextPlugin({ filename: 'index.css' }) ] }