webpack通俗易懂(四)
- 1,source-map
- 2,devServer
- 3, https
- 4,eslint
- 5,git-hooks 與 husky
辛苦編寫良久,還望點(diǎn)贊鼓勵(lì)呦~
1,提高開發(fā)效率,完善團(tuán)隊(duì)開發(fā)規(guī)范
1.1,source-map
之前我們通過webpack, 將我們的源碼打包成了 bundle.js, 實(shí)際上客戶端(瀏覽器)讀取的是打包后的 bundle.js, 當(dāng)瀏覽器執(zhí)行代碼報(bào)錯(cuò)的時(shí)候,報(bào)錯(cuò)的信息自然也是 bundle 的內(nèi)容。我們?nèi)绾螌?bào)錯(cuò)信息(bundle錯(cuò)誤的語(yǔ)句及其所在行列)映射到源碼上,這時(shí)候我們就需要用 source-map了,webpack已經(jīng)內(nèi)置了sourcemap功能,我們只需要通過簡(jiǎn)單的配置,就可以開啟它
module.exports = {
// 開啟 source map
// 開發(fā)中推薦使用 'source-map' // 生產(chǎn)環(huán)境一般不開啟 sourcemap
devtool: 'source-map'
}
當(dāng)我們執(zhí)行打包命令后,我們發(fā)現(xiàn)bundle的最后一行總是會(huì)多出一個(gè)注釋,指向打包出的bundle.map.js(sourcemap文件)。 sourcemap文件用來描述源碼文件和 bundle 文件的代碼位置映射關(guān)系?;谒?,我們將bundle文件的錯(cuò)誤信息映射到源碼文件上。
除開'source-map'外,還可以基于我們的需求設(shè)置其他值,webpack——devtool一共提供了7種SourceMap模式:
- eval:每個(gè)module會(huì)封裝到 eval 里包裹起來執(zhí)行,并且會(huì)在末尾追加注釋 //@ sourceURL.
- source-map:生成一個(gè)SourceMap文件
- hidden-source-map:和 source-map一樣,但不會(huì)在 bundle 末尾追加注釋
- inline-source-map:生成一個(gè) DataUrl 形式的 SourceMap 文件.
- eval-source-map:每個(gè)module會(huì)通過eval()來執(zhí)行,并且生成一個(gè)DataUrl形式的 SourceMap
- cheap-source-map:生成一個(gè)沒有列信息(column-mappings)的SourceMaps文 件,不包含loader的 sourcemap(譬如 babel 的 sourcemap)
- cheap-module-source-map:生成一個(gè)沒有列信息(column-mappings)的SourceMaps文件,同時(shí) loader 的 sourcemap 也被簡(jiǎn)化為只包含對(duì)應(yīng)行的。
要注意的是,生產(chǎn)環(huán)境我們一般不會(huì)開啟sourcemap功能,主要有兩點(diǎn)原因:
- 通過bundle和sourcemap文件,可以反編譯出源碼————也就是說,線上產(chǎn)物有soucemap文件的話,就意味著有暴漏源碼的風(fēng)險(xiǎn)。
- 我們可以觀察到,sourcemap文件的體積相對(duì)比較巨大,這跟我們生產(chǎn)環(huán)境的追求不同(生產(chǎn)環(huán)境追求更小更輕量的bundle)。
有時(shí)候我們期望能第一時(shí)間通過線上的錯(cuò)誤信息,來追蹤到源碼位置,從而快速解決掉bug以減輕損失。但又不希望sourcemap文件報(bào)漏在生產(chǎn)環(huán)境,有什么比較好的方案呢?
1.2,devServer
開發(fā)環(huán)境下,我們往往需要啟動(dòng)一個(gè)web服務(wù),方便我們模擬一個(gè)用戶從瀏覽器中訪問我們的web服務(wù),讀取我們的打包產(chǎn)物,以觀測(cè)我們的代碼在客戶端的表現(xiàn)。webpack內(nèi)置了這樣的功能,我們只需要簡(jiǎn)單的配置就可以開啟它。
安裝 devServer
yarn add -D webpack-dev-server
devServer.proxy基于強(qiáng)大的中間件 http-proxy-middleware 實(shí)現(xiàn)的,因此它支持很多的配置項(xiàng),我們基于此,可以做應(yīng)對(duì)絕大多數(shù)開發(fā)場(chǎng)景的定制化配置。
基礎(chǔ)使用:
const path = require('path')
devServer: {
static: {
directory: path.join(__dirname, 'dist')
}, // 默認(rèn)是把dist目錄作為web服務(wù)的根目錄
compress: true, // 可選擇開啟gzips壓縮功能,對(duì)應(yīng)靜態(tài)資源請(qǐng)求的響應(yīng)頭里的Content-Encoding: gzip
port: 3000, // 端口號(hào)
},
為了方便,我們配置一下工程的腳本命令,在package.json的scripts里:
{
"scripts": {
"dev": "webpack serve --mode development"
}
}
- 1.2.1, 添加響應(yīng)頭
有些場(chǎng)景需求下,我們需要為所有響應(yīng)添加headers, 來對(duì)資源的請(qǐng)求和響應(yīng)打入標(biāo)志,以便做一些安全防范,或者方便發(fā)生異常后做請(qǐng)求的鏈路追蹤。比如:
module.exports = {
devServer: {
headers: {
'X-Token': 'ZlcjLCe+sAW1S4QC8Z'
}
}
}

