給螞蟻金服 antv 提個 PR, 以為是改個錯別字, 未曾想背後的原因竟如此複雜!

語言: CN / TW / HK

視訊版: www.bilibili.com/video/BV1tt… [1]

前言

什麼? 你不瞭解G2Plot? 沒關係, 今天咱們要分享的內容和G2Plot的關係, 就像雷鋒和雷峰塔的關係. 因此, 不必擔心聽不懂.

業務背景

最近在使用 G2Plot雙軸圖 開發業務. 大概長下面這個樣子.

既然要開發 雙軸圖 , 那麼先看一遍文件是正常操作. 於是我就打開了他的 官網 [2] , 看到了這樣的一段話, 以及demo連結.

看完後似懂非懂, 這不有DEMO嗎? 那我們就點選看看效果.

兄弟們! 好像不太對啊. 雖然我才剛剛開始看 雙軸圖 , 對它的業務不是特別瞭解. 但是我不瞎啊. 這標題不是寫著 面積圖 嗎? 而且左側分類也屬於 面積圖 . 好傢伙, 在我眼皮底下狸貓換太子?

顯然, 這是路由跳轉有問題. 要不然我就提個PR幫他們改了吧. 這種連結錯誤、錯別字的PR, 本來我是不屑提的. 但是不知道為什麼, 冥冥之中就覺得這個PR和我八字相剋, 那就不好意思了, 老夫今天就替天行道了!

找到連結錯誤之處

那麼既然要提PR, 首先我得知道這個正確的連結應該是啥樣的. 於是我觀察了下 面積圖 的第一個demo的連結

顯然, 路由裡面主要有3個部分 area/basic#basic , area/basic 其實大家都好理解, 為什麼最後還要+個雜湊引數呢? 簡單思考後很容易想到, 因為 basic 下面有好多的例子, 不同的demo對應不同的雜湊. 我們戳第二個demo試試看.

果然! 只有#後面的東西變化了. 接下來我們找到一開始時, 我們點選文件跳轉的連結 https://g2plot.antv.vision/zh/examples/dual-axes/dual-line 顯然, 就是少了雜湊引數. 我們為其+上小尾巴應該就可以正常訪問了.

大功告成! 接下來我們要做的就是找到原始碼中的跳轉之處替換連結即可.

事情好像沒有那麼簡單

開啟github, clone倉庫後, 搜尋結果, 一氣呵成. 接下來我要做的事情就是替換這個markdown中的地址. 一想到就這樣輕輕鬆鬆的解決了一個bug, 我露出了邪惡的笑容.

等等! 我發現了什麼!

這是個女孩子的名字?! 不對, 這不是重點, 重點是這樣的寫法居然是17個月前提交的? 如果說我剛剛+雜湊路由的方式是正確的解法, 那麼這個bug已經存在了17個月了!? 我繼續翻閱著原始碼. 發現了令人細思恐極的細節.

在19個月前, 同樣有人使用了不帶雜湊值的路由寫法. 為什麼明明不正確的寫法, 那麼多人要用呢? 難道...難道他們在程式碼中下毒? 不對! 我唯一能想到的可能, 那就是他們的寫法在當初是正確的!

想到這, 我的笑容凝固了. 所以我們來複盤一下. 如果一個路由連結 dual-axes/dual-line , 缺少了雜湊那一部分, 那麼作者希望的正常表現是啥樣的呢? 再看一下文件的描述

第一個demo連結是 dual-axes/dulal-line , 作者只是想表達這裡是雙軸折線圖, 至於具體是哪種雙軸折線, 重要嗎? 不重要. 因此沒必要指定雜湊引數, 也就是具體的demo. 而第二個demo連結是 dual-axes/column-line 原理同上.

通過以上分析, 我們可以確定, 不帶雜湊引數的連結是合法的! 他預設應該定位二級選單的第一個demo. 只是這個功能在後來的迭代中被改壞了.

你以為的真相只是你以為

按理說, 好不容易發現了真相, 我應該是無比的興奮與開心的. 事實上, 確實很開心, 但是開心中又夾雜著抑鬱. 開心是因為發現了更深層的原因, 而抑鬱的點是 "我咋知道他路由跳轉是咋弄的? G2Plot又不是我開發的!"

