到目前胧后,Webpack已發(fā)布到v3.8.1尊沸,網(wǎng)上有很多Webpack入門到精通的教程及文檔,此文就從問(wèn)答的角度梳理下Webpack的知識(shí)脈絡(luò)妇多。
基礎(chǔ)篇
1. 為什么要使用Webpack伤哺?
在面對(duì)復(fù)雜的業(yè)務(wù)需求和前端性能優(yōu)化的場(chǎng)景時(shí),會(huì)存在以下需要解決的問(wèn)題:
- 前端模塊化工具
- 異步模塊加載及管理
- 樣式(Style)預(yù)處理:Scss者祖、Less立莉、Postcss等
- 樣式(Style)后處理:autoprefixer、img資源內(nèi)聯(lián)七问、原子css蜓耻、css module
- ES6語(yǔ)法、TypeScript械巡、CoffeeScript支持
- 專有代碼處理:JSX媒熊、*.vue
- 外部依賴模塊:Handlerbars等模板、gzip坟比、text芦鳍、json等
- 公共模塊提取
- 開(kāi)模模式自動(dòng)刷新瀏覽器
- 長(zhǎng)效緩存處理
- 代碼壓縮混淆
- ...
等等以上如果人肉處理則會(huì)相當(dāng)麻煩,按照Webpack的模塊打包思路葛账,以上問(wèn)題都能很好的解決柠衅。
2. 什么是Webpack?
Webpack是模塊打包器:它做的事情是籍琳,分析你的項(xiàng)目結(jié)構(gòu)菲宴,找到JavaScript模塊以及其它的一些瀏覽器不能直接運(yùn)行的拓展語(yǔ)言(Scss,TypeScript等)趋急,并將其轉(zhuǎn)換和打包為合適的格式供瀏覽器使用喝峦。
3. Webpack和Browserify、Rollup呜达、Gulp谣蠢、Grunt、Seajs、Requirejs的區(qū)別眉踱?
以上工具的功能可分為兩類:
1. 任務(wù)管理器(Task Runner)
Gulp / Grunt是一種任務(wù)管理工具挤忙,能夠優(yōu)化前端工作流程。比如自動(dòng)刷新頁(yè)面谈喳、combo册烈、壓縮css、js婿禽、編譯less等等赏僧。簡(jiǎn)單來(lái)說(shuō),就是使用Gulp/Grunt扭倾,然后配置你需要的插件次哈,就可以把以前需要手工做的事情讓它幫你做了。
2. 模塊化解決方案
2.1 在線模塊方案
Seajs/Requirejs是一種在線"編譯" 模塊的方案吆录,相當(dāng)于在頁(yè)面上加載一個(gè) CMD/AMD 解釋器。這樣瀏覽器就認(rèn)識(shí)了 define琼牧、exports恢筝、module 這些東西。也就實(shí)現(xiàn)了模塊化巨坊。
2.2 模塊打包器
Browserify / Webpack / Rollup是一個(gè)預(yù)編譯模塊的方案撬槽,相比于上面 ,這個(gè)方案更加智能趾撵。沒(méi)用過(guò)browserify侄柔,這里以webpack為例。首先占调,它是預(yù)編譯的暂题,不需要在瀏覽器中加載解釋器。另外究珊,你在本地直接寫JS沼琉,不管是 AMD / CMD / ES6 風(fēng)格的模塊化腻扇,它都能認(rèn)識(shí),并且編譯成瀏覽器認(rèn)識(shí)的JS。以上三個(gè)工具都能完成對(duì)lib庫(kù)的打包虫碉,React,Vue罩驻,Ember悔常,Preact,D3瞬浓,Three.js初婆,Moment 以及其他許多知名的庫(kù)都使用 Rollup 。Rollup使用Tree Shaking
技術(shù)精簡(jiǎn)Lib庫(kù),這個(gè)最新的Webpack也能做到烟逊,但是從最終的打包代碼中分析渣窜,Rollup做好的包會(huì)更純凈一些,性能更好一些宪躯。
4. Webpack的構(gòu)建思路乔宿?
一切都是模塊
就像JS文件可以視作“模塊”一樣,其他所有的一切(CSS访雪,圖片详瑞,HTML)都可以被視作模塊。也就是說(shuō)臣缀,你可以require("myJSfile.js")
或者require("myCSSfile.css")
坝橡。這意味著我們可以把任何靜態(tài)資源分割成可控的模塊,以供重復(fù)使用等不同的操作精置。
只加載“你需要的”和“你何時(shí)需要”的
典型的模塊加載器會(huì)把所有的模塊最終打包生成一個(gè)巨大的“bundle.js”文件计寇。但在很多實(shí)際的項(xiàng)目當(dāng)中,這個(gè)“bundle.js”文件體積可能會(huì)達(dá)到10MB~15MB脂倦,并且會(huì)一直不停進(jìn)行加載番宁!所以Webpack通過(guò)大量的特性去分割你的代碼,生成多個(gè)“bundle”片段赖阻,并且異步地加載項(xiàng)目的不同部分蝶押,因此只會(huì)為你加載“你需要的”和“你何時(shí)需要”的部分。
入門篇
入門主要是對(duì)Webpack配置的解讀及常見(jiàn)問(wèn)題點(diǎn)解答火欧。
1. 如何區(qū)分Webpack構(gòu)建形式(開(kāi)發(fā)環(huán)境 VS 生產(chǎn)環(huán)境)棋电?
按照Webpack文檔中說(shuō)明的,使用配置對(duì)象編寫配置文檔苇侵,一般Webpack構(gòu)建分為兩個(gè)環(huán)境:開(kāi)發(fā)赶盔、發(fā)布。因此需要三個(gè)配置:
-
webpack.base.config.js
:基礎(chǔ)公共 -
webpack.dev.config.js
:開(kāi)發(fā)模式 -
webpack.prod.config.js
:發(fā)布模式
通過(guò)webpack-merge
將配置合并榆浓,使用pkg.script
管理構(gòu)建任務(wù)招刨。
為了在window和max上都能無(wú)縫兼容,這里使用了 cross-env 配置環(huán)境變量哀军,例如:
"scripts": {
"clear": "rm -rf build&& mkdir build",
"start": "npm run clear&& cross-env NODE_ENV=development webpack-dev-server --host 0.0.0.0 --devtool eval --progress --color --profile",
"deploy": "npm run pre&& npm run clear&& cross-env NODE_ENV=production webpack -p --progress"
}
代碼中可使用如下方式判斷:
if (process.env.NODE_ENV === 'development') {
// ...
}
2. Entry在MPA下如何寫沉眶?
MPA是Muti Page Application的縮寫,指多頁(yè)應(yīng)用程序杉适。這種需求常見(jiàn)于:
- 多頁(yè)靜態(tài)項(xiàng)目編寫
- 多頁(yè)后端渲染模板編寫
如果頁(yè)面不多谎倔,且模板路徑無(wú)規(guī)律的話,使用對(duì)象語(yǔ)法定義Entry配置猿推,具體Entry配置參考文檔片习。
如果頁(yè)面結(jié)構(gòu)已約定捌肴,且頁(yè)面眾多,這里建議使用JS代碼獲取入口路徑藕咏,比如像這個(gè)項(xiàng)目express-hbs-webpack-demo状知,約定了chunkName和入口JSindex: .../views/index/main.js
| ├── views // 前端各個(gè)頁(yè)面
| | |── common // 公共Partials
| | |── layout.hbs
| | |── index // 主頁(yè)
| | | |── partials
| | | | |── content.hbs // 頁(yè)面Partials
| | | | |── xxxxxxxx.hbs // 其余頁(yè)面Partials
| | | | |── resource.ejs // webpack的 HTMLHtmlWebpackPlugin 插件需要這個(gè)模板
| | | | └── resource.hbs // 最終生成的hbs資源片段
| | | |── index.hbs // 頁(yè)面主結(jié)構(gòu)
| | | |── main.js // 頁(yè)面入口js,約定只能是index.js或者main.js
| | | └── style.less // 當(dāng)前頁(yè)面的樣式孽查,支持scss/less/css等
| | └── xxxxx // 其余頁(yè)面
| └── app.js // 頁(yè)面公共部分饥悴,比如全局樣式及腳本,或者頁(yè)面初始化時(shí)的動(dòng)作
代碼示例如下(源碼):
/**
* read path of js entry files(for webpack entry)
* notice: the entry file name must be main.js or index.js, and only one exist
*/
exports.webpackEntry = function () {
var webpackEntry = {} // for webpack entry
glob.sync(`${config.viewsPath}/**/{main,index}.js`).forEach(function (entry) {
var tmp = entry.split('/').splice(-3)
var moduleName = tmp.slice(1, 2)[0]
// eg: entry -> {about: '/Users/xxx/xxx/express-here/client/views/about/main.js'}
webpackEntry[moduleName] = entry
})
return webpackEntry
4. 如何在移動(dòng)配置文件的同時(shí)不影響構(gòu)建結(jié)果盲再?
因?yàn)樵谂渲弥行枰褂孟鄬?duì)路徑獲取某些文件/模塊的位置西设,因此當(dāng)移動(dòng)Webpack配置時(shí)會(huì)影響資源引用,這個(gè)很好處理答朋,使用context
即可贷揽,這使得你的配置獨(dú)立于 CWD(current working directory - 當(dāng)前執(zhí)行路徑)。
context: path.resolve(__dirname, "app")
4. 靜態(tài)資源托管到CDN時(shí)梦碗,構(gòu)建時(shí)資源如何改寫禽绪?
需要看下文檔中對(duì)output.publicPath
的說(shuō)明。這里需要對(duì)三個(gè)常用屬性進(jìn)行解讀:
- filename:決定了每個(gè)輸出 bundle 的名稱
- path:這些 bundle 將寫入到 output.path 選項(xiàng)指定的目錄下洪规。
- publicPath:將上面兩個(gè)準(zhǔn)備的路徑字符串前面加上外部路徑印屁,比如某個(gè)CDN:
http://xx.xx.com/public/
,此時(shí)原資源位置:js/xx.xxxxx.bundle.js
-->http://xx.xx.com/public/js/xx.xxxxx.bundle.js
因此淹冰,增加publicPath
屬性就可改寫資源名稱外部路徑。
5. 構(gòu)建Lib庫(kù)使用Rollup還是Webpack巨柒?
建議使用Rollup打包樱拴,編譯最終生成的模塊干凈清晰。
6. 引入模塊時(shí)洋满,rules的匹配順序是晶乔?
module
是對(duì)import/require
引入的資源進(jìn)行匹配處理的配置項(xiàng)。
module.noParse
首先牺勾,如果定義了module.noParse
且匹配到時(shí)正罢,則不解析匹配的模塊,因此模塊內(nèi)部不應(yīng)該有:import
, require
, define
的調(diào)用驻民,或任何其他導(dǎo)入機(jī)制翻具,這樣做可以忽略大型的 library 可以提高構(gòu)建性能。比如:
noParse: /jquery|lodash/
// 從 webpack 3.0.0 開(kāi)始
noParse: function(content) {
return /jquery|lodash/.test(content);
}
module.rules
其次回还,如果上述未匹配到裆泳,則進(jìn)行module.rules
匹配。數(shù)組中對(duì)象可通過(guò)屬性:test
, include
, exclude
和 resource
對(duì)資源匹配柠硕。
這里需要注意工禾,Webpack執(zhí)行最后一次匹配到的rule配置运提。因此,rule配置順序影響構(gòu)建闻葵,無(wú)用的rule全部去掉民泵。
7. 代碼中import/require
找模塊的過(guò)程?
這個(gè)問(wèn)題也是對(duì)“模塊解析(Module Resolution)”過(guò)程的提問(wèn)槽畔。
Webpack使用enhanced-resolve來(lái)解析文件路徑栈妆。
1. 絕對(duì)路徑
不需要解析
2. 相對(duì)路徑
由當(dāng)前文件的上下文生成絕對(duì)路徑,之后轉(zhuǎn)到第一種情況竟痰。
3. 模塊路徑
import "module"; // 文件
import "module/lib/file"; // 文件夾
模塊將在 resolve.modules 中指定的所有目錄內(nèi)搜索签钩。
- 在
resolve.alias
查找有沒(méi)匹配到的別名,如果匹配到則替換路徑坏快,繼續(xù)向下 - 如果路徑指向文件:
- 如果文件自帶拓展名铅檩,則直接引用
- 否則,使用
resolve.extensions
作為拓展名解析
- 如果路徑指向文件夾:
- 如果文件夾中包含 package.json 文件莽鸿,則按照順序查找
resolve.mainFields
配置選項(xiàng)中指定的字段(默認(rèn)為index)昧旨。并且 package.json 中的第一個(gè)這樣的字段確定文件路徑。 - 如果 package.json 文件不存在或者 package.json 文件中的 main 字段沒(méi)有返回一個(gè)有效路徑祥得,則按照順序查找
resolve.mainFields
配置選項(xiàng)中指定的文件名兔沃,看是否能在import/require
目錄下匹配到一個(gè)存在的文件名。 - 文件擴(kuò)展名通過(guò)
resolve.mainFields
選項(xiàng)采用類似的方法進(jìn)行解析级及。
- 如果文件夾中包含 package.json 文件莽鸿,則按照順序查找
8. 外部依賴如何處理乒疏?
比如公共模塊不希望打包到vendor中,而是通過(guò)CDN的方式引入的時(shí)候饮焦。這里參考下externals
的配置怕吴。
配置:
externals: {
jquery: 'jQuery'
}
代碼中:
import $ from 'jquery';
說(shuō)明:
jQuery
為<script>
中的引入全局變量window.jQuery
。jquery
為代碼中的引入名稱县踢。
9. Webpack管理的模塊如何掛載到window
上转绷?
參考這個(gè)loader:expose-loader。
8. 如何選擇SourceMap生成類型硼啤?
SourceMap 在devtool
屬性下設(shè)置:
- 開(kāi)發(fā)模式時(shí)议经,使用
source-map
,他提供全套支持 - 發(fā)布模式時(shí)谴返,使用
cheap-module-eval-source-map
煞肾,不影響構(gòu)建速度
進(jìn)階篇
1. 長(zhǎng)效緩存怎么處理?
原則
一般來(lái)說(shuō)嗓袱,我們發(fā)布到線上的資源都會(huì)再次進(jìn)行迭代開(kāi)發(fā)扯旷,或者增加新需求。為了保證用戶瀏覽的流暢性索抓,服務(wù)端會(huì)對(duì)靜態(tài)資源進(jìn)行長(zhǎng)效緩存钧忽。這里我們希望:
- 新需求上線對(duì)緩存變更影響越小越好毯炮,最小化用戶本地更新,減少服務(wù)器請(qǐng)求帶寬
- 用戶本地緩存不能和新上線的資源產(chǎn)生沖突
- 資源和頁(yè)面在進(jìn)行升級(jí)時(shí)耸黑,不能出現(xiàn)資源引用的問(wèn)題(404)
傳統(tǒng)的方式使用資源名后面加query的方式處理緩存桃煎,比如:
var sourcePath = `http://xx.xx.com/public/js/index.js?time=${new Date().getTime()}`
var sourcePath = `http://xx.xx.com/public/js/index.js?v=${version()}`
類似的方式進(jìn)行防緩存處理,但是這樣的做法不便于管理大刊,且頁(yè)面和資源部署先后會(huì)出現(xiàn)沖突为迈。
因此,最暴力直接的方式是根據(jù)資源內(nèi)容改寫文件名的方式處理長(zhǎng)效緩存的問(wèn)題缺菌。當(dāng)資源發(fā)生變化時(shí)修改資源名稱葫辐,不變則不處理。使用這種方式伴郁,可以先替換靜態(tài)資源耿战,之后替換頁(yè)面,不會(huì)造成沖突焊傅。
以上的介紹建議閱讀這篇文章:用 webpack 實(shí)現(xiàn)持久化緩存剂陡。我這里總結(jié)最終執(zhí)行的部分。
Webpack解讀
Webpack使用內(nèi)建模塊系統(tǒng)管理模塊引用狐胎,為了保證模塊內(nèi)容穩(wěn)定鸭栖,這里需要將webpack runtime/chunk清單(經(jīng)常改變)和模塊(相對(duì)固定)分離。
因此握巢,長(zhǎng)效緩存的核心就是:生成穩(wěn)定的模塊及模塊ID晕鹊,只有模塊內(nèi)容變動(dòng)才會(huì)改變模塊及模塊ID,只進(jìn)行必要的模塊及模塊ID變更暴浦。
措施
- 合理劃分公共模塊
- 提取vendor模塊
- 使用
CommonsChunkPlugin
提取公共模塊 - 提取manifest溅话,將模塊與webpack runtime/Manifest分離
- 使用chunkhash改寫js模塊的文件名, 使模塊唯一化
- 使用
HashedModuleIdsPlugin
穩(wěn)定模塊ID - 使用
import()
加載異步模塊 - 使用
inline-manifest-webpack-plugin
將 manifest文件 inline到html中處理
2. 模塊熱替換的思路是?
3. 如何優(yōu)化構(gòu)建性能肉渴?
4. 如何在編譯后的代碼中添加作者信息公荧, 在文件后面添加版權(quán)信息带射?
使用new webpack.BannerPlugin('版權(quán)所有同规,翻版必究')
處理
(完)