- 1.2.2, 開啟代理
我們打包出的js bundle里有時(shí)會(huì)含有一些對(duì)特定接口的網(wǎng)絡(luò)請(qǐng)求(ajax/fetch). 要注意,此時(shí)客戶端地址是在 http://localhost:3000/ 下,假設(shè)我們的接口來自 http://localhost:4001/,那制臺(tái)就會(huì)報(bào)錯(cuò)跨域,在開發(fā)環(huán)境下,我們可以使用devServer自帶的proxy功能來解決這個(gè)問題。
我們新搭建一個(gè)服務(wù),在當(dāng)前項(xiàng)目下新建 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')
})
再次打開一個(gè)終端執(zhí)行node server.js,啟動(dòng)服務(wù)

瀏覽器輸入:

下面我們開始請(qǐng)求,請(qǐng)求我們可以使用瀏覽器自帶的方法fetch,這個(gè)方法返回的是一個(gè)promise
fetch('/api/user')
.then(val => val.text()) // res.text()可以把返回的結(jié)果變成文本)
.then(res => {
console.log(res)
})

如何解決上面跨域的問題呢:
module.exports = {
//...
devServer: {
proxy: {
'/api': 'http://localhost:4001'
}
}
}
// 如果用戶在地址欄一旦請(qǐng)求了一個(gè)資源叫 /api 的話,我們就給他指向到 http://localhost:4001 服務(wù)器上去
現(xiàn)在對(duì) /api/user 的請(qǐng)求會(huì)將請(qǐng)求代理到 http://localhost:4001/api/user。如果不希望傳遞 /api, 則可以重寫路徑:
proxy: {
'/api': {
target: 'http://localhost:4001',
pathRewrite: {
'^/api': '/' // 這里可以是'/'也可以是''
}
}
}

- 1.2.3,https
默認(rèn)情況下,將不接受在 HTTPS 上運(yùn)行且證書無效的后端服務(wù)器。如果想讓我們的本地http服務(wù)改為https服務(wù),可以這樣配置:
devServer: {
https: true
}
重新啟動(dòng)服務(wù):npx webpack, 我們發(fā)現(xiàn)訪問http://localhost:port是無法訪問我們的服務(wù)的,我們需要在地址欄里加前綴: https,注意: 由于默認(rèn)配置使用的是自簽名證書,所以有的瀏覽器會(huì)告訴你是不安全的,但我們依然可以繼續(xù)訪問它。當(dāng)然我們也可以提供自己的證書:
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默認(rèn)自帶https自簽名證書,當(dāng)然我們?nèi)匀豢梢酝ㄟ^https配置項(xiàng)來使用自己的證書

