為什么說 90% 的前端不會調試 Ant Design 源碼?
寫 react 項目的小伙伴應該都用過 antd 組件庫,但絕大多數同學并沒有看過它的源碼。
而想深入掌握 antd 組件庫,只熟悉參數是不行的,必須要深入到源碼層面。
所以今天就來分享下如何調試 antd 的源碼。
而且我敢說這種調試源碼的方式 90% 的前端都不會。
為什么呢?看到后面你就知道了。
首先,我們用 create-react-app 創建一個 react 項目:
創建成功后,進入到項目里,把 dev server 跑起來。

瀏覽器訪問可以看到渲染出的頁面:

然后我們安裝 antd,在入口組件里引入樣式和 Button 組件:

頁面會顯示這個 Button:

那怎么調試這個 Button 組件的源碼呢?
可以這樣:
首先,創建一個 VSCode 調試配置:

指定調試的 URL,然后啟動調試。
在組件里打個斷點,代碼會在這里斷住:

可以看到調用棧中上一幀是 renderWithHooks,這就是 react 源碼里調用函數組件的地方。
點擊那個調用棧,你就會看到:

它調用了 App 的函數組件,傳入了參數,拿到渲染后的 children 做后續處理。
所有函數組件都是在這里被調用的,而 antd 的組件也全部是函數組件,那么我們在這里加個斷點,打名字為 Button 的函數組件被調用的時候斷住不就行了?
這種在某種條件下才斷住的情況可以用條件斷點:
右鍵選擇添加條件斷點:

輸入斷住的條件:

當組件名字包含 Button 的時候才斷住。
然后刷新:

你會看到 App 組件明明也是函數組件,卻沒有在這里斷住,而 InternalButton 在這里斷住了。
這就是條件斷點的作用。
這個 InternalButton 就是 antd 里的 Button 組件。
step into 進入函數內部:

你會發現這確實是 Button 組件的源碼,但卻是被編譯后的,比如 jsx 都被編譯成了 React.createElement:

這樣是可以調試 Button 組件源碼的,但是比較別扭。
那能不能直接調試 Button 組件對應的 tsx 源碼呢?
可以的,這就要用到 sourcemap 了。
我們得把 antd 的源碼下載下來(我下載的時候是 4.23):
下載的時候加個 --single-branch 是下載單個分支, --depth=1 是下載單個 commit, 這樣速度會快幾十倍,是個有用的加速小技巧。
antd 下載下來,安裝完依賴之后,我們開始 build。
但你會發現 package.json 中有 build 命令,有 dist 命令,該執行哪個呢?
這個就需要了解下 antd 的幾種入口了。
去 react 項目的 node_modules 下,找到 antd 的 package.json 看一下,你會發現它有三種入口:

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 更熟悉一些。
執行 npm run dist,就會構建出 dist 目錄,下面是 UMD 的代碼:


你會發現默認的構建就是會生成 sourcemap 的,其實你去那個 react 測試項目里看下,從 npm 下載的 antd 包也帶了 sourcemap:

那直接用 dist 入口的代碼就能調試源碼了么?
我們試一下:

把引入組件的地方換成 dist 目錄下,也就是用 UMD 形式的入口。
重新跑調試:
你會發現代碼確實比之前更像源碼了。
之前前面是這樣的:

現在是這樣:

也就是沒了 babel runtime 的代碼,這明顯是源碼了。
但是你往后看:
之前是這樣的:

現在是這樣:

依然還是 React.createElement,而不是 jsx,也沒有 ts 的代碼。
說明它還不是最初的源碼。
為什么會出現這種既是源碼又不是源碼的情況呢?
因為它的編譯流程是這樣的:

代碼經過了 tsc 的編譯,然后又經過了 babel 的編譯,最后再通過 webpack 打包成 bundle.js。
tsc 和 babel 的編譯都會生成 sourcemap,而 webpack 也會生成一個 sourcemap。
webpack 的 sourcemap 默認只會根據最后一個 loader 的 sourcemap 來生成。
所以說上面我們用了 sourcemap 之后只能關聯到 babel 處理之前的代碼,像 ts 語法、jsx 代碼這些都沒有了。
因為沒有關聯更上一級的 ts-loader 的 sourcemap,自然是沒法直接映射回源碼的。
所以想映射回最初的 tsx 源碼,只要關聯了每一級 loader 的 sourcemap 就可以了。而這個是可以配置的,就是 devtool。
devtool 可以設置 soruce-map,就是生成 sourcemap,但是這個不會關聯 loader 的 sourcemap。
還可以設置 cheap-module-source-map,這個 module 就是關聯 loader 的 soruce-map 的意思。(那個 cheap 是只保留行的 sourcemap,生成速度會更快)
思路理清楚了,我們去改下編譯配置:
antd 的編譯工具鏈在 @ant-design/tools 這個包里,從 antd/node_modules/@antd-design/tools/lib/getWebpackConfig.js 就可以找到 webpack 的配置:

搜一下 ts-loader,你就會看到這段配置:

確實就像我們分析的,tsx 會經過 ts-loader 和 babel-loader 的處理。
搜一下 devtool,你會發現它的配置是 source-map:

這就是 antd 雖然有 sourcemap,但是關聯不到 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 組件的源碼么!

你可以斷點調試 antd 的參數是怎么處理的,什么參數會走什么邏輯等。
這個完全不影響正常開發,也就是把 antd 換成了從 antd/dist/antd 引入而已,開發完了換回去就行。
現在開發 antd 組件還有看文檔么?
直接看源碼它不更香么!
有的同學可能會擔心 node_modules 下的改動保存不下來。
這個也不是問題,可以執行下 npx patch-package antd,會生成這樣一個 patch 文件:

patch 文件里記錄了你對 antd 包的改動,這個可以上傳到 git 倉庫,其他小伙伴拉下來再執行 npx patch-package 就會自動應用這些改動。
至此,我們成功的調試了 antd 組件的 tsx 源碼。
為什么說 90% 的前端不會調試它的源碼呢?
主要是涉及的技術比較多:
- VSCode Chrome Debugger 調試網頁,這個知道的人就不多
- react 源碼里 renderWithHooks 是調用函數組件的地方
- 條件斷點可以在滿足條件的時候斷住
- antd 的 esm、commonjs、UMD 三種入口
- sourcemap 是干啥的,雖然經常接觸,但還是有很多前端沒用過
- webpack 的 cheap-module-source-map 的含義,為什么需要關聯 loader 的 sourcemap
而調試 antd 的組件源碼需要綜合運用這些技術,難度還是比較高的。
總結
antd 是 react 主流組件庫,我們經常使用它但可能并沒有調試過它的源碼。
我們可以在 renderWithHooks 里調用函數組件的地方打個條件斷點,在調用想調試的組件時斷住,這樣我們就可以 step into 到該組件定義的地方。
但是這樣調試的并不是最初的源碼,沒有 jsx 和 ts 語法。
想調試最初的 tsx 源碼需要用 sourcemap。
antd 有三種入口:es 目錄對應 esm 入口,lib 目錄對應 commonjs 入口,dist 目錄對應 UMD 入口。
把 antd 代碼下載下來,執行 npm run dist 就可以生成 UMD 形式的代碼。
想要 sourcemap 映射到 tsx 源碼,需要把 devtool 設置成 cheap-module-source-map,然后開啟 babel-loader 和 ts-loader 的 sourcemap。
把產物覆蓋 antd 的 dist 下的產物,再調試就可以直接調試 antd 組件的 tsx 源碼了。
用 antd 組件寫業務邏輯之余,對什么組件感興趣,可以順便去看看它的源碼,它不香么?
























