前言
什麼? 你不瞭解G2Plot? 沒關係, 今天咱們要分享的內容和G2Plot的關係, 就像雷鋒和雷峯塔的關係. 因此, 不必擔心聽不懂. 我一直覺得, 如果我寫的文章有人看不懂, 那一定是我寫的不夠好. 而不是讀者的問題.
業務背景
最近在使用G2Plot的雙軸圖開發業務. 大概長下面這個樣子.
既然要開發雙軸圖, 那麼先看一遍文檔是正常操作. 於是我就打開了他的官網, 看到了這樣的一段話, 以及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/dual-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]
有意思! 太有意思了! 現在我們來更新下咱們的破案手冊.
鎖定目標, 精準打擊!
接下來, 我們得找找到底這個公共構建體系在什麼地方? 我們翻看G2Plot的package.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生態半年有感
在多倉庫管理模式下, 也不是沒有辦法調試, 我們可以藉助yalc. 但是我們都知道, 項目依賴是個神奇的東西, 坦白説, 把G2Plot跑起來就花了倆小時. 而這個構建系統gatsby-theme-antv我本地則是完全跑不起來.
什麼? 你問我是什麼開發環境? 既然你誠心誠意的問了, 那我就大發慈悲地告訴你吧.
論配置, 誰能與之一戰? 但就是跑不起來. 似乎線索又斷了...怎麼辦呢? 既然我們能把G2Plot跑起來, 要不然我們去G2Plot的node_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的.
首先我們找到這個組件定義的地方, 看到他接收的參數包含了exampleSections和allDemos. 很好! 非常好! 不出意外的話, 他會在某一個地方, 取allDemos的第一個元素, 所以我們在這個組件中搜一下allDemos[0]看看能不能搜到. 其實是搜不到的, 但是搜[0]卻可以搜到.
其實這裏的examples就是allDemos, 為什麼這麼説呢, 我們看看調用Playground的地方是如何傳參的
看着這命名, 我悟了! 這波啊, 我願稱之為最強幻術! 其實通過打斷點的方式, 也能確定這裏的examples就是我們一開始看到的allDemos
接下來就好辦了! 只要讓讀取的默認值取自exampleSections.examples即可!
然後我們保存後, G2Plot會重新觸發HMR, 再試一下看看是否正常了
果不其然! 接下來就是給antv提PR了. 老夫花了一天的時間費了九牛二虎之力, 就改了2行代碼...
<img src="https://eve-sama.oss-cn-shanghai.aliyuncs.com/blog/202208190103248.png" style="zoom:50%;" />
真正的真相, 不得而知
突然, 我很好奇, 到底是什麼原因, 導致這樣的bug存在呢? 於是我通過git log查找到上一次更改這一行的人.
顯然, 不是這次的提交導致的, 她只是優化了下寫法, 我又往前翻了幾十個commits, 發現其實從一開始, 就是examples[0]的寫法.
看來, 我又錯了...看着自己推演的過程, 我不禁開始思考, 問題到底出在哪?
我唯一能想到的, 就是構建系統的人, 一開始就是希望強制+上哈希路由的. 而後面寫文檔的人, 則是對此並不知情, 在寫完文檔後也沒有去檢查效果. 不知道這個推斷是否正確, 但其實不重要了. 我的PR使得這種寫法變得合法.
結語
其實這個過程還蠻有趣的. 從一開始以為是文檔錯誤, 到化身偵探一路推理排查找到關鍵點. 其實這個和工作內容很像, 很多問題浮現出來的都是表面問題, 而解決表面問題其實並沒有什麼難度, 對症下藥即可. 但是想要藥到病除, 那是不現實的. 唯有多加思考、 推理、 分析, 找到核心問題所在, 從根本上解決問題才是一勞永逸的. 當然, 二者的成本差距也非常大. 依據實際情況抉擇即可.
我是前夕, 專注於前端和成長, 希望我的內容可以幫助到你. 公眾號: 前夕小課堂
本文禁止轉載!