剛剛,發(fā)布Webpack中級教程系列

webpack是什么?
webpack是前端最火的打包工具,是大前端自動(dòng)化工廠的重要組成部分。
webpack關(guān)于HTML的部分
- 對于瀏覽器而言,html文件是用戶訪問的入口點(diǎn),也是所有資源的掛載點(diǎn),所有資源都是通過html中的標(biāo)記來進(jìn)行引用的。
- 在webpack的構(gòu)建世界里,html只是一個(gè)展示板,而entry參數(shù)中指定的javascript入口文件才是真正在構(gòu)建過程中管理和調(diào)度資源的掛載點(diǎn),html文件中最終展示的內(nèi)容,都是webpack在加工并為所有資源打好標(biāo)記以后傳遞給它的,業(yè)界將這種有別與瀏覽器的模式稱之為“webpack的逆向注入”
- 前端項(xiàng)目可以大致分為 單頁面應(yīng)用 和 多頁面應(yīng)用
- html文件主要作為訪問入口文件,是<style> 樣式標(biāo)簽和<script>腳本標(biāo)簽的掛載點(diǎn)
打包中需要注意:
- 第一,個(gè)性化內(nèi)容填充,如頁面標(biāo)題,描述,關(guān)鍵字;
- 第二,多余空格刪除,連續(xù)多個(gè)空白字符的合并;
- 第三,代碼壓縮,多余空白字符的合并;
- 第四,去除注解
入口html文件的處理
- 單頁面應(yīng)用打包
入口html文件的處理使用 html-webpack-plugin 插件來設(shè)置一定的配置參數(shù)。
webpack.config.js配置

index.html 模板文件(構(gòu)建生成的入口頁面是以此為模板的):

多頁面應(yīng)用打包
項(xiàng)目中有多個(gè)頁面,考慮兩個(gè)基本問題:
- 如何自動(dòng)生成多個(gè)頁面
- 如果引用中存在公共的模塊,怎么樣才能提取公共模塊
> 多頁面應(yīng)用的基本結(jié)構(gòu)理解起來并不復(fù)雜,可以將其看做是多個(gè)單頁面應(yīng)用的組合
- entry參數(shù)需要配置多個(gè)依賴入口文件

html文件則需要分別引用對應(yīng)的入口文件并生成對應(yīng)的訪問入口:

可以看到在生成html文件時(shí)已經(jīng)為其單獨(dú)引用了chunks數(shù)組中指定的模塊,這使得對應(yīng)的頁面生成時(shí)只依賴自己需要的腳本。
html-webpack-plugin插件是依賴于html-loader而工作的,當(dāng)你顯式使用/\.html$/作為規(guī)則來篩選文件時(shí),同樣會(huì)選擇到作為入口文件的html資源,從而造成沖突報(bào)錯(cuò)。
webpack中關(guān)于CSS的部分
CSS文件的處理,需要處理的基本問題:
- 預(yù)編譯語言轉(zhuǎn)換
- 樣式文件掛載方式選擇
- 代碼優(yōu)化(合并以及壓縮)
- 去除或保留指定格式的注解
- 資源定位路徑的轉(zhuǎn)換
- 響應(yīng)式布局單位轉(zhuǎn)換
- 模塊化
- 處理瀏覽器兼容
> 解決方案
- 舊的解決方案:預(yù)編譯語言 + 命名方法論
- 新的解決方案:預(yù)編譯語言 + 構(gòu)建工具 + BEM + ACSS全局樣式+CSSModule組件樣式+ POSTCSS
舊:例如編寫簡單的@mixin px2rem( )函數(shù)來將開發(fā)中使用的px單位轉(zhuǎn)換為rem單位,達(dá)到移動(dòng)端自適應(yīng)的目的,或是編寫一些處理兼容性的函數(shù)來處理瀏覽器兼容性。
新:構(gòu)建工具可以通過自動(dòng)化檢測將預(yù)編譯語言轉(zhuǎn)換為CSS,基于現(xiàn)代化構(gòu)建工具的CSS-Module功能,可以通過特定的語法解決CSS模塊化的問題,而基于POSTCSS實(shí)現(xiàn)的autoprefixer插件,可以依據(jù)CanIUse網(wǎng)站提供的瀏覽器支持度數(shù)據(jù)實(shí)現(xiàn)代碼的跨瀏覽器前綴自動(dòng)補(bǔ)齊。
常用的插件:
- style-loader——將處理結(jié)束的CSS代碼存儲(chǔ)在js中,運(yùn)行時(shí)嵌入<style>后掛載至html頁面上
- css-loader——加載器,使webpack可以識(shí)別css模塊
- postcss-loader——加載器
- sass-loader——加載器,使webpack可以識(shí)別scss/sass文件,默認(rèn)使用node-sass進(jìn)行編譯
- mini-css-extract-plugin——插件,4.0版本啟用的插件,替代原extract-text-webpack-plugin插件,將處理后的CSS代碼提取為獨(dú)立的CSS文件
- optimize-css-assets-webpack-plugin——插件,實(shí)現(xiàn)CSS代碼壓縮
- autoprefixer——自動(dòng)化添加跨瀏覽器兼容前綴
使用SCSS作為預(yù)編譯語言



