给蚂蚁金服 antv 提个 PR, 以为是改个错别字, 未曾想背后的原因竟如此复杂!
前言
什么? 你不了解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行代码...
![](https://eve-sama.oss-cn-shanghai.aliyuncs.com/blog/202208190103248.png)
真正的真相, 不得而知
突然, 我很好奇, 到底是什么原因, 导致这样的bug存在呢? 于是我通过git log查找到上一次更改这一行的人.
显然, 不是这次的提交导致的, 她只是优化了下写法, 我又往前翻了几十个commits, 发现其实从一开始, 就是examples[0]
的写法.
看来, 我又错了...看着自己推演的过程, 我不禁开始思考, 问题到底出在哪?
我唯一能想到的, 就是构建系统的人, 一开始就是希望强制+上哈希路由的. 而后面写文档的人, 则是对此并不知情, 在写完文档后也没有去检查效果. 不知道这个推断是否正确, 但其实不重要了. 我的PR使得这种写法变得合法.
结语
其实这个过程还蛮有趣的. 从一开始以为是文档错误, 到化身侦探一路推理排查找到关键点. 其实这个和工作内容很像, 很多问题浮现出来的都是表面问题, 而解决表面问题其实并没有什么难度, 对症下药即可. 但是想要药到病除, 那是不现实的. 唯有多加思考、 推理、 分析, 找到核心问题所在, 从根本上解决问题才是一劳永逸的. 当然, 二者的成本差距也非常大. 依据实际情况抉择即可.
我是前夕, 专注于前端和成长, 希望我的内容可以帮助到你. 公众号: 前夕小课堂
本文禁止转载!