自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

我是如何調(diào)試 Webpack 問題的

開發(fā) 前端
思考一下,express 架構(gòu)的特點(diǎn)就是 —— 基于中間件的洋蔥模型,而中間件之間通過 next 函數(shù)調(diào)起下一個(gè)中間件。

[[415970]]

事情是這樣的,前兩天有個(gè)小伙伴問我:「為啥我的 webpack 運(yùn)行完看不到我寫的頁面,而是:」

嗯?文件列表頁?好吧,這種情況我似乎沒遇到過,一下子沒法給出答案,只能要來關(guān)鍵代碼:

重點(diǎn)看看 webpack.config.js 配置,用到 devServer + HMR 功能,其中:

  • Webpack 版本為 5.37.0
  • webpack-dev-server 版本為 3.11.2

看了半天,沒問題呀,給了幾個(gè)紙糊的建議還是解決不了問題,剛好在開會(huì)這事就暫且放下了。過了一會(huì),小伙伴興沖沖跑過來跟我說經(jīng)過一番盲猜,問題被解決了:

  • output.publicPath = '/' 時(shí)一切正常
  • output.publicPath = './' 時(shí)出錯(cuò),返回文件列表頁

啊?這玩意還會(huì)影響 devServer 的效果,直覺告訴我不應(yīng)該啊。

emmm,成功勾起我的好奇心了,雖然寫過一些 Webpack 源碼分析的文章,但 webpack-dev-server 確實(shí)不在我的知識(shí)范圍,好在我有秘籍《如何閱讀源碼 —— 以 Vetur 為例》,是時(shí)候展示真正的技術(shù)了!

第一步:定義問題

先復(fù)盤一下問題發(fā)生的過程:

  • webpack.config.js 同時(shí)配置了 ouput.publicPath 與 devServer
  • 運(yùn)行 npx webpack serve 啟動(dòng)開發(fā)服務(wù)器
  • 瀏覽器訪問 http://localhost:9000 沒有按預(yù)期返回用戶代碼,而是返回了文件列表頁面;但如果恢復(fù) output.publicPath 的默認(rèn)配置,一切如常

講道理, ouput.publicPath 應(yīng)該只是影響了最終產(chǎn)物引用的路徑,試試命令行工具運(yùn)行 curl 檢測(cè)首頁返回的內(nèi)容:

Tips:有時(shí)候可以試試?yán)@過瀏覽器的復(fù)雜邏輯,用最簡單的工具驗(yàn)證 http 請(qǐng)求返回的內(nèi)容。

可以看到,請(qǐng)求 http://localhost:9000 地址返回一大串 html 代碼,且頁面的 title 為 listing directory —— 也就是我們看到的文件列表頁面:

雖然不知道這是在那一層生成的,但可以肯定絕對(duì)不是我寫的,而且這是在 HTTP 層面發(fā)生的。

所以問題的核心就是:「為何 Webpack 的 output.publicPath 會(huì)影響 webpack-dev-server 的運(yùn)行效果」?

第二步:回顧背景

帶著問題我又 review 了一遍 Webpack 官方文檔。

publicPath配置

首先 output.publicPath 是這么描述的:

This is an important option when using on-demand-loading or loading external resources like images, files, etc. If an incorrect value is specified you'll receive 404 errors while loading these resources.

大意就是,這是一個(gè)控制按需加載或資源文件加載的選項(xiàng),如果對(duì)應(yīng)的路徑資源加載失敗時(shí)會(huì)返回 404。

 

 

 

嗐,其實(shí)這段描述就非常不明所以了,簡單理解 output.publicPath 會(huì)改變產(chǎn)物資源在 html 文件的路徑,比如說 Webpack 編譯完生成了 bundle.js 文件,默認(rèn)情況下寫到 html 的路徑是:

  1. <script src="bundle.js" /> 

如果設(shè)置了 output.publicPath 值,就會(huì)在路徑前增加前綴:

  1. <script src="${output.publicPath}/bundle.js" /> 

看起來很簡單。

devServer配置項(xiàng)

再來看看 devServer 配置:

This set of options is picked up by webpack-dev-server and can be used to change its behavior in various ways.

大意就是,devServer 配置最終會(huì)被 webpack-dev-server 消費(fèi),而 webpack-dev-server 提供了包括 HMR —— 模塊熱更新在內(nèi)的 web 服務(wù)。

感受一下,包括 vue-cli、create-react-app 之類的腳手架工具底層都依賴于 webpack-dev-server ,它的作用和重要性就可想而知了吧。

第三步:分析問題