可以看到轉(zhuǎn)換后的結(jié)果:

代碼壓縮等優(yōu)化功能在 默認(rèn)當(dāng)mode: 'production'時(shí)有效
使用CSS-Modules
CSS Module在CSS中使用類選擇器,其基本原理是將CSS代碼中的樣式名替換為哈希值,并建立一個(gè)json對照表,在js文件中對于屬性名選擇器的使用均被替換為哈希字符串,以此來解決CSS模塊化的問題。
在webpack中使用CSS Modules功能非常簡單,只需要在css-loader的配置參數(shù)中設(shè)置:{modules:true}即可激活模塊化功能。
開啟模塊化功能后再進(jìn)行打包,可以看到同樣的main.css文件變成了如下樣子:

進(jìn)一步了解 Css-Process-Chain
webpack中關(guān)于Assets部分
Assets資源的基本處理需求
Assets,指項(xiàng)目中被引用的資源,通常為各種格式的圖片和字體文件,當(dāng)然也可能包含各式各樣其他擴(kuò)展名的文件(.json,.xml等),常見的圖片和文字資源的處理包括:
- 體積壓縮
- 雪碧圖合并及引用修正
- 資源的引用路徑自動(dòng)替換
webpack處理引用資源
資源打標(biāo)
webpack通過file-loader處理資源文件,它會(huì)將rules規(guī)則命中的資源文件按照配置的信息(路徑,名稱等)輸出到指定目錄,并返回其資源定位地址(輸出路徑,用于生產(chǎn)環(huán)境的publicPath路徑),默認(rèn)的輸出名是以原文件內(nèi)容計(jì)算的MD5 Hash命名的。

引用優(yōu)化
構(gòu)建工具通過url-loader來優(yōu)化項(xiàng)目中對于資源的引用路徑,并設(shè)定大小限制,當(dāng)資源的體積小于limit時(shí)將其直接進(jìn)行Base64轉(zhuǎn)換后嵌入引用文件,體積大于limit時(shí)可通過fallback參數(shù)指定的loader進(jìn)行處理。

sprites雪碧圖合成
雪碧圖合成,聽起來是一個(gè)顯得略高端的知識(shí)點(diǎn),但它并不是必須進(jìn)行的,任何一種技術(shù)都有其使用場景。有的場景下需要將圖片資源合并為獨(dú)立的雪碧圖而減少http請求的次數(shù),有的時(shí)候或許通過url-loader直接將其嵌入文檔就可以。矢量圖在不同場景下的處理方式也不相同。
采用url-loader + file-loader作為資源處理的一般通用方案
位圖處理


