山地人

开发idev365踩过的坑

山地人
山地人
2021-05-17

美好的开始

idev365的前端我使用的是gatsby,这个框架国内目前使用的还不是很多,但在国外却有很多公司在使用。比如CodeSandBox的官方站点是用的gatsby

我了解gatsby也是从阅读CodeSandBox的源码过程中知道的。基于对于CodeSandBox这款在线沙盒产品的喜好。致使我对gatsby拥有一个良好的第一印象。 于是我专门去了解了gatsby,看到了gatsby种类繁多的插件,活跃的社区。其产品的设计理念和我的要做的内容十分吻合,这让我最终决定使用它来构建我的idev365的新站点。

遇到的第一个坑

快速浏览过gatsby官网后,我就开始搭建基于gatsby的项目了,就在我还处于兴奋的状态中,我碰到了第一个问题。gatsby在处理照片素材时,使用了两个库。

mozjpeg
pngquant

这两个包其实是对C语言实现的Native代码的包装库,安装过程中会探测当前系统环境,安装对应平台的可执行文件。问题的关键是这些安装包请求的是github上编译好的资源。可是github在非代理模式下经常是不能访问的。

这就是碰上的第一个问题。查阅了网站的资料,获得的信息是要改动这两个包内部的安装逻辑。也就是在这两个包通过yarn或者npm下载到本地后,在他们的安装过程前,要把包内的请求链接修改成可以访问的链接。比如淘宝镜像的链接。

有了思路就开始行动,首先尝试把下载后的两个包里的请求链接逻辑改成,优先检查环境变量里是否有配置站点,如果有则直接使用变量里定义的站点域名。如果没有再使用原始路径来下载。

-const url = `https://raw.githubusercontent.com/imagemin/mozjpeg-bin/v${pkg.version}/vendor/`;
+// const url = `https://raw.githubusercontent.com/imagemin/mozjpeg-bin/v${pkg.version}/vendor/`;
+
+const site = process.env.PNGQUANT_BINARY_SITE ||
+ process.env.npm_config_pngquant_binary_site ||
+ 'https://raw.githubusercontent.com/imagemin/';
+

这样定义后,只需要在下载前在shell中定义PNGQUANT_BINARY_SITE变量就可以访问指定的站点,绕过无法访问的问题。

刚解决问题的一半

但解决了上面绕过访问的问题,又碰到了新问题。怎么让每次yarn install的是否都能把下载的包内代码都改正确后再执行安装呢?

经过搜索,我找到了patch-package,这个包可以对已经安装好的第三方包打补丁。这个可以解决手动打补丁的问题。但是我们得利用npm或者yarn命令的生命周期Hook钩子函数。

而最接近的钩子函数时postinstall,但这个是在安装包安装成功后才会调用,我们目前的问题发生在安装包下载好准备安装这两个时间段之间,而令人失望的是,npmyarn都没有提供这个阶段的Hook钩子。

通过观察,我发现yarn install实际上在准备安装时,会打印一个 build 然后开始执行每个包的安装过程。所以一个想法诞生了。改造yarn,重新发布一个定制版本的yarn命令,来解决Hook问题。

这样,定制版的yarn会在我本地开发阶段生效,但对于使用的Github Actions提供的环境,我不需要定制yarn,这样两个场景下的自动打包都能解决。

于是下载yarn代码,找到对应的build阶段,创建了新的Hook函数,在package.json中,我定义了一个prebuildstep阶段。

...
"prebuildstep": "patch-package",
...

这样patch-package在安装包准备安装前,代码就会被patch-package修正好,达到我们想要的效果。

一波刚平一波又起

搞定上面的问题,就可以开心写代码了。各种组件的实现,内容的编写,一切都非常顺利。于是打算把老站点的内容也搬移到新站点上进行改造。说干就干,老站已经积累了500多篇文章了。所以,编写了脚本来转换原站点的数据匹配生成新的文件格式。

一切准备妥当,准备编译,然而新的问题又出现了。yarn build进度始终出现运行一段时间后,卡在了某个阶段。然后内存一直往上涨,最后崩溃,编译失败。

