為什么說 90% 的前端不會調(diào)試 Ant Design 源碼?
寫 react 項目的小伙伴應該都用過 antd 組件庫,但絕大多數(shù)同學并沒有看過它的源碼。
而想深入掌握 antd 組件庫,只熟悉參數(shù)是不行的,必須要深入到源碼層面。
所以今天就來分享下如何調(diào)試 antd 的源碼。
而且我敢說這種調(diào)試源碼的方式 90% 的前端都不會。
為什么呢?看到后面你就知道了。
首先,我們用 create-react-app 創(chuàng)建一個 react 項目:
創(chuàng)建成功后,進入到項目里,把 dev server 跑起來。
瀏覽器訪問可以看到渲染出的頁面:
然后我們安裝 antd,在入口組件里引入樣式和 Button 組件:
頁面會顯示這個 Button:
那怎么調(diào)試這個 Button 組件的源碼呢?
可以這樣:
首先,創(chuàng)建一個 VSCode 調(diào)試配置:
指定調(diào)試的 URL,然后啟動調(diào)試。
在組件里打個斷點,代碼會在這里斷?。?/p>
可以看到調(diào)用棧中上一幀是 renderWithHooks,這就是 react 源碼里調(diào)用函數(shù)組件的地方。
點擊那個調(diào)用棧,你就會看到:
它調(diào)用了 App 的函數(shù)組件,傳入了參數(shù),拿到渲染后的 children 做后續(xù)處理。
所有函數(shù)組件都是在這里被調(diào)用的,而 antd 的組件也全部是函數(shù)組件,那么我們在這里加個斷點,打名字為 Button 的函數(shù)組件被調(diào)用的時候斷住不就行了?
這種在某種條件下才斷住的情況可以用條件斷點:
右鍵選擇添加條件斷點:
輸入斷住的條件:
當組件名字包含 Button 的時候才斷住。
然后刷新:
你會看到 App 組件明明也是函數(shù)組件,卻沒有在這里斷住,而 InternalButton 在這里斷住了。
這就是條件斷點的作用。
這個 InternalButton 就是 antd 里的 Button 組件。
step into 進入函數(shù)內(nèi)部:
你會發(fā)現(xiàn)這確實是 Button 組件的源碼,但卻是被編譯后的,比如 jsx 都被編譯成了 React.createElement:
這樣是可以調(diào)試 Button 組件源碼的,但是比較別扭。
那能不能直接調(diào)試 Button 組件對應的 tsx 源碼呢?
可以的,這就要用到 sourcemap 了。
我們得把 antd 的源碼下載下來(我下載的時候是 4.23):
下載的時候加個 --single-branch 是下載單個分支, --depth=1 是下載單個 commit, 這樣速度會快幾十倍,是個有用的加速小技巧。
antd 下載下來,安裝完依賴之后,我們開始 build。
但你會發(fā)現(xiàn) package.json 中有 build 命令,有 dist 命令,該執(zhí)行哪個呢?
這個就需要了解下 antd 的幾種入口了。
去 react 項目的 node_modules 下,找到 antd 的 package.json 看一下,你會發(fā)現(xiàn)它有三種入口:
main 是 commonjs 的入口,也就是 require('antd') 的時候會走這個。
module 是 esm 的入口,也就是 import xx from 'antd' 的時候會走這個。
unpkg 是 UMD 的入口,也就是通過 script 標簽引入的時候或者 commonjs 的方式等都可以用。
分別對應了 lib、es、dist 的目錄。
所以 antd 項目里的 dist 命令就是單獨生成 UMD 代碼的,而 build 命令是生成這三種代碼。
這三種形式的代碼都是可用的,這里我們選擇構建 UMD 形式的代碼,因為它會用 webpack 打包,而另外兩種是通過 gulp 構建的。我對 webpack 更熟悉一些。
執(zhí)行 npm run dist,就會構建出 dist 目錄,下面是 UMD 的代碼:
你會發(fā)現(xiàn)默認的構建就是會生成 sourcemap 的,其實你去那個 react 測試項目里看下,從 npm 下載的 antd 包也帶了 sourcemap:
那直接用 dist 入口的代碼就能調(diào)試源碼了么?
我們試一下:
把引入組件的地方換成 dist 目錄下,也就是用 UMD 形式的入口。
重新跑調(diào)試:
你會發(fā)現(xiàn)代碼確實比之前更像源碼了。
之前前面是這樣的:
現(xiàn)在是這樣:
也就是沒了 babel runtime 的代碼,這明顯是源碼了。
但是你往后看:
之前是這樣的:
現(xiàn)在是這樣:
依然還是 React.createElement,而不是 jsx,也沒有 ts 的代碼。
說明它還不是最初的源碼。
為什么會出現(xiàn)這種既是源碼又不是源碼的情況呢?
因為它的編譯流程是這樣的:
代碼經(jīng)過了 tsc 的編譯,然后又經(jīng)過了 babel 的編譯,最后再通過 webpack 打包成 bundle.js。
tsc 和 babel 的編譯都會生成 sourcemap,而 webpack 也會生成一個 sourcemap。
webpack 的 sourcemap 默認只會根據(jù)最后一個 loader 的 sourcemap 來生成。
所以說上面我們用了 sourcemap 之后只能關聯(lián)到 babel 處理之前的代碼,像 ts 語法、jsx 代碼這些都沒有了。
因為沒有關聯(lián)更上一級的 ts-loader 的 sourcemap,自然是沒法直接映射回源碼的。
所以想映射回最初的 tsx 源碼,只要關聯(lián)了每一級 loader 的 sourcemap 就可以了。而這個是可以配置的,就是 devtool。
devtool 可以設置 soruce-map,就是生成 sourcemap,但是這個不會關聯(lián) loader 的 sourcemap。
還可以設置 cheap-module-source-map,這個 module 就是關聯(lián) loader 的 soruce-map 的意思。(那個 cheap 是只保留行的 sourcemap,生成速度會更快)
思路理清楚了,我們?nèi)ジ南戮幾g配置:
antd 的編譯工具鏈在 @ant-design/tools 這個包里,從 antd/node_modules/@antd-design/tools/lib/getWebpackConfig.js 就可以找到 webpack 的配置:
搜一下 ts-loader,你就會看到這段配置:
確實就像我們分析的,tsx 會經(jīng)過 ts-loader 和 babel-loader 的處理。
搜一下 devtool,你會發(fā)現(xiàn)它的配置是 source-map:
這就是 antd 雖然有 sourcemap,但是關聯(lián)不到 tsx 源碼的原因。
那我們給它改一下:
把 devtool 改為 cheap-module-source-map。
并且改一下 babel 配置,設置 sourceMap 為 true,讓它生成 sourcemap。
ts也同樣要生成 sourcemap,不過那個是在根目錄的 tsconfig.json里改:
改完這三點之后,再重新跑 npm run dist。
dist 目錄下會生成新的 antd.js 和 antd.js.map。
把它復制到 react 項目的 node_modules/antd/dist 下,覆蓋之前的。
清一下 babel-loader 的緩存:
重新跑 dev server。
注意,這里要用 dist 下的代碼:
然后再跑到斷點的位置,進入組件源碼,你會進入一個新世界:
ts 類型、jsx 的語法,熟悉的感覺又回來了,這不就是 antd 組件的源碼么!
你可以斷點調(diào)試 antd 的參數(shù)是怎么處理的,什么參數(shù)會走什么邏輯等。
這個完全不影響正常開發(fā),也就是把 antd 換成了從 antd/dist/antd 引入而已,開發(fā)完了換回去就行。
現(xiàn)在開發(fā) antd 組件還有看文檔么?
直接看源碼它不更香么!
有的同學可能會擔心 node_modules 下的改動保存不下來。
這個也不是問題,可以執(zhí)行下 npx patch-package antd,會生成這樣一個 patch 文件:
patch 文件里記錄了你對 antd 包的改動,這個可以上傳到 git 倉庫,其他小伙伴拉下來再執(zhí)行 npx patch-package 就會自動應用這些改動。
至此,我們成功的調(diào)試了 antd 組件的 tsx 源碼。
為什么說 90% 的前端不會調(diào)試它的源碼呢?
主要是涉及的技術比較多:
- VSCode Chrome Debugger 調(diào)試網(wǎng)頁,這個知道的人就不多
- react 源碼里 renderWithHooks 是調(diào)用函數(shù)組件的地方
- 條件斷點可以在滿足條件的時候斷住
- antd 的 esm、commonjs、UMD 三種入口
- sourcemap 是干啥的,雖然經(jīng)常接觸,但還是有很多前端沒用過
- webpack 的 cheap-module-source-map 的含義,為什么需要關聯(lián) loader 的 sourcemap
而調(diào)試 antd 的組件源碼需要綜合運用這些技術,難度還是比較高的。
總結(jié)
antd 是 react 主流組件庫,我們經(jīng)常使用它但可能并沒有調(diào)試過它的源碼。
我們可以在 renderWithHooks 里調(diào)用函數(shù)組件的地方打個條件斷點,在調(diào)用想調(diào)試的組件時斷住,這樣我們就可以 step into 到該組件定義的地方。
但是這樣調(diào)試的并不是最初的源碼,沒有 jsx 和 ts 語法。
想調(diào)試最初的 tsx 源碼需要用 sourcemap。
antd 有三種入口:es 目錄對應 esm 入口,lib 目錄對應 commonjs 入口,dist 目錄對應 UMD 入口。
把 antd 代碼下載下來,執(zhí)行 npm run dist 就可以生成 UMD 形式的代碼。
想要 sourcemap 映射到 tsx 源碼,需要把 devtool 設置成 cheap-module-source-map,然后開啟 babel-loader 和 ts-loader 的 sourcemap。
把產(chǎn)物覆蓋 antd 的 dist 下的產(chǎn)物,再調(diào)試就可以直接調(diào)試 antd 組件的 tsx 源碼了。
用 antd 組件寫業(yè)務邏輯之余,對什么組件感興趣,可以順便去看看它的源碼,它不香么?