矢量圖處理
開發(fā)中常用的矢量圖為svg格式,既可以使用inline-svg-loader進(jìn)行資源嵌入,也可以使用svg-sprite-loader將矢量圖資源合并為雪碧圖,具體采用哪種方案,需要由項(xiàng)目的實(shí)際情況來判斷。

圖片壓縮
- 圖片資源是可以以清晰度為量化參考進(jìn)行體積壓縮的?
webpack中關(guān)于JavaScript和splitChunk
javascript之所以需要打包合并,是因?yàn)槟K化開發(fā)的存在。開發(fā)階段我們需要將js文件分開寫在很多零碎的文件中,方便調(diào)試和修改,但如果就這樣上線,那首頁的http請求數(shù)量將直接爆炸。同一個(gè)項(xiàng)目,別人2-3個(gè)請求就拿到了需要的文件,而你的可能需要20-30個(gè),結(jié)果就不用多說了。
但是合并腳本可不是“把所有的碎片文件都拷貝到一個(gè)js文件里”這樣就能解決的,不僅要解決命名空間沖突的問題,還需要兼容不同的模塊化方案,更別提根據(jù)模塊之間復(fù)雜的依賴關(guān)系來手動(dòng)確定模塊的加載順序了,所以利用自動(dòng)化工具來將開發(fā)階段的js腳本碎片進(jìn)行合并和優(yōu)化是非常有必要的。
JS文件的打包:
- 代碼編譯(TS或ES6代碼的編譯)
- 腳本合并
- 公共模塊識(shí)別
- 代碼分割
- 代碼壓縮混淆
使用webpack處理js文件
使用babel轉(zhuǎn)換ES6+語法
babel是ES6語法的轉(zhuǎn)換工具

腳本合并
- 模塊管理和文件合并這兩個(gè)功能是webpack最初設(shè)計(jì)的主要用途
- webpack默認(rèn)支持的是CommonJs規(guī)范
公共模塊識(shí)別

代碼分割

