webpack通俗易懂(四)
- 1唠粥,source-map
- 2疏魏,devServer
- 3, https
- 4,eslint
- 5晤愧,git-hooks 與 husky
辛苦編寫良久大莫,還望點贊鼓勵呦~
1,提高開發(fā)效率官份,完善團隊開發(fā)規(guī)范
1.1只厘,source-map
之前我們通過webpack, 將我們的源碼打包成了 bundle.js, 實際上客戶端(瀏覽器)讀取的是打包后的 bundle.js, 當瀏覽器執(zhí)行代碼報錯的時候,報錯的信息自然也是 bundle 的內(nèi)容舅巷。我們?nèi)绾螌箦e信息(bundle錯誤的語句及其所在行列)映射到源碼上羔味,這時候我們就需要用 source-map
了,webpack已經(jīng)內(nèi)置了sourcemap功能钠右,我們只需要通過簡單的配置赋元,就可以開啟它
module.exports = {
// 開啟 source map
// 開發(fā)中推薦使用 'source-map' // 生產(chǎn)環(huán)境一般不開啟 sourcemap
devtool: 'source-map'
}
當我們執(zhí)行打包命令后,我們發(fā)現(xiàn)bundle的最后一行總是會多出一個注釋飒房,指向打包出的bundle.map.js(sourcemap文件)搁凸。 sourcemap文件用來描述源碼文件和 bundle 文件的代碼位置映射關(guān)系∏橐伲基于它坪仇,我們將bundle文件的錯誤信息映射到源碼文件上。
除開'source-map'外垃你,還可以基于我們的需求設(shè)置其他值椅文,webpack——devtool一共提供了7種SourceMap模式:
- eval:每個module會封裝到 eval 里包裹起來執(zhí)行,并且會在末尾追加注釋 //@ sourceURL.
- source-map:生成一個SourceMap文件
- hidden-source-map:和 source-map一樣惜颇,但不會在 bundle 末尾追加注釋
- inline-source-map:生成一個 DataUrl 形式的 SourceMap 文件.
- eval-source-map:每個module會通過eval()來執(zhí)行皆刺,并且生成一個DataUrl形式的 SourceMap
- cheap-source-map:生成一個沒有列信息(column-mappings)的SourceMaps文 件,不包含loader的 sourcemap(譬如 babel 的 sourcemap)
- cheap-module-source-map:生成一個沒有列信息(column-mappings)的SourceMaps文件凌摄,同時 loader 的 sourcemap 也被簡化為只包含對應(yīng)行的羡蛾。
要注意的是,生產(chǎn)環(huán)境我們一般不會開啟sourcemap功能锨亏,主要有兩點原因:
- 通過bundle和sourcemap文件痴怨,可以反編譯出源碼————也就是說,線上產(chǎn)物有soucemap文件的話器予,就意味著有暴漏源碼的風險浪藻。
- 我們可以觀察到,sourcemap文件的體積相對比較巨大,這跟我們生產(chǎn)環(huán)境的追求不同(生產(chǎn)環(huán)境追求更小更輕量的bundle)乾翔。
有時候我們期望能第一時間通過線上的錯誤信息爱葵,來追蹤到源碼位置,從而快速解決掉bug以減輕損失。但又不希望sourcemap文件報漏在生產(chǎn)環(huán)境萌丈,有什么比較好的方案呢赞哗?
1.2,devServer
開發(fā)環(huán)境下辆雾,我們往往需要啟動一個web服務(wù)肪笋,方便我們模擬一個用戶從瀏覽器中訪問我們的web服務(wù),讀取我們的打包產(chǎn)物乾颁,以觀測我們的代碼在客戶端的表現(xiàn)涂乌。webpack內(nèi)置了這樣的功能,我們只需要簡單的配置就可以開啟它英岭。
安裝 devServer
yarn add -D webpack-dev-server
devServer.proxy基于強大的中間件 http-proxy-middleware 實現(xiàn)的湾盒,因此它支持很多的配置項,我們基于此诅妹,可以做應(yīng)對絕大多數(shù)開發(fā)場景的定制化配置罚勾。
基礎(chǔ)使用:
const path = require('path')
devServer: {
static: {
directory: path.join(__dirname, 'dist')
}, // 默認是把dist目錄作為web服務(wù)的根目錄
compress: true, // 可選擇開啟gzips壓縮功能,對應(yīng)靜態(tài)資源請求的響應(yīng)頭里的Content-Encoding: gzip
port: 3000, // 端口號
},
為了方便吭狡,我們配置一下工程的腳本命令尖殃,在package.json的scripts里:
{
"scripts": {
"dev": "webpack serve --mode development"
}
}
- 1.2.1, 添加響應(yīng)頭
有些場景需求下,我們需要為所有響應(yīng)添加headers, 來對資源的請求和響應(yīng)打入標志划煮,以便做一些安全防范送丰,或者方便發(fā)生異常后做請求的鏈路追蹤。比如:
module.exports = {
devServer: {
headers: {
'X-Token': 'ZlcjLCe+sAW1S4QC8Z'
}
}
}
- 1.2.2, 開啟代理
我們打包出的js bundle里有時會含有一些對特定接口的網(wǎng)絡(luò)請求(ajax/fetch). 要注意弛秋,此時客戶端地址是在 http://localhost:3000/ 下器躏,假設(shè)我們的接口來自 http://localhost:4001/,那制臺就會報錯跨域蟹略,在開發(fā)環(huán)境下登失,我們可以使用devServer自帶的proxy功能來解決這個問題。
我們新搭建一個服務(wù)挖炬,在當前項目下新建 server.js:
const http = require('http');
const app = http.createServer((req, res) => {
if (req.url === '/api/user') {
res.end('hello node')
}
})
app.listen(4001, 'localhost', () => {
console.log('localhost listening on 4001')
})
再次打開一個終端執(zhí)行node server.js揽浙,啟動服務(wù)
瀏覽器輸入:
下面我們開始請求,請求我們可以使用瀏覽器自帶的方法fetch意敛,這個方法返回的是一個promise
fetch('/api/user')
.then(val => val.text()) // res.text()可以把返回的結(jié)果變成文本)
.then(res => {
console.log(res)
})
如何解決上面跨域的問題呢:
module.exports = {
//...
devServer: {
proxy: {
'/api': 'http://localhost:4001'
}
}
}
// 如果用戶在地址欄一旦請求了一個資源叫 /api 的話馅巷,我們就給他指向到 http://localhost:4001 服務(wù)器上去
現(xiàn)在對 /api/user 的請求會將請求代理到 http://localhost:4001/api/user。如果不希望傳遞 /api草姻, 則可以重寫路徑:
proxy: {
'/api': {
target: 'http://localhost:4001',
pathRewrite: {
'^/api': '/' // 這里可以是'/'也可以是''
}
}
}
- 1.2.3令杈,https
默認情況下,將不接受在 HTTPS 上運行且證書無效的后端服務(wù)器碴倾。如果想讓我們的本地http服務(wù)改為https服務(wù),可以這樣配置:
devServer: {
https: true
}
重新啟動服務(wù):npx webpack, 我們發(fā)現(xiàn)訪問http://localhost:port是無法訪問我們的服務(wù)的,我們需要在地址欄里加前綴: https跌榔,注意: 由于默認配置使用的是自簽名證書异雁,所以有的瀏覽器會告訴你是不安全的,但我們依然可以繼續(xù)訪問它僧须。當然我們也可以提供自己的證書:
module.exports = {
devServer: {
https: {
cacert: './server.pem',
pfx: './server.pfx',
key: './server.key',
cert: './server.crt',
passphrase: 'webpack-dev-server',
requestCert: true,
}
}
}
- 1.2.4, http2
我們也可以不使用https纲刀,可以使用http2
如果想要配置http2,那么直接設(shè)置:
devServer: {
http2: true
}
http2默認自帶https自簽名證書担平,當然我們?nèi)匀豢梢酝ㄟ^https配置項來使用自己的證書
- 1.2.5, historyApiFallback
如果我們的應(yīng)用是個SPA(單頁面應(yīng)用)示绊,當路由到/some 時(可以直接在地址欄里輸入),會發(fā)現(xiàn)此時刷新頁面后暂论,控制臺會報錯:
GET http://localhost:3000/some 404 (Not Found)
此時打開network面褐,刷新并查看,就會發(fā)現(xiàn)問題所在———瀏覽器把這個路由當作了靜態(tài)資源的地址去請求取胎,然而我們并沒有打包出/some這樣的資源展哭,所以這個訪問無疑是404的。如何解決它?我們可以通過配置來提供頁面代替任何404的靜態(tài)資源響應(yīng):
module.exports = {
//...
devServer: {
historyApiFallback: true
}
}
此時重啟服務(wù)刷新后發(fā)現(xiàn)請求變成了index.html, 當然, 在多數(shù)業(yè)務(wù)場景下闻蛀,我們需要根據(jù)不同的訪問路徑定制替代的頁面匪傍,這種情況下,我們可以使用rewrites這個配置項觉痛。 類似這樣:
module.exports = {
//...
devServer: {
historyApiFallback: {
rewrites: [
{ from: /^\/$/, to: '/views/landing.html' },
{ from: /^\/subpage/, to: '/views/subpage.html' },
{ from: /./, to: '/views/404.html' },
]
}
}
}
- 1.2.6, 開發(fā)服務(wù)器主機
如果我們在開發(fā)環(huán)境中起了一個devserve服務(wù)役衡,并希望在同一局域網(wǎng)下的同事也能訪問它,只需要配置:
devServer: {
host: '0.0.0.0'
}
這時候薪棒,如果我們的同事跟我們處在同一局域網(wǎng)下手蝎,就可以通過局域網(wǎng)ip來訪問我們的服務(wù) 啦
1.3, 模塊熱替換與熱加載
- 模塊熱替換
模塊熱替換(HMR - hot module replacement)功能會在應(yīng)用程序運行過程中,
替換盗尸、添加或刪除 模塊柑船,而無需重新加載整個頁面
module.exports = {
//...
devServer: {
hot: true,
},
}
HMR 加載樣式,如果我們配置了style-loader泼各,那么現(xiàn)在已經(jīng)同樣支持樣式文件的
熱替換功能了
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
}
這是因為style-loader的實現(xiàn)使用了module.hot.accept鞍时,在CSS依賴模塊更新之后,
會對 style 標簽打補丁扣蜻。從而實現(xiàn)了這個功能逆巍。
熱加載(文件更新時,自動刷新我們的服務(wù)和頁面) 新版的webpack-dev-server
默認已經(jīng)開啟了熱加載的功能莽使。 它對應(yīng)的參數(shù)是devServer.liveReload锐极,默認為
true。 注意芳肌,如果想要關(guān)掉它灵再,要將liveReload設(shè)置為false的同時肋层,也要關(guān)掉
hot
module.exports = {
//...
devServer: {
liveReload: false, //默認為true,即開啟熱更新功能翎迁。
},
};
1.4栋猖,eslint
eslint是用來掃描我們所寫的代碼是否符合規(guī)范的工具。往往我們的項目是多人協(xié)作開發(fā)的汪榔,我們期望統(tǒng)一的代碼規(guī)范蒲拉,這時候可以讓eslint來對我們進行約束。嚴格意義上來說痴腌,eslint配置跟webpack無關(guān)雌团,但在工程化開發(fā)環(huán)境中,它往往是不可或缺的
yarn add eslint -D
npx eslint --init
我們可以看到控制臺里的展示:
并生成了一個配置文件(.eslintrc.json)士聪,這樣我們就完成了eslint的基本規(guī)則配置锦援。 eslint配置文件里的配置項含義如下:
- env 指定腳本的運行環(huán)境。每種環(huán)境都有一組特定的預(yù)定義全局變量戚嗅。此處使用的 browser 預(yù)定義了瀏覽器環(huán)境中的全局變量雨涛,es6 啟用除了 modules 以外的所有 ECMAScript 6 特性(該選項會自動設(shè)置 ecmaVersion 解析器選項為 6)。
- globals 腳本在執(zhí)行期間訪問的額外的全局變量懦胞。也就是 env 中未預(yù)定義替久,但我
們又需要使用的全局變量。
- globals 腳本在執(zhí)行期間訪問的額外的全局變量懦胞。也就是 env 中未預(yù)定義替久,但我
- extends 檢測中使用的預(yù)定義的規(guī)則集合躏尉。
- rules 啟用的規(guī)則及其各自的錯誤級別蚯根,會合并 extends 中的同名規(guī)則,定義沖
突時優(yōu)先級更高胀糜。
- rules 啟用的規(guī)則及其各自的錯誤級別蚯根,會合并 extends 中的同名規(guī)則,定義沖
- parserOptions ESLint 允許你指定你想要支持的 JavaScript 語言選項颅拦。
ecmaFeatures 是個對象,表示你想使用的額外的語言特性教藻,這里 jsx 代表啟用 JSX距帅。ecmaVersion 用來指定支持的 ECMAScript 版本 。默認為 5括堤,即僅支持 es5碌秸,你可以使用 6、7悄窃、8讥电、9 或 10 來指定你想要使用的 ECMAScript 版本。你 也可以用使用年份命名的版本號指定為 2015(同 6)轧抗,2016(同 7)恩敌,或 2017(同 8)或 2018(同 9)或 2019 (same as 10)。上面的 env 中啟用了 es6横媚,自動設(shè)置了ecmaVersion 解析器選項為 6纠炮。 plugins plugins 是一個 npm 包月趟,通常輸出 eslint 內(nèi)部未定義的規(guī)則實現(xiàn)。rules 和 extends 中定義的規(guī)則抗碰, 并不都在 eslint 內(nèi)部中有實現(xiàn)狮斗。比如 extends 中的 plugin:react/recommended,其中定義了規(guī)則開關(guān)和等級弧蝇,但是這些規(guī)則如何 生效的邏輯是在其對應(yīng)的插件 ‘react’ 中實現(xiàn)的
- parserOptions ESLint 允許你指定你想要支持的 JavaScript 語言選項颅拦。
新建項目文件夾,并在繼承終端中打開:
yarn init -y
npm install eslint -D
npx eslint --init
新建src -> app.js
// app.js
console.log('hello eslit')
npx eslint ./src
// eslintrc.json
{
"rules": {
"no-console": "warn" // 可以在rules中自定義約束規(guī)范
}
}
執(zhí)行npx eslint ./src就可以檢測出代碼是否存在語法錯誤等規(guī)范問題折砸,我們可以在控制臺看出哪些不符合規(guī)范看疗,這樣并不直觀,我們可以通過安裝vscode插件eslint醒目的看到代碼上有紅色波浪線??睦授。
我們可以通過命令來讓elisnt檢測代碼——在我們的package.scripts里添加一個腳本命令:
// package.json
{
"scripts": {
"eslint": "eslint ./src"
}
}
然后執(zhí)行
eslint src
以上我們直觀的看到代碼規(guī)范錯誤是通過安裝vscode插件两芳,如果不想使用插件,又想實時提示報錯去枷,我們可以結(jié)合 webpack 的打包編譯功能來實現(xiàn)怖辆。
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node-modules/,
use: ['babel-loader', 'eslint-loader']
}
]
因為我們使用了devServer,因此需要在devServer下添加一個對應(yīng)的配置參數(shù):
module.exports = {
devServer: {
liveReload: false, //默認為true删顶,即開啟熱更新功能竖螃。
}
}
現(xiàn)在我們就可以實時地看到代碼里的不規(guī)范報錯啦
1.5,git-hooks 與 husky
為了保證團隊里的開發(fā)人員提交的代碼符合規(guī)范逗余,我們可以在開發(fā)者上傳代碼時進行校驗特咆。我們常用 husky 來協(xié)助進行代碼提交時的 eslint 校驗。在使用husky之前录粱,我們先來研究一下 git-hooks
我們回到項目的根目錄下腻格。git init, ls -a 命令 ———— “-a”可以顯示隱藏目錄(目錄名的第一位是.)
接來下我們進入到這個文件夾,進一步查看它內(nèi)部的內(nèi)容
cd .git
ls -a
可以看到啥繁,當前目錄下存在一個hooks文件夾菜职,顧名思義,這個文件夾提供了git 命令相關(guān)的鉤子
cd hooks
ls -a
那我們可以看到有很多git命令相關(guān)的文件名旗闽。比如"pre-commit.sample pre- push.sample"酬核。 回到正題——我們期望在git提交(commit)前,對我們的代碼進行檢測宪睹,如果不能通 過檢測愁茁,就無法提交我們的代碼, 這個動作的時機應(yīng)該是?————"pre commit", 也就是 commit之前。
現(xiàn)在亭病,我們查看一下pre-commit.sample的內(nèi)容
# cat命令可以查看一個文件的內(nèi)容
cat pre-commit.sample
OK鹅很,它返回了這樣的內(nèi)容,是一串shell注釋罪帖。翻譯過來大概意思是促煮,這是個示例鉤子邮屁,然后我們看到了這一句話
# To enable this hook, rename this file to "pre-commit"
意思是要啟用這個鉤子的話,我們就把這個文件的后綴名去掉菠齿。
雖然這樣對我們本地來講是可行的佑吝,但要注意,.git文件夾的改動無法同步到遠端倉庫
所以我們期望將git-hook的執(zhí)行權(quán)移交到外面來
好的绳匀,我們回到項目的根目錄下芋忿,然后我們新建一個文件夾,暫時命名為".mygithooks" 然后在此文件夾下疾棵,新增一個git-hook文件,命名為"pre-commit"戈钢,并寫入以下內(nèi)容:
echo pre-commit執(zhí)行啦
好了,我們新建了自己的git-hook是尔,但此時git并不能識別殉了。下面我們執(zhí)行這行命令:
# 項目根目錄下
git config core.hooksPath .mygithooks
上述命令給我們自己的文件,配置了git-hook的執(zhí)行權(quán)限拟枚。
但這個時候我們git commit的話薪铜,可能會報這樣的waring,并且沒有執(zhí)行我們的
shell:
hint: The 'pre-commit' hook was ignored because it's not set as
executable.
hint: You can disable this warning with `git config
advice.ignoredHook false`
這是因為我們的操作系統(tǒng)沒有給出這個文件的可執(zhí)行權(quán)限恩溅。
因此我們得再執(zhí)行這樣一句命令:
chmod +x .mygithooks/pre-commit
ok!現(xiàn)在我們嘗試執(zhí)行g(shù)it add . && git commit -m "any meesage"隔箍。 我們發(fā)現(xiàn)控制臺日志會先打印 “pre-commit執(zhí)行啦”。 這意味著成功啦!
總結(jié):
也就是說暴匠,我們搞git-hook的話鞍恢,要分三步走:
- 新增任意名稱文件夾以及文件pre-commit(這個文件名字比如跟要使用的git- hook名字一致)!
- 執(zhí)行以下命令來移交git-hook的配置權(quán)限
git config core.hooksPath .mygithooks
- 給這個文件添加可執(zhí)行權(quán)限:
chmod +x .mygithooks/pre-commit
然后就成功啦。 這時候我們可以在pre-commit里寫任意腳本每窖,比如:
eslint src
當eslint掃描代碼帮掉,出現(xiàn)error時,會在結(jié)束掃描時將退出碼設(shè)為大于0的數(shù)字窒典。 也就是會報錯蟆炊,這時候commit就無法往下執(zhí)行啦,我們成功的攔截了此次錯誤操作瀑志。
husky
husky在升級到7.x后涩搓,做了跟我們上述同樣的事。 安裝它之前劈猪,我們需要在package.json中的script里昧甘,先添加
"sctript": {
//...others
"prepare": "husky install"
}
prepare是一個npm鉤子,意思是安裝依賴的時候战得,會先執(zhí)行husky install命令充边。 這個命令就做了上述的123這三件事! 我們安裝了7.x的husky會發(fā)現(xiàn),項目根目錄下生成了.husky的文件夾。 當然浇冰,7.x的husky似乎是有bug的贬媒,如果不能正常使用,那么我們只需要驗證兩件事:
- 是否移交了git-hook的配置權(quán)限?
執(zhí)行命令 "git config --list"查看core.hooksPath配置是否存在肘习,是否正確指向 了.husky际乘。
如果沒有,我們只需要手動的給加上就行:
- 是否移交了git-hook的配置權(quán)限?
git config core.hooksPath .husky
- 是否是可執(zhí)行文件? 參考上述總結(jié)中的3即可 這時我們的husky就正常了