webpack通俗易懂(四)

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)原因:

  1. 通過bundle和sourcemap文件,可以反編譯出源碼————也就是說,線上產(chǎn)物有soucemap文件的話,就意味著有暴漏源碼的風(fēng)險(xiǎn)。
  2. 我們可以觀察到,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'
    }
  }
}
image
  • 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ù)

image

瀏覽器輸入:

image

下面我們開始請(qǐng)求,請(qǐng)求我們可以使用瀏覽器自帶的方法fetch,這個(gè)方法返回的是一個(gè)promise

fetch('/api/user')
  .then(val => val.text()) // res.text()可以把返回的結(jié)果變成文本)
  .then(res => {
    console.log(res)
  })
image

如何解決上面跨域的問題呢:

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': '/'  // 這里可以是'/'也可以是''
    }
  }
}
image
  • 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,
    }
  }
}
image
  • 1.2.4, http2

我們也可以不使用https,可以使用http2

如果想要配置http2,那么直接設(shè)置:

devServer: {
   http2: true
}

http2默認(rèn)自帶https自簽名證書,當(dāng)然我們?nèi)匀豢梢酝ㄟ^https配置項(xiàng)來使用自己的證書

image
  • 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ù) 啦

image

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

image
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)里的展示:

image

并生成了一個(gè)配置文件(.eslintrc.json),這樣我們就完成了eslint的基本規(guī)則配置。 eslint配置文件里的配置項(xiàng)含義如下:

    1. 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)。
    1. globals 腳本在執(zhí)行期間訪問的額外的全局變量。也就是 env 中未預(yù)定義,但我
      們又需要使用的全局變量。
    1. extends 檢測(cè)中使用的預(yù)定義的規(guī)則集合。
    1. rules 啟用的規(guī)則及其各自的錯(cuò)誤級(jí)別,會(huì)合并 extends 中的同名規(guī)則,定義沖
      突時(shí)優(yōu)先級(jí)更高。
    1. 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)的

新建項(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”可以顯示隱藏目錄(目錄名的第一位是.)

image

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

cd .git
ls -a
image

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

cd hooks
ls -a
image

那我們可以看到有很多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的話,要分三步走:

    1. 新增任意名稱文件夾以及文件pre-commit(這個(gè)文件名字比如跟要使用的git- hook名字一致)!
    1. 執(zhí)行以下命令來移交git-hook的配置權(quán)限
git config core.hooksPath .mygithooks
    1. 給這個(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)證兩件事:

    1. 是否移交了git-hook的配置權(quán)限?
      執(zhí)行命令 "git config --list"查看core.hooksPath配置是否存在,是否正確指向 了.husky。
      如果沒有,我們只需要手動(dòng)的給加上就行:
git config core.hooksPath .husky
  1. 是否是可執(zhí)行文件? 參考上述總結(jié)中的3即可 這時(shí)我們的husky就正常了
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容