按照現(xiàn)有的情報(bào),加上我對(duì) HTTP 協(xié)議的理解,可以基本推斷問題必然是出在 webpack-dev-server 框架處理首頁請(qǐng)求的邏輯上,大概率是 output.publicPath 屬性影響到首頁資源的判定邏輯,導(dǎo)致 webpack-dev-server 找不到對(duì)應(yīng)的資源文件,返回兜底的文件列表頁面。

嗯,我覺得靠譜,那就沿著這個(gè)思路挖一挖源碼,找到具體原因吧。

第四步:分析代碼

結(jié)構(gòu)分析

書上得來終須淺,debug 還需看源碼啊,啥都別說了先打開 webpack-dev-server 包的代碼看看內(nèi)容吧:

Tips: 讀者也可以試試 clone webpack-dev-server 倉庫的代碼,有驚喜~~

項(xiàng)目結(jié)構(gòu)并不復(fù)雜,按 Webpack 的習(xí)慣可以推斷主要代碼都在 lib 目錄:

cloc 是一個(gè)非常好用的代碼統(tǒng)計(jì)工具,官網(wǎng):https://www.npmjs.com/package/cloc

代碼量也就 2000 出頭,還好還好。

接下來再打開 package.json 文件,看看有哪些 dependency,一個(gè)個(gè)捋過去之后,與我們的問題強(qiáng)相關(guān)的依賴有:

  • express:應(yīng)用不用多介紹了吧
  • webpack-dev-middleware:這個(gè)應(yīng)該大多數(shù)人沒有注意過,從官網(wǎng)文檔判斷這是一個(gè)橋接 Webpack 編譯過程與 express 的中間件
  • serve-index:「提供特定目錄下文件列表頁面的 express 中間件」!?。?/li>
  • 按照這個(gè)描述,這鍋肯定出在 serve-index 的調(diào)用上啊,感覺離答案很近了。

局部分析

切入點(diǎn):驗(yàn)證 serve-index 包的作用

經(jīng)過上面的分析,雖然我還不知道問題具體出在哪里,但大致可以判定跟 serve-index 包強(qiáng)相關(guān),先搜一下 webpack-dev-server 在哪些地方引用這個(gè)包:

很幸運(yùn),只在 lib/Server.js 文件中用到,那就簡單多了,「靜態(tài)分析」調(diào)用語句前后的語句,大致上可以推導(dǎo)出:

  • serveIndex 調(diào)用被包裹在 this.app.use 內(nèi),推測(cè) this.app 指向 express 示例,use 函數(shù)用于注冊(cè)中間件,所以整個(gè) serveIndex 就是一個(gè)中間件
  • 除 setupStaticServeIndexFeature 外,Server 類型中還包含了其它命名為 setupXXXFeature 的函數(shù),基本上都用于添加 express 中間件,這些中間件組合拼裝出 webpack-dev-server 提供的 HMR、proxy、ssl 等功能

也看不出別的啥了,先做個(gè)對(duì)照實(shí)驗(yàn),運(yùn)行起來「動(dòng)態(tài)分析」代碼的實(shí)際執(zhí)行過程,驗(yàn)證到底是不是這個(gè)地方出錯(cuò)吧。先在 serveIndex 函數(shù)之前插入 debugger 語句,之后:

  • 先按照正常情況,也就是 output.publicPath = '/' 執(zhí)行 ndb npx webpack serve,結(jié)果是如常打開了頁面,沒有命中斷點(diǎn),沒有中斷
  • 再按照 ouput.publicPath = './' 執(zhí)行 ndb npx webpack serve,進(jìn)入斷點(diǎn):

Tips: ndb 是一個(gè)開箱即用的 node debugger 工具,不需要做任何配置就能調(diào)試 node 應(yīng)用,非常方便

OK,答案揭曉了,在 ouput.publicPath = './' 場(chǎng)景下會(huì)命中這個(gè)中間件,執(zhí)行 serveIndex 函數(shù)返回文件目錄列表,這很 make sense。

不過,作為一個(gè)有追求的程序員怎么會(huì)止步于此呢,我們繼續(xù)往下挖呀:到底是那一段代碼決定了流程會(huì)不會(huì)進(jìn)入 serveIndex 中間件?

切入點(diǎn):確定 serveIndex 的上游中間件

思考一下,express 架構(gòu)的特點(diǎn)就是 —— 基于中間件的洋蔥模型,而中間件之間通過 next 函數(shù)調(diào)起下一個(gè)中間件。

[[415971]]

嗯,有思路了,我們沿著 webpack-dev-server 的 middleware 隊(duì)列,找到 serveIndex 之前都有哪些中間件,分析這些中間件的代碼應(yīng)該就能解答:

到底是那一段代碼決定了流程會(huì)不會(huì)進(jìn)入 serveIndex 中間件?

但是,express 中間件架構(gòu)下,從 next 調(diào)用到實(shí)際中間件函數(shù)隔著很遠(yuǎn)的調(diào)用鏈路,很難通過斷點(diǎn)的調(diào)用堆棧判斷出上一級(jí)中間件,以及更更上一級(jí)中間件在哪里啊:

這時(shí)候不能硬剛,得換一個(gè)技巧了 —— 找到創(chuàng)建 express 示例的代碼,用魔法包裹住 use 函數(shù):

Tips: 這種技巧在某些復(fù)雜場(chǎng)景下特別有用,比如我在學(xué)習(xí) Webpack 源碼的時(shí)候,就經(jīng)常配合 Proxy 類對(duì) hook 植入 debugger 語句,追蹤鉤子被誰監(jiān)聽,在哪里被觸發(fā)

通過這種重寫函數(shù),植入斷點(diǎn)的方式,我們就能輕松追溯到 webpack-dev-server 用到了哪些中間件,以及中間件注冊(cè)的順序:

  1. setupCompressFeature => 注冊(cè)資源壓縮中間件 
  2. setupMiddleware => 注冊(cè) webpack-dev-middleware 中間件 
  3. setupStaticFeature => 注冊(cè)靜態(tài)資源服務(wù)中間件 
  4. setupServeIndexFeature => 注冊(cè) serveIndex 中間件 

可以看到,在當(dāng)前 Webpack 配置下總共注冊(cè)了這四個(gè)中間件函數(shù),按照 express 的執(zhí)行邏輯這四個(gè)中間件會(huì)按注冊(cè)順序從上往下執(zhí)行,所以 serveIndex 函數(shù)的直接上游就是 setupStaticFeature 注冊(cè)的靜態(tài)資源服務(wù)中間件了。

繼續(xù)看看 setupStaticFeature 函數(shù)的代碼:

這里只是調(diào)用標(biāo)準(zhǔn)化的 [express.static](https://expressjs.com/en/starter/static-files.html) 函數(shù),注入靜態(tài)資源服務(wù)功能,如果這個(gè)中間件運(yùn)行的時(shí)候按路徑找不到對(duì)應(yīng)的文件資源,會(huì)調(diào)用下一個(gè)中間件繼續(xù)處理請(qǐng)求,看起來跟我們的問題沒啥關(guān)系。

繼續(xù)往上,看看 setupMiddleware 函數(shù):

注冊(cè)了 webpack-dev-middleware,從名字就可以看出這個(gè)中間件跟 webpack-dev-server 應(yīng)該關(guān)系匪淺,那就繼續(xù)打開 webpack-dev-middleware 看看里面的代碼:

我去。。。也不少啊,這看起來太費(fèi)勁了,我只是想找到這個(gè) bug 的原因,沒必要全看吧!那就直接搜關(guān)鍵詞 publicPath 試試吧:

比較幸運(yùn),publicPath 關(guān)鍵字出現(xiàn)的頻率還是比較少的:

  • webpack-dev-middleware/lib/middleware.js 文件中被使用了 1 次
  • webpack-dev-middleware/lib/util.js 文件中被使用了 23 次

那,就先挑軟柿子捏,看看 middleware.js 文件中是怎么用的:

  1. const { getFilenameFromUrl } = require('./util'); 
  2.  
  3. module.exports = function wrapper(context) { 
  4.   return function middleware(req, res, next) { 
  5.     function goNext() { 
  6.       // ... 
  7.       resolve(next()); 
  8.     } 
  9.     // ... 
  10.     let filename = getFilenameFromUrl( 
  11.       context.options.publicPath, 
  12.       context.compiler, 
  13.       req.url 
  14.     ); 
  15.  
  16.     if (filename === false) { 
  17.       return goNext(); 
  18.     } 
  19.  
  20.     return new Promise((resolve) => { 
  21.       handleRequest(context, filename, processRequest, req); 
  22.       // ... 
  23.     }); 
  24.   }; 
  25. }; 

注意代碼中有一個(gè)邏輯,就是調(diào)用 util 文件的 getFilenameFromUrl 函數(shù),并判斷返回的 filename 值是否為 false,是的話調(diào)用 next 函數(shù),這看起來很像那么回事了!

那就繼續(xù)進(jìn)去看看 getFilenameFromUrl 的代碼:

逐行分析下來,注意看紅框框出來這一句:

  1. if(xxx && url.indexOf(publicPath) !== 0){ 
  2.   return false

講道理,從字面意義上這個(gè) url 應(yīng)該是客戶端發(fā)過來的請(qǐng)求連接,publicPath 應(yīng)該就是我們?cè)?webpack.config.js 中配置的 output.publicPath 項(xiàng)的值了吧?運(yùn)行起來看看:

果然,斷點(diǎn)進(jìn)去之后可以看到這兩個(gè)值確確實(shí)實(shí)符合前面的猜想,問題就出在這里,此時(shí):

  • url = '/`'
  • publicPath = output.publicPath = '/helloworld'
  • 所以 url.indexOf(publicPath) === false 實(shí)錘

getFilenameFromUrl 函數(shù)執(zhí)行結(jié)果為 false,所以 webpack-dev-middleware 會(huì)直接調(diào)用 next 方法進(jìn)入下一個(gè)中間件。

如果手動(dòng)在默認(rèn)打開的路徑后加上 output.publicPath 的內(nèi)容:

果然,它又行了。

第五步:總結(jié)

嗐,你看,這就是源碼分析的過程,繁瑣但不復(fù)雜,簡直人人都能成為技術(shù)大牛啊?;仡櫼幌麓a的流程:

  • webpack-dev-server 啟動(dòng)后會(huì)調(diào)用自動(dòng)打開瀏覽器訪問默認(rèn)路徑 http://localhost:9000
  • 此時(shí) webpack-dev-server 接收到默認(rèn)路徑請(qǐng)求,沿著 express 邏輯逐步走到 webpack-dev-middleware 中間件中
  • webpack-dev-middleware 中間件內(nèi)部呢,又繼續(xù)調(diào)用 webpack-dev-middleware/lib/util.js 文件的 getFilenameFromUrl 方法
  • getFilenameFromUrl 內(nèi)部判斷 url.indexOf(publicPath)
  • 若 getFilenameFromUrl 返回 false 則 webpack-dev-middleware 直接調(diào)用 next ,流程進(jìn)入下一個(gè)中間件 express.static
  • express.static 嘗試讀取 http://localhost:9000 對(duì)應(yīng)的資源文件,發(fā)現(xiàn)文件不存在,流程繼續(xù)進(jìn)入最后一個(gè)中間件 serveIndex
  • serveIndex 返回產(chǎn)物目錄結(jié)構(gòu)界面,不符合開發(fā)者預(yù)期

歸根結(jié)底,這里面的問題:

  1. Webpack 官網(wǎng)關(guān)于 output.publicPath 的介紹只說了會(huì)影響 bundle 產(chǎn)物路徑,沒說會(huì)影響主頁面的索引路徑,開發(fā)者表示很 confuse 咯
  2. webpack-dev-server 啟動(dòng)后,自動(dòng)打開頁面時(shí)沒有在鏈接后面自動(dòng)追加 output.publicPath 值導(dǎo)致默認(rèn)打開的路徑與真正的 index 首頁不一致,而且還沒返回 「404」 一類通用的錯(cuò)誤提示,取而代之以一個(gè)不明所以的「文件列表頁」,開發(fā)者很難迅速 get 到問題到底出在哪

到這里就把問題從表象,到原理,到最最根本的問題所在都挖出來了,以后可以跟其他同學(xué)說:

開發(fā)階段,盡量避免配置 output.publicPath 項(xiàng),否則會(huì)有驚喜哦~~

 

責(zé)任編輯:武曉燕 來源: Tecvan
相關(guān)推薦

2021-07-02 07:06:20

調(diào)試代碼crash

2022-10-27 06:48:23

sourcemap源碼Element

2023-03-21 17:06:24

樹莓派路由器

2015-08-10 14:56:31

Google

2020-06-23 09:48:09

Python開發(fā)內(nèi)存

2021-01-15 05:19:08

wireshark軟件網(wǎng)絡(luò)

2016-05-18 10:04:17

技術(shù)面試

2015-01-28 13:10:55

2016-11-21 15:08:38

Leader工程師團(tuán)隊(duì)管理

2022-03-07 10:41:09

云計(jì)算容器Kubernetes

2009-08-27 10:20:14

思科認(rèn)證CCNA準(zhǔn)備

2010-03-02 10:13:56

程序員面試

2020-10-14 10:29:58

人工智能

2016-12-02 09:30:03

思科網(wǎng)絡(luò)

2018-05-23 09:11:42

微信Android開發(fā)面試

2009-03-05 09:34:22

畢業(yè)生面試招聘

2017-05-02 13:38:51

CSS繪制形狀

2018-04-16 16:31:56

前端開發(fā)從零開始

2021-04-25 08:43:30

管理前端后端

2013-05-02 09:36:44

代碼項(xiàng)目
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)