给蚂蚁金服antv提个PR, 以为是改个错别字, 未曾想背后的原因竟如此复杂!

35,626 阅读15分钟

视频版: www.bilibili.com/video/BV1tt…

前言

什么? 你不了解G2Plot? 没关系, 今天咱们要分享的内容和G2Plot的关系, 就像雷锋和雷峰塔的关系. 因此, 不必担心听不懂. 我一直觉得, 如果我写的文章有人看不懂, 那一定是我写的不够好. 而不是读者的问题.

业务背景

最近在使用G2Plot双轴图开发业务. 大概长下面这个样子.

image-20220818162350608

既然要开发双轴图, 那么先看一遍文档是正常操作. 于是我就打开了他的官网, 看到了这样的一段话, 以及demo链接.

image-20220818163051799

看完后似懂非懂, 这不有DEMO吗? 那我们就点击看看效果.

兄弟们! 好像不太对啊. 虽然我才刚刚开始看双轴图, 对它的业务不是特别了解. 但是我不瞎啊. 这标题不是写着面积图吗? 而且左侧分类也属于面积图. 好家伙, 在我眼皮底下狸猫换太子?

显然, 这是路由跳转有问题. 要不然我就提个PR帮他们改了吧. 这种链接错误、错别字的PR, 本来我是不屑提的. 但是不知道为什么, 冥冥之中就觉得这个PR和我八字相克, 那就不好意思了, 老夫今天就替天行道了!

点击查看图片来源

找到链接错误之处

那么既然要提PR, 首先我得知道这个正确的链接应该是啥样的. 于是我观察了下面积图的第一个demo的链接

image-20220818164810027

显然, 路由里面主要有3个部分area/basic#basic, area/basic其实大家都好理解, 为什么最后还要+个哈希参数呢? 简单思考后很容易想到, 因为basic下面有好多的例子, 不同的demo对应不同的哈希. 我们戳第二个demo试试看.

image-20220818164948407

果然! 只有#后面的东西变化了. 接下来我们找到一开始时, 我们点击文档跳转的链接https://g2plot.antv.vision/zh/examples/dual-axes/dual-line显然, 就是少了哈希参数. 我们为其+上小尾巴应该就可以正常访问了.

大功告成! 接下来我们要做的就是找到源码中的跳转之处替换链接即可.

img

事情好像没有那么简单

image-20220818171037980

打开github, clone仓库后, 搜索结果, 一气呵成. 接下来我要做的事情就是替换这个markdown中的地址. 一想到就这样轻轻松松的解决了一个bug, 我露出了邪恶的笑容.

点击查看图片来源

等等! 我发现了什么!

image-20220818172037979

这是个女孩子的名字?! 不对, 这不是重点, 重点是这样的写法居然是17个月前提交的? 如果说我刚刚+哈希路由的方式是正确的解法, 那么这个bug已经存在了17个月了!? 我继续翻阅着源代码. 发现了令人细思恐极的细节.

image-20220818172518053

在19个月前, 同样有人使用了不带哈希值的路由写法. 为什么明明不正确的写法, 那么多人要用呢? 难道...难道他们在代码中下毒? 不对! 我唯一能想到的可能, 那就是他们的写法在当初是正确的!

想到这, 我的笑容凝固了. 所以我们来复盘一下. 如果一个路由链接dual-axes/dual-line, 缺少了哈希那一部分, 那么作者希望的正常表现是啥样的呢? 再看一下文档的描述

image-20220818173626437

image-20220818175344026

第一个demo链接是dual-axes/dulal-line, 作者只是想表达这里是双轴折线图, 至于具体是哪种双轴折线, 重要吗? 不重要. 因此没必要指定哈希参数, 也就是具体的demo. 而第二个demo链接是dual-axes/column-line原理同上.

img

通过以上分析, 我们可以确定, 不带哈希参数的链接是合法的! 他默认应该定位二级菜单的第一个demo. 只是这个功能在后来的迭代中被改坏了.