再次填坑

既然遇到了问题,我们就得想办法去搞定问题。只有通过一次次踩坑和填坑的过程,我们才能积累经验获得进步。遇到这个问题,先怀疑是内存泄漏。常规思路,升级版本。于是把gatsby相关依赖同步升级到最新,再次编译还是老样子。

然后,搜索官网IssuesStack Overflow,找到一些看似相关的讨论,按照思路去操作,发现没有任何作用。经过了半天的如此这般的操作后,觉得自己应该换个思路。

于是仔细观察日志,发现有几个 warn,但是gatsby里的warn日志显示的却是[object Object],很明显这是object对象被toString()转换成了字符串。

这种warn打出来一点意义也没有,做为开发如果有一点产品思维,应该不会干这种事情,于是找到node_module里的对应安装包,定位出warn的位置。

if (stats.hasWarnings()) {
reporter.warn(`gatsby-plugin-mdx\n` + info.warnings)
}

这个info.warnings只要是非基本类型,进行字符串拼接操作后,打印输出的内容就基本没有可读性了。于是强加了下面这一句

2if (stats.hasWarnings()) {
3 console.log(`Warnings:`, info.warnings)
4 reporter.warn(`gatsby-plugin-mdx\n` + info.warnings)
5}

改完后,重新进行调试。这个warn问题终于浮出了水面,warn报告说gatsby中一个压缩css的插件参数配置不正确,关键是这个参数是gatsby内置写法里的参数,看来写gatsby的人也干活这么粗糙,这种问题不知道要让多少开发掉入坑中(至少我掉坑了)。于是用最简单的方式将这个压缩css的参数问题改正确。

改完后,继续测试,warn不再有了,但是依然会出现编译过程,内存上涨,最后崩溃。

于是开始怀疑是否是自己的文件格式有什么问题,于是移除大部分文章,只保留少量文章进行测试。编译通过了,问题也变得更加难以察觉。于是一点点往里面添加内容,想看在什么时候出现问题。

但是文章越来越多,编译等待的时长越来越长。最让程序员受不了的就是无限等待漫长的编译。于是想办法优化这个过程,使用GitHub Action并行执行我的测试。

每次提交少量几个文件,然后提交触发一次Action编译,这样做比之前单线程的本地操作速度快了许多,也找到了某个临界点时,编译奔溃问题就出现的问题。

于是更换不同的文件测试,想进一步明确崩溃的发生点。但崩溃规则很难定位出来。 就这样持续了两天,定位依然非常困难。因为这种Crash没有可以的日志。

这期间有换了很多思路,比如在gatsby源码的不同阶段添加日志,在奔溃发生前后的阶段补充更多进度内的日志。来细化问题。

出现了新线索

这两天的不同编译观察,最终排查出来了一个新线索,那就是在gatsbyonPostBuild阶段编译奔溃了。然后对这个阶段先找了网路资源,各种关键词查找,相关可能方案实验,都没有获得好的结果。

最后,一篇将onPostBuild too slow的文章里提到的在onPostBuild阶段可能会生成xml全站导航地图可能会引发循环引用导致内存溢出的文章让我获得了新的启发。

于是顺着新思路,排查所有用到的gatsby插件是否有何站点导航地图生成相关的内容。另外所有使用了onPostBuild接口的插件。最终定位出了gatsby-plugin-feed这个插件。

过段去除了这个插件的使用,重新编译当前内容。竟然编译顺利通过。于是把之前搬移出去的内容都放进来,编译也十分顺利通过了。

到这里长舒一口气,定位了3天的Bug总算搞定了。

结束语

虽然这个填坑的过程还是让人挺难熬的,然而时候回忆起来,这个过程让我对gatsby的源码有了更加深刻的理解。另外每一次难缠问题的解决都会给自己下一次处理复杂问题,带来了更多经验和思路。

所以,整过过程还是很有收获的,我的idev365站点又可以往前推进了。

最后,希望大家都能搞定自己遇到的内个坑,不断积累前行,我们一起加油。