為什么要進(jìn)行代碼分割?
代碼分割最基本的任務(wù)是分離出第三方依賴庫,因?yàn)榈谌綆斓膬?nèi)容可能很久都不會(huì)變動(dòng),所以用來標(biāo)記變化的摘要哈希contentHash也很久不變,這也就意味著我們可以利用本地緩存來避免沒有必要的重復(fù)打包,并利用瀏覽器緩存避免冗余的客戶端加載。另外當(dāng)項(xiàng)目發(fā)布新版本時(shí),如果第三方依賴的contentHash沒有變化,就可以使用客戶端原來的緩存文件(通用的做法一般是給靜態(tài)資源請求設(shè)置一個(gè)很大的max-age),提升訪問速度。另外一些場景中,代碼分割也可以提供對腳本在整個(gè)加載周期內(nèi)的加載時(shí)機(jī)的控制能力。
代碼分割的使用場景
舉個(gè)很常見的例子,比如你在做一個(gè)數(shù)據(jù)可視化類型的網(wǎng)站,引用到了百度的Echarts作為第三方庫來渲染圖表,如果你將自己的代碼和Echarts打包在一起生成一個(gè)main.bundle.js文件,這樣的結(jié)果就是在一個(gè)網(wǎng)速欠佳的環(huán)境下打開你的網(wǎng)站時(shí),用戶可能需要面對很長時(shí)間的白屏,你很快就會(huì)想到將Echarts從主文件中剝離出來,讓體積較小的主文件先在界面上渲染出一些動(dòng)畫或是提示信息,然后再去加載Echarts,而分離出的Echarts也可以從速度更快的CDN節(jié)點(diǎn)獲取,如果加載某個(gè)體積龐大的庫,你也可以選擇使用懶加載的方案,將腳本的下載時(shí)機(jī)延遲到用戶真正使用對應(yīng)的功能之前。這就是一種人工的代碼分割。
從上面的例子整個(gè)的生命周期來看,我們將原本一次就可以加載完的腳本拆分為了兩次,這無疑會(huì)加重服務(wù)端的性能開銷,畢竟建立TCP連接是一種開銷很大的操作,但這樣做卻可以換來對渲染節(jié)奏的控制和用戶體驗(yàn)的提升,異步模塊和懶加載模塊從宏觀上來講實(shí)際上都屬于代碼分割的范疇。code splitting最極端的狀況其實(shí)就是拆分成打包前的原貌,也就是源碼直接上線。
代碼分割的本質(zhì)
源代碼直接上線
代碼分割:優(yōu)點(diǎn)是過程可控,可減少首屏空白時(shí)長;缺點(diǎn)是http請求多,性能開銷大。
Code Splitting與平衡(請求可合并的腳本;某較大的第三方庫;工具型第三方庫;某個(gè)按鈕點(diǎn)擊后加載。
客戶端-》緩存命中率高-》性能開銷和用戶體驗(yàn)的平衡
打包為一個(gè)腳本上線(main.bundle.js)
優(yōu)點(diǎn):一把搞完,省事,服務(wù)器壓力??;缺點(diǎn):時(shí)間長,頁面空白期長
代碼混淆壓縮
- webpack4中已經(jīng)內(nèi)置了UglifyJs插件,當(dāng)打包模式參數(shù)mode設(shè)置為production時(shí)就會(huì)自動(dòng)開啟
- babel的插件中也能提供代碼壓縮的處理
splitChunks技術(shù)

參數(shù)配置

代碼分割實(shí)例
單頁面應(yīng)用
單頁面應(yīng)用只有一個(gè)入口文件,splitChunks的主要作用是將引用的第三方庫拆分出來。從下面的分包結(jié)果就可以看出,node_modules中的第三方引用被分離了出來,放在了vendors-main.[hash].js中。
webpack --config webpack.spa.config.js
多頁面應(yīng)用
源碼的依賴關(guān)系為:
entryA.js: vue vuex component10k
entryB.js: vue axios component10k
entryC.js: vue vuex axios component10k
splitChunks提供了更精確的分割策略,但是似乎無法直接通過html-webpack-plugin配置參數(shù)來動(dòng)態(tài)解決分割后代碼的注入問題,因?yàn)榉职Q是不確定的。這個(gè)場景在使用chunks:'async'默認(rèn)配置時(shí)是不存在的,因?yàn)楫惒侥K的引用代碼是不需要以<script>標(biāo)簽的形式注入html文件的。
當(dāng)chunks配置項(xiàng)設(shè)置為all或initial時(shí),就會(huì)有問題,例如上面示例中,通過在html-webpack-plugin中配置excludeChunks可以去除page和about這兩個(gè)chunk,但是卻無法提前排除vendors-about-page這個(gè)chunk,因?yàn)榇虬盁o法知道是否會(huì)生成這樣一個(gè)chunk。
webpack中的關(guān)于Module
大前端模塊化
CMD規(guī)范:引用Sea.js;瀏覽器
Webpack可識(shí)別:
UMD規(guī)范:AMD規(guī)范(引用Require.js);瀏覽器
CommonJs規(guī)范:原生支持,node
ESHarmony規(guī)范:支持度暫不完善,統(tǒng)一JS全環(huán)境
- 腳本合并是基于模塊化規(guī)范的
webpack與模塊化
webpack默認(rèn)支持的是CommonJs規(guī)范,畢竟它是nodejs支持的模塊管理方式,而沒有node哪來的webpack。但同時(shí)為了擴(kuò)展其使用場景,webpack在版本迭代中也加入了對ES harmony規(guī)范和AMD規(guī)范的兼容。
webpack如何識(shí)別CommonJs模塊

webpack如何識(shí)別ES Harmony模塊



webpack是一個(gè)JS代碼模塊化的打包工具。
資料官網(wǎng):www.webpackjs.com