- 1.2.5, historyApiFallback
如果我們的應(yīng)用是個(gè)SPA(單頁(yè)面應(yīng)用),當(dāng)路由到/some 時(shí)(可以直接在地址欄里輸入),會(huì)發(fā)現(xiàn)此時(shí)刷新頁(yè)面后,控制臺(tái)會(huì)報(bào)錯(cuò):
GET http://localhost:3000/some 404 (Not Found)
此時(shí)打開network,刷新并查看,就會(huì)發(fā)現(xiàn)問題所在———瀏覽器把這個(gè)路由當(dāng)作了靜態(tài)資源的地址去請(qǐng)求,然而我們并沒有打包出/some這樣的資源,所以這個(gè)訪問無疑是404的。如何解決它?我們可以通過配置來提供頁(yè)面代替任何404的靜態(tài)資源響應(yīng):
module.exports = {
//...
devServer: {
historyApiFallback: true
}
}
此時(shí)重啟服務(wù)刷新后發(fā)現(xiàn)請(qǐng)求變成了index.html, 當(dāng)然, 在多數(shù)業(yè)務(wù)場(chǎng)景下,我們需要根據(jù)不同的訪問路徑定制替代的頁(yè)面,這種情況下,我們可以使用rewrites這個(gè)配置項(xiàng)。 類似這樣:
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ù)器主機(jī)
如果我們?cè)陂_發(fā)環(huán)境中起了一個(gè)devserve服務(wù),并希望在同一局域網(wǎng)下的同事也能訪問它,只需要配置:
devServer: {
host: '0.0.0.0'
}
這時(shí)候,如果我們的同事跟我們處在同一局域網(wǎng)下,就可以通過局域網(wǎng)ip來訪問我們的服務(wù) 啦

1.3, 模塊熱替換與熱加載
- 模塊熱替換
模塊熱替換(HMR - hot module replacement)功能會(huì)在應(yīng)用程序運(yùn)行過程中,
替換、添加或刪除 模塊,而無需重新加載整個(gè)頁(yè)面
module.exports = {
//...
devServer: {
hot: true,
},
}
HMR 加載樣式,如果我們配置了style-loader,那么現(xiàn)在已經(jīng)同樣支持樣式文件的
熱替換功能了
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
}
這是因?yàn)閟tyle-loader的實(shí)現(xiàn)使用了module.hot.accept,在CSS依賴模塊更新之后,
會(huì)對(duì) style 標(biāo)簽打補(bǔ)丁。從而實(shí)現(xiàn)了這個(gè)功能。
熱加載(文件更新時(shí),自動(dòng)刷新我們的服務(wù)和頁(yè)面) 新版的webpack-dev-server
默認(rèn)已經(jīng)開啟了熱加載的功能。 它對(duì)應(yīng)的參數(shù)是devServer.liveReload,默認(rèn)為
true。 注意,如果想要關(guān)掉它,要將liveReload設(shè)置為false的同時(shí),也要關(guān)掉
hot

module.exports = {
//...
devServer: {
liveReload: false, //默認(rèn)為true,即開啟熱更新功能。
},
};
1.4,eslint
eslint是用來掃描我們所寫的代碼是否符合規(guī)范的工具。往往我們的項(xiàng)目是多人協(xié)作開發(fā)的,我們期望統(tǒng)一的代碼規(guī)范,這時(shí)候可以讓eslint來對(duì)我們進(jìn)行約束。嚴(yán)格意義上來說,eslint配置跟webpack無關(guān),但在工程化開發(fā)環(huán)境中,它往往是不可或缺的
yarn add eslint -D
npx eslint --init
我們可以看到控制臺(tái)里的展示:

并生成了一個(gè)配置文件(.eslintrc.json),這樣我們就完成了eslint的基本規(guī)則配置。 eslint配置文件里的配置項(xiàng)含義如下:
- env 指定腳本的運(yùn)行環(huán)境。每種環(huán)境都有一組特定的預(yù)定義全局變量。此處使用的 browser 預(yù)定義了瀏覽器環(huán)境中的全局變量,es6 啟用除了 modules 以外的所有 ECMAScript 6 特性(該選項(xiàng)會(huì)自動(dòng)設(shè)置 ecmaVersion 解析器選項(xiàng)為 6)。
- globals 腳本在執(zhí)行期間訪問的額外的全局變量。也就是 env 中未預(yù)定義,但我
們又需要使用的全局變量。
- globals 腳本在執(zhí)行期間訪問的額外的全局變量。也就是 env 中未預(yù)定義,但我
- extends 檢測(cè)中使用的預(yù)定義的規(guī)則集合。
- rules 啟用的規(guī)則及其各自的錯(cuò)誤級(jí)別,會(huì)合并 extends 中的同名規(guī)則,定義沖
突時(shí)優(yōu)先級(jí)更高。
- rules 啟用的規(guī)則及其各自的錯(cuò)誤級(jí)別,會(huì)合并 extends 中的同名規(guī)則,定義沖
- parserOptions ESLint 允許你指定你想要支持的 JavaScript 語(yǔ)言選項(xiàng)。
ecmaFeatures 是個(gè)對(duì)象,表示你想使用的額外的語(yǔ)言特性,這里 jsx 代表啟用 JSX。ecmaVersion 用來指定支持的 ECMAScript 版本 。默認(rèn)為 5,即僅支持 es5,你可以使用 6、7、8、9 或 10 來指定你想要使用的 ECMAScript 版本。你 也可以用使用年份命名的版本號(hào)指定為 2015(同 6),2016(同 7),或 2017(同 8)或 2018(同 9)或 2019 (same as 10)。上面的 env 中啟用了 es6,自動(dòng)設(shè)置了ecmaVersion 解析器選項(xiàng)為 6。 plugins plugins 是一個(gè) npm 包,通常輸出 eslint 內(nèi)部未定義的規(guī)則實(shí)現(xiàn)。rules 和 extends 中定義的規(guī)則, 并不都在 eslint 內(nèi)部中有實(shí)現(xiàn)。比如 extends 中的 plugin:react/recommended,其中定義了規(guī)則開關(guān)和等級(jí),但是這些規(guī)則如何 生效的邏輯是在其對(duì)應(yīng)的插件 ‘react’ 中實(shí)現(xiàn)的
- parserOptions ESLint 允許你指定你想要支持的 JavaScript 語(yǔ)言選項(xiàng)。
新建項(xiàng)目文件夾,并在繼承終端中打開:
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就可以檢測(cè)出代碼是否存在語(yǔ)法錯(cuò)誤等規(guī)范問題,我們可以在控制臺(tái)看出哪些不符合規(guī)范,這樣并不直觀,我們可以通過安裝vscode插件eslint醒目的看到代碼上有紅色波浪線??。
我們可以通過命令來讓elisnt檢測(cè)代碼——在我們的package.scripts里添加一個(gè)腳本命令:
// package.json
{
"scripts": {
"eslint": "eslint ./src"
}
}
然后執(zhí)行
eslint src
以上我們直觀的看到代碼規(guī)范錯(cuò)誤是通過安裝vscode插件,如果不想使用插件,又想實(shí)時(shí)提示報(bào)錯(cuò),我們可以結(jié)合 webpack 的打包編譯功能來實(shí)現(xiàn)。
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node-modules/,
use: ['babel-loader', 'eslint-loader']
}
]
因?yàn)槲覀兪褂昧薲evServer,因此需要在devServer下添加一個(gè)對(duì)應(yīng)的配置參數(shù):
module.exports = {
devServer: {
liveReload: false, //默認(rèn)為true,即開啟熱更新功能。
}
}
現(xiàn)在我們就可以實(shí)時(shí)地看到代碼里的不規(guī)范報(bào)錯(cuò)啦
1.5,git-hooks 與 husky
為了保證團(tuán)隊(duì)里的開發(fā)人員提交的代碼符合規(guī)范,我們可以在開發(fā)者上傳代碼時(shí)進(jìn)行校驗(yàn)。我們常用 husky 來協(xié)助進(jìn)行代碼提交時(shí)的 eslint 校驗(yàn)。在使用husky之前,我們先來研究一下 git-hooks
我們回到項(xiàng)目的根目錄下。git init, ls -a 命令 ———— “-a”可以顯示隱藏目錄(目錄名的第一位是.)

接來下我們進(jìn)入到這個(gè)文件夾,進(jìn)一步查看它內(nèi)部的內(nèi)容
cd .git
ls -a

可以看到,當(dāng)前目錄下存在一個(gè)hooks文件夾,顧名思義,這個(gè)文件夾提供了git 命令相關(guān)的鉤子
cd hooks
ls -a