你以为的真相只是你以为

按理说, 好不容易发现了真相, 我应该是无比的兴奋与开心的. 事实上, 确实很开心, 但是开心中又夹杂着抑郁. 开心是因为发现了更深层的原因, 而抑郁的点是**"我咋知道他路由跳转是咋弄的? G2Plot又不是我开发的!"**

我像刚刚奖励完自己一样呆坐在电脑前, 不知所措. 好不容易挖掘到的线索就此中断了. 突然间, 我的脑海中闪过几个antv产品的模样.

image-20220818180541440

image-20220818180844867

image-20220818180854929

这几个antv产品, 不能说十分相似, 只能说一模一样. 等等! 一模一样? 难道说...又一个猜想浮现在我的脑海中: 这些网站的构建是不是用的同一套系统? 那么问题会不会和G2Plot没有半毛钱关系, 而是这个构建系统上?

想到这, 我迫不及待地打开了X6(蚂蚁金服的图编辑器, 反正也是个可视化项目), 随便点开了个示例, 观察他的URL结构.

image-20220818181630457

好! 很好!! 非常好!!! 这个链接和我们前面的案例一样, 表现一切正常! 我屏住呼吸, 把哈希值去掉, 按下回车!

挖槽! X6也有这个问题! 这证明我的猜想没错! 他们师出同门, 一定是公共的网站构建体系出了问题! 事情变得越来越有趣了! 我们理一下思路, 从一开始的文档错误的方向一路推理到现在.

image-20220818190420931

我撸起了袖子, 准备大干一场. 今天, 老夫就算拿出全部的本领, 掘地三尺也要把这个bug挖出来!

点击查看图片来源

那么这个神秘的公共构建体系, 到底是在哪呢? 确实没什么想法, 但是不管他在哪里, 他总归要体现在运行时吧? 接下来, 我clone了G2Plot的仓库, 简单看了下项目结构, 这些目录一看就知道是干嘛的, 挺不错.

image-20220818191126231

装好了依赖, 念出了咒语.

npm run start

接下来咱们故技重施, 看看效果.

还是原来的配方, 还是熟悉的味道. 只不过这次访问的是迷你图而不是面积图了. 再看一眼仓库文件

image-20220818194107039

哦豁! 无中生有! 一眼就看到了public目录, 我在里面一顿搜寻. 发现只有一个看起来像双轴图的东西. 大几十个图表, 为什么只有双轴图呢? 难道是因为我刚刚访问过?

image-20220818201643105

于是我随便点了个折线图, 再观察下目录

image-20220818201819573

可以确定, 当我访问哪个类别的时候, 他就会生成该类别的page-data.json, 那么这到底是何方神圣呢? 我们打开dual-line/page-data.json, 看看他到底卖的什么药. 打开这个文件, 我们对其格式化后, 随便翻翻, 就能找到一个叫allDemos的东西. 通过名字可以判断, 好像是所有demo的集合.

image-20220818202008180

再随便翻翻, 发现还有个exampleSections的东西, 看起来好像是该类别的所有demo.

image-20220818202137889

等等! 再看一眼本地开发模式下的错误重定向页面

image-20220818201425766

这个demo不就是allDemos里的第一个demo吗? 而他应该导向的地址, 不就是exampleSections.examples里的第一个demo吗? Soga! 那么我们可以推断, 当路由不带哈希值时, 构建系统应该读的是exampleSections.examples[0], 而不是allDemos[0]

有意思! 太有意思了! 现在我们来更新下咱们的破案手册.

image-20220818203008048

锁定目标, 精准打击!

接下来, 我们得找找到底这个公共构建体系在什么地方? 我们翻看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上搜一下这个仓库

image-20220818204206205