我像剛剛獎勵完自己一樣呆坐在電腦前, 不知所措. 好不容易挖掘到的線索就此中斷了. 突然間, 我的腦海中閃過幾個antv產品的模樣.

這幾個antv產品, 不能說十分相似, 只能說一模一樣. 等等! 一模一樣? 難道說...又一個猜想浮現在我的腦海中: 這些網站的構建是不是用的同一套系統? 那麼問題會不會和G2Plot沒有半毛錢關係, 而是這個構建系統上?

想到這, 我迫不及待地打開了X6(螞蟻金服的圖編輯器, 反正也是個視覺化專案), 隨便點開了個示例, 觀察他的URL結構.

好! 很好!! 非常好!!! 這個連結和我們前面的案例一樣, 表現一切正常! 我屏住呼吸, 把雜湊值去掉, 按下回車!

挖槽! X6也有這個問題! 這證明我的猜想沒錯! 他們師出同門, 一定是公共的網站構建體系出了問題! 事情變得越來越有趣了! 我們理一下思路, 從一開始的文件錯誤的方向一路推理到現在.

我擼起了袖子, 準備大幹一場. 今天, 老夫就算拿出全部的本領, 掘地三尺也要把這個bug挖出來!

那麼這個神祕的公共構建體系, 到底是在哪呢? 確實沒什麼想法, 但是不管他在哪裡, 他總歸要體現在執行時吧? 接下來, 我clone了 G2Plot 的倉庫, 簡單看了下專案結構, 這些目錄一看就知道是幹嘛的, 挺不錯.

裝好了依賴, 念出了咒語.

npm run start

接下來咱們故技重施, 看看效果.

還是原來的配方, 還是熟悉的味道. 只不過這次訪問的是 迷你圖 而不是 面積圖 了. 再看一眼倉庫檔案

哦豁! 無中生有! 一眼就看到了 public 目錄, 我在裡面一頓搜尋. 發現只有一個看起來像雙軸圖的東西. 大幾十個圖表, 為什麼只有雙軸圖呢? 難道是因為我剛剛訪問過?

於是我隨便點了個 折線圖 , 再觀察下目錄

可以確定, 當我訪問哪個類別的時候, 他就會生成該類別的 page-data.json , 那麼這到底是何方神聖呢? 我們開啟 dual-line/page-data.json , 看看他到底賣的什麼藥. 開啟這個檔案, 我們對其格式化後, 隨便翻翻, 就能找到一個叫 allDemos 的東西.  通過名字可以判斷, 好像是所有demo的集合.

再隨便翻翻, 發現還有個 exampleSections 的東西, 看起來好像是該類別的所有demo.

等等! 再看一眼本地開發模式下的錯誤重定向頁面

這個demo不就是 allDemos 裡的第一個demo嗎? 而他應該導向的地址, 不就是 exampleSections.examples 裡的第一個demo嗎? Soga! 那麼我們可以推斷, 當路由不帶雜湊值時,  構建系統應該讀的是 exampleSections.examples[0] , 而不是 allDemos[0]

有意思! 太有意思了! 現在我們來更新下咱們的破案手冊.

鎖定目標, 精準打擊!

接下來, 我們得找找到底這個公共構建體系在什麼地方? 我們翻看 G2Plotpackage.json , 看看有沒有啥線索.