那我們可以看到有很多git命令相關(guān)的文件名。比如"pre-commit.sample pre- push.sample"。 回到正題——我們期望在git提交(commit)前,對(duì)我們的代碼進(jìn)行檢測(cè),如果不能通 過檢測(cè),就無法提交我們的代碼, 這個(gè)動(dòng)作的時(shí)機(jī)應(yīng)該是?————"pre commit", 也就是 commit之前。
現(xiàn)在,我們查看一下pre-commit.sample的內(nèi)容
# cat命令可以查看一個(gè)文件的內(nèi)容
cat pre-commit.sample
OK,它返回了這樣的內(nèi)容,是一串shell注釋。翻譯過來大概意思是,這是個(gè)示例鉤子,然后我們看到了這一句話
# To enable this hook, rename this file to "pre-commit"
意思是要啟用這個(gè)鉤子的話,我們就把這個(gè)文件的后綴名去掉。
雖然這樣對(duì)我們本地來講是可行的,但要注意,.git文件夾的改動(dòng)無法同步到遠(yuǎn)端倉(cāng)庫(kù)
所以我們期望將git-hook的執(zhí)行權(quán)移交到外面來
好的,我們回到項(xiàng)目的根目錄下,然后我們新建一個(gè)文件夾,暫時(shí)命名為".mygithooks" 然后在此文件夾下,新增一個(gè)git-hook文件,命名為"pre-commit",并寫入以下內(nèi)容:
echo pre-commit執(zhí)行啦
好了,我們新建了自己的git-hook,但此時(shí)git并不能識(shí)別。下面我們執(zhí)行這行命令:
# 項(xiàng)目根目錄下
git config core.hooksPath .mygithooks
上述命令給我們自己的文件,配置了git-hook的執(zhí)行權(quán)限。
但這個(gè)時(shí)候我們git commit的話,可能會(huì)報(bào)這樣的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`
這是因?yàn)槲覀兊牟僮飨到y(tǒng)沒有給出這個(gè)文件的可執(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)控制臺(tái)日志會(huì)先打印 “pre-commit執(zhí)行啦”。 這意味著成功啦!
總結(jié):
也就是說,我們搞git-hook的話,要分三步走:
- 新增任意名稱文件夾以及文件pre-commit(這個(gè)文件名字比如跟要使用的git- hook名字一致)!
- 執(zhí)行以下命令來移交git-hook的配置權(quán)限
git config core.hooksPath .mygithooks
- 給這個(gè)文件添加可執(zhí)行權(quán)限:
chmod +x .mygithooks/pre-commit
然后就成功啦。 這時(shí)候我們可以在pre-commit里寫任意腳本,比如:
eslint src
當(dāng)eslint掃描代碼,出現(xiàn)error時(shí),會(huì)在結(jié)束掃描時(shí)將退出碼設(shè)為大于0的數(shù)字。 也就是會(huì)報(bào)錯(cuò),這時(shí)候commit就無法往下執(zhí)行啦,我們成功的攔截了此次錯(cuò)誤操作。
husky
husky在升級(jí)到7.x后,做了跟我們上述同樣的事。 安裝它之前,我們需要在package.json中的script里,先添加
"sctript": {
//...others
"prepare": "husky install"
}
prepare是一個(gè)npm鉤子,意思是安裝依賴的時(shí)候,會(huì)先執(zhí)行husky install命令。 這個(gè)命令就做了上述的123這三件事! 我們安裝了7.x的husky會(huì)發(fā)現(xiàn),項(xiàng)目根目錄下生成了.husky的文件夾。 當(dāng)然,7.x的husky似乎是有bug的,如果不能正常使用,那么我們只需要驗(yàn)證兩件事:
- 是否移交了git-hook的配置權(quán)限?
執(zhí)行命令 "git config --list"查看core.hooksPath配置是否存在,是否正確指向 了.husky。
如果沒有,我們只需要手動(dòng)的給加上就行:
- 是否移交了git-hook的配置權(quán)限?
git config core.hooksPath .husky
- 是否是可執(zhí)行文件? 參考上述總結(jié)中的3即可 這時(shí)我們的husky就正常了