不仅可以搜到, 还不打自招了. 通过readme部分, 我们完全确认了这个货就是始作俑者! 接下来又开始头疼了, 我怎么调试呢? 要知道, G2并没有使用monorepo的方式去管理, 关于G2的架构, 我在之前的一篇文章中简单描述过, 在这里我就不赘述了. 感兴趣的可以访问: 使用antv/G2生态半年有感

在多仓库管理模式下, 也不是没有办法调试, 我们可以借助yalc. 但是我们都知道, 项目依赖是个神奇的东西, 坦白说, 把G2Plot跑起来就花了俩小时. 而这个构建系统gatsby-theme-antv我本地则是完全跑不起来.

image-20220818212456091

什么? 你问我是什么开发环境? 既然你诚心诚意的问了, 那我就大发慈悲地告诉你吧.

image-20220818233653468

论配置, 谁能与之一战? 但就是跑不起来. 似乎线索又断了...怎么办呢? 既然我们能把G2Plot跑起来, 要不然我们去G2Plotnode_modules里看看?

image-20220818234156565

可以看到, 这个gatsby-theme-antv也是比较简洁的目录, 我们想要的东西应该就在这个里面. 我的预感告诉我, 我想要的东西大概率就在components文件夹里, 于是我就点开随便看了看

image-20220818234456998

坦白说, 我不是这个项目的开发者, 我对这个项目是完全没概念的, 这真的非常艰难. 就算核心bug在某一个文件里, 对我来说也和大海捞针差不多了. 其实之前我们在分析的时候也数次遇到束手无策的情况. 每当这个时候, 我们要尝试转换思路, 不要在一棵树上吊死. 不管他的文件结构有多复杂, 最终不都得打包到一起吗? 所以我打开了G2Plot的控制台, 找到了commons.js, 我们想要找的东西, 一定在这49万行的代码中...

image-20220818235236870

嗯, 49万行代码. 更绝望了...

嘤嘤嘤

于是, 我又去读node_modules里的源码了, 碰碰运气, 随便点几个文件到处看看, 边看边分析依赖关系和渲染逻辑. 于是, 我终于看到了下面这段代码:

image-20220819000132914

其实我不是随便点开文件看的, 我是通过在common.js中打断点的方式找到这个地方的, 但是这个过程充满了试探与分析, 而且会在多个组件中跳来跳去, 又绕又繁琐. 所以就一笔带过吧~

兄弟们! 当我看见这段代码的时候, 比看见黑丝都兴奋呐! 这currentExample是啥? 从语义上来说, 就是当前展示的demo. 那么现在的问题不就是URL没有带哈希值的时候导致currentExample不对吗? 当然, 这只是我的猜想. 还需要更进一步的证据. 此时, 我利用react的dev-tool来查找这个PlayGrounds组件, 看看是不是渲染的部分.

image-20220819000806731

哎, 只是个菜单组件啊? 不是渲染区域的组件啊...空欢喜一场. 等等! 就算渲染区域想正确渲染, 不也得保证左侧菜单选到正确的demo吗? 所以是不是可以理解为, 就是因为左侧菜单没有选到正确的demo, 才导致的渲染区域不是正确的demo? 好! 证据确凿, 严查PlayGrounds组件!

但稍加思考后我就确定了, PlayGrounds只是一个傀儡罢了, 他是接收currentExample的, 并没有权利决定currentExample是谁. 因此我们应该把矛头对准调用这个组件的地方.

image-20220819001510566

这是真假美猴王吗? 区别就是有没有s? 不纠结了, 那么我们就开始探寻究竟在PlayGround组件中, 是如何定义currentExample的.

image-20220819002541277

首先我们找到这个组件定义的地方, 看到他接收的参数包含了exampleSectionsallDemos. 很好! 非常好! 不出意外的话, 他会在某一个地方, 取allDemos的第一个元素, 所以我们在这个组件中搜一下allDemos[0]看看能不能搜到. 其实是搜不到的, 但是搜[0]却可以搜到.

image-20220819001842567

其实这里的examples就是allDemos, 为什么这么说呢, 我们看看调用Playground的地方是如何传参的

image-20220819005305657

看着这命名, 我悟了! 这波啊, 我愿称之为最强幻术! 其实通过打断点的方式, 也能确定这里的examples就是我们一开始看到的allDemos

image-20220819005533355

接下来就好办了! 只要让读取的默认值取自exampleSections.examples即可!

图片.png

然后我们保存后, G2Plot会重新触发HMR, 再试一下看看是否正常了

demo5

果不其然! 接下来就是给antv提PR了. 老夫花了一天的时间费了九牛二虎之力, 就改了2行代码...

真正的真相, 不得而知

突然, 我很好奇, 到底是什么原因, 导致这样的bug存在呢? 于是我通过git log查找到上一次更改这一行的人.

image-20220819102309727

显然, 不是这次的提交导致的, 她只是优化了下写法, 我又往前翻了几十个commits, 发现其实从一开始, 就是examples[0]的写法.

image-20220819102746240

看来, 我又错了...看着自己推演的过程, 我不禁开始思考, 问题到底出在哪?

image-20220819103530733

我唯一能想到的, 就是构建系统的人, 一开始就是希望强制+上哈希路由的. 而后面写文档的人, 则是对此并不知情, 在写完文档后也没有去检查效果. 不知道这个推断是否正确, 但其实不重要了. 我的PR使得这种写法变得合法.

结语

其实这个过程还蛮有趣的. 从一开始以为是文档错误, 到化身侦探一路推理排查找到关键点. 其实这个和工作内容很像, 很多问题浮现出来的都是表面问题, 而解决表面问题其实并没有什么难度, 对症下药即可. 但是想要药到病除, 那是不现实的. 唯有多加思考、 推理、 分析, 找到核心问题所在, 从根本上解决问题才是一劳永逸的. 当然, 二者的成本差距也非常大. 依据实际情况抉择即可.

(二更)开源, 比爱情还要神圣

坦白说, 我没想到这篇文章会这么火, 今天一天的时间B站涨粉三位数, 掘金涨粉也有一些, 文章点赞过百. 感谢大家对我的认可. 也因为这个PR, antv团队打算送我一点小礼品.

图片.png

礼物是啥不重要, 重要的是人家的心意. 其实开源和爱情一样, 都是需要双向奔赴的. 如果我提了PR, 官方迟迟没有跟进, 我既拿不到想要的结果, 也得不到认可. 我一定会选择close掉, 不会再给这个组织提PR. 反言之也一样, 如果团队在输出, 而开发者提的issue连最基本的信息都不全, 总是要消耗精力去人工询问, 那么开源者也会心力憔悴. 我之前提过的一些比较有含金量, 自我满意的开源PR, 都是在官方跟进的情况下持续输出的. 大家可以看我之前给X6官方提的一个feature级PR: juejin.cn/post/704564…

可以看到, 当时在写那个feature的时候, 因为不熟悉X6底层实现而遇到了一些问题. 所幸X6官方及时给予了我支援, 帮助我跨过了最关键的几个小坑. 我才得以完成这个作品. 虽然很多人说, 程序员这个行业很奇怪, 总是把生产资料共享出来. 本来是壁垒的东西, 却做成了人皆有之的东西. 坦白说, 我也说不清楚为什么这个行业会有这个现象. 我只知道, 我能帮助到大家, 能够为开源社区做一些有价值的贡献, 我会觉得非常开心.

看着大家这么认可我, 能从我的文章中学到了些经验, 我发自内心的满足. 我打算抽空再修一个bug(其实只要你想修, antv的bug你还真修不完, 一坑更比一更深), 届时会以类似的风格再做一次分享! 在这之前, 大家可以点一点关注, 到时候就会有自动推送了. 希望开源社区在你我的努力下, 变得愈加辉煌!


最新一期: 从百草园修到三味书屋, 我又给蚂蚁金服提PR了!