"devDependencies": {
"@antv/data-set": "^0.11.5",
"@antv/gatsby-theme-antv": "^1.1.15",
"@babel/core": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.11.5",
"@babel/preset-env": "^7.10.4",
"@babel/runtime": "^7.11.2",
"@commitlint/cli": "^8.2.0",
"@commitlint/config-angular": "^8.2.0",
"@types/jest": "^25.2.1",
"@typescript-eslint/eslint-plugin": "^2.0.0",
"@typescript-eslint/parser": "^2.0.0",
"all-contributors-cli": "^6.20.0",
"antd": "^4.8.4",
"babel-loader": "^8.1.0",
"chroma-js": "^2.1.2",
"conventional-changelog-cli": "^2.0.34",
"cross-env": "^7.0.2",
"eslint": "^6.1.0",
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^3.1.0",
"gatsby": "^2.24.63",
"gatsby-plugin-webpack-bundle-analyser-v2": "^1.1.27",
"generate-changelog": "^1.8.0",
"gh-pages": "^3.1.0",
"husky": "^4.2.3",
"jest": "^26.0.1",
"jest-electron": "^0.1.7",
"jest-extended": "^0.11.2",
"jest-matcher-deep-close-to": "^2.0.1",
"limit-size": "^0.1.3",
"lint-md-cli": "^0.1.2",
"lint-staged": "^10.0.7",
"miz": "^1.0.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.1",
"rc-for-plots": "^0.0.1",
"react": "^16.11.0",
"react-dom": "^16.11.0",
"react-i18next": "^11.7.0",
"rimraf": "^3.0.0",
"ts-jest": "^25.4.0",
"ts-loader": "^7.0.0",
"typescript": "^3.5.3",
"webpack": "^4.44.2",
"webpack-bundle-analyzer": "^3.9.0",
"webpack-cli": "^3.3.7",
"webpack-dev-server": "^3.9.0"
},

雖然密密麻麻的, 但是這裡面絕大部分其實都是眼熟的. 咱們想一想, 這套構建體系可能是螞蟻金服以外的人寫的嗎? 完全沒有可能!

第一, 這個難度非常大, 一定要非常瞭解他們的需求才行. 第二, 我們都知道做開源是用愛發電的. 但是這樣龐大的構建體系需要的電量極大, 如果不是拿著工資, 我想電量是不足以支撐的.  那麼既然是螞蟻的人寫的, 那這個倉庫最有可能是放在 @antv 下面的.  這樣想來, 那麼最有可能的就是下面這個了.

"@antv/gatsby-theme-antv": "^1.1.15",

然後我們去github上搜一下這個倉庫

不僅可以搜到, 還不打自招了. 通過 readme 部分, 我們完全確認了這個貨就是始作俑者! 接下來又開始頭疼了, 我怎麼除錯呢? 要知道, G2並沒有使用monorepo的方式去管理, 關於G2的架構,在這裡就不贅述了. 感興趣的可以訪問: 使用antv/G2生態半年有感 [3]

在多倉庫管理模式下, 也不是沒有辦法除錯, 我們可以藉助 yalc [4] . 但是我們都知道, 專案依賴是個神奇的東西, 坦白說, 把 G2Plot 跑起來就花了倆小時. 而這個構建系統 gatsby-theme-antv 我本地則是完全跑不起來.

什麼? 你問我是什麼開發環境? 既然你誠心誠意的問了, 那我就大發慈悲地告訴你吧.

論配置, 誰能與之一戰? 但就是跑不起來. 似乎線索又斷了...怎麼辦呢? 既然我們能把 G2Plot 跑起來, 要不然我們去 G2Plotnode_modules 裡看看?

可以看到, 這個 gatsby-theme-antv 也是比較簡潔的目錄, 我們想要的東西應該就在這個裡面. 我的預感告訴我, 我想要的東西大概率就在 components 資料夾裡, 於是我就點開隨便看了看

坦白說, 我不是這個專案的開發者, 我對這個專案是完全沒概念的, 這真的非常艱難. 就算核心bug在某一個檔案裡, 對我來說也和大海撈針差不多了. 其實之前我們在分析的時候也數次遇到束手無策的情況.

每當這個時候, 我們要嘗試轉換思路, 不要在一棵樹上吊死. 不管他的檔案結構有多複雜, 最終不都得打包到一起嗎? 所以我打開了 G2Plot 的控制檯, 找到了 commons.js , 我們想要找的東西, 一定在這49萬行的程式碼中...

嗯, 49萬行程式碼. 更絕望了...

於是, 我又去讀node_modules裡的原始碼了, 碰碰運氣, 隨便點幾個檔案到處看看, 邊看邊分析依賴關係和渲染邏輯. 於是, 我終於看到了下面這段程式碼:

其實我不是隨便點開檔案看的, 我是通過在 common.js 中打斷點的方式找到這個地方的, 但是這個過程充滿了試探與分析, 而且會在多個元件中跳來跳去, 又繞又繁瑣. 所以就一筆帶過吧~

兄弟們! 當我看見這段程式碼的時候, 比看見黑絲都興奮吶! 這 currentExample 是啥? 從語義上來說, 就是當前展示的demo. 那麼現在的問題不就是URL沒有帶雜湊值的時候導致 currentExample 不對嗎? 當然, 這只是我的猜想. 還需要更進一步的證據. 此時, 我利用react的dev-tool來查詢這個 PlayGrounds 元件, 看看是不是渲染的部分.

哎, 只是個選單元件啊? 不是渲染區域的元件啊...空歡喜一場. 等等! 就算渲染區域想正確渲染, 不也得保證左側選單選到正確的demo嗎? 所以是不是可以理解為, 就是因為左側選單沒有選到正確的demo, 才導致的渲染區域不是正確的demo? 好! 證據確鑿, 嚴查 PlayGrounds 元件!

但稍加思考後我就確定了, PlayGrounds 只是一個傀儡罷了, 他是接收 currentExample 的, 並沒有權利決定 currentExample 是誰. 因此我們應該把矛頭對準呼叫這個元件的地方.

這是真假美猴王嗎? 區別就是有沒有 s ? 不糾結了, 那麼我們就開始探尋究竟在 PlayGround 元件中, 是如何定義 currentExample 的.

首先我們找到這個元件定義的地方, 看到他接收的引數包含了 exampleSectionsallDemos . 很好! 非常好! 不出意外的話, 他會在某一個地方, 取 allDemos 的第一個元素, 所以我們在這個元件中搜一下 allDemos[0] 看看能不能搜到. 其實是搜不到的, 但是搜 [0] 卻可以搜到.

其實這裡的 examples 就是 allDemos , 為什麼這麼說呢, 我們看看呼叫 Playground 的地方是如何傳參的

看著這命名, 我悟了! 這波啊, 我願稱之為最強幻術! 其實通過打斷點的方式, 也能確定這裡的 examples 就是我們一開始看到的 allDemos

接下來就好辦了! 只要讓讀取的預設值取自 exampleSections.examples 即可!

然後我們儲存後, G2Plot 會重新觸發 HMR , 再試一下看看是否正常了

果不其然! 接下來就是給antv提PR了.  老夫花了一天的時間費了九牛二虎之力, 就改了2行程式碼...

真正的真相, 不得而知

突然, 我很好奇, 到底是什麼原因, 導致這樣的bug存在呢? 於是我通過git log查詢到上一次更改這一行的人.

顯然, 不是這次的提交導致的, 她只是優化了下寫法, 我又往前翻了幾十個commits, 發現其實從一開始, 就是 examples[0] 的寫法.

看來, 我又錯了...看著自己推演的過程, 我不禁開始思考, 問題到底出在哪?

我唯一能想到的, 就是構建系統的人, 一開始就是希望強制+上雜湊路由的. 而後面寫文件的人, 則是對此並不知情, 在寫完文件後也沒有去檢查效果. 不知道這個推斷是否正確, 但其實不重要了. 我的PR使得這種寫法變得合法.

結語

其實這個過程還蠻有趣的. 從一開始以為是文件錯誤, 到化身偵探一路推理排查詢到關鍵點. 其實這個和工作內容很像, 很多問題浮現出來的都是表面問題, 而解決表面問題其實並沒有什麼難度, 對症下藥即可. 但是想要藥到病除, 那是不現實的. 唯有多加思考、 推理、 分析, 找到核心問題所在, 從根本上解決問題才是一勞永逸的.  當然, 二者的成本差距也非常大. 依據實際情況抉擇即可.

參考資料

[1]

www.bilibili.com/video/BV1tt…: https://www.bilibili.com/video/BV1tt4y1n7KC?vd_source=626aa553e41d96c25f58add05b7734c3#reply126976123072

[2]

官網: https://g2plot.antv.vision/zh/docs/api/plots/dual-axes#圖形樣式

[3]

使用antv/G2生態半年有感: https://juejin.cn/post/7043068238539784206

[4]

yalc: https://juejin.cn/post/7033400734746066957

作者: 前夕

https://juejin.cn/post/7134614197689647112

- EOF -

覺得本文對你有幫助?請分享給更多人

關注「大前端技術之路」加星標,提升前端技能

點贊和在看就是最大的支援 :heart: