十年老博客修复计划

话说我的学校有一个Linux社团,这个社团成立了十余年之久(2013年至今),大约是在成立一年后,社团有了自己的博客站点。彼时的会长提交了部署脚本,使用Grunt执行,最终构建出了社团的博客,此后的1年内,社团博客持续更新,2015年结束后,社团博客便也停止了更新。随着技术的迭代,后来的人写了一篇文章准备发到博客上时,发现彼时的脚本、配置文件已经过期,没有办法再像之前一样直接部署,这也就意味着,恰好我此刻已经差不多完成毕设的工作了,于是Fork了一下仓库,开始修复工作。
诊断
不首先找到问题所在,修复工作自然也无从谈起。我首先找到原博客的部署指南,写得可真是简明:
首先需要安装Node.js和Ruby,然后执行
1
2
3 bundle install
npm install
bower install接下来修改
_config.yml
即可。
Node.JS我有,大版本号为22,Ruby没有,安装一下最新版即可,毕竟是修复计划,那么只要确保博客能够在当前的新版本下跑起来即可,社团里有的人认为我应该使用nvm来安装旧版本Node.JS,但我不以为然,既然我是要更新博客,那么光是跑起来博客是不够的,还需要让博客的依赖、配置文件和脚本之类的小玩意都能够得到更新,光是使用nvm显然不能达到这些目的。
接着让我们开始安装相应的依赖。
提示
编写本文时已经是2025年7月,距离修复社团博客的日期已经过了近两个月,其中的一些细节早已忘记,所以一些关于修复工作的描述可能会与实际有出入。
按照原版说明里给出的命令来执行后,发现几个问题
bundle install
没反应。package.json
里面的依赖版本过低,没有办法在22版本的Node.JS下正常运作。bower.json
问题同上。
问题修复
问题1
bundle install
执行后没有反应,说白了其实就是网络问题,没法与源站建立连接,最终bundle只会无限等待,解决办法有很多,一般来说可以用国内镜像站来搞定,当然有条件的话也可以使用网络代理。
问题2
package.json
所给定的依赖版本过低,没法在22版本的Node.JS下正常运作。
这个问题其实是因为package.json
里指定各种依赖版本时,使用了“~”符号来限定了各种依赖的大版本,这些大版本已经非常远古,无法在现在的Node.JS LTS版本上正常运作,因此便陷入了鱼与熊掌困境:要保证大版本,就不能保证在新版本Node.JS上运行;要保证在新版本Node.JS上运行,则不能保证大版本符合依赖需求。因此执行安装依赖任务时,npm报错。
一种简单粗暴的方法是将所有的版本号都设置为“*”,指定始终使用最新版本的依赖,不过每当依赖包含了破坏性变更,要求开发者对项目做出一些修改才能正常运作时,npm同样会报错。此外,如果当前版本的Node.JS 22版本已经成为过去式,不再是LTS版本时,最新版本的依赖可能也会无法正常安装。
虽说使用星号代替版本号的方法并没有那么好,但是我最终还是采用了这种方法。
提示
有一些工具可以完成这些操作,只不过我当时不太了解,所以最终还是直接用星号来当版本号。或许我会在有空时修复这些,又或许等某个有缘人帮忙。
版本号更新后,npm就可以正常地安装依赖了。
问题3
bower.json
包含的依赖版本过低。
这是与问题2一样的情况,同样的,我也实在是没有什么兴致研究如何用最优解安装所有依赖,所以最终还是用了和问题2一样的方法。
其他问题
在Windows下预览博客的时候,Jekyll无法获取到时区信息。好在这个问题并不难解决,增加了一个时区依赖后便解决了。
引发的新问题
还记得问题2中将所有的依赖都更新到最新的操作吗?被更新的依赖里面包括了Grunt这个自动构建的工具,并且更新前后的两个版本在构建脚本上有一些语法上的差异,不巧的是,博客用到的构建脚本里面恰好就涉及到了这个差异。
成功安装完所有的依赖后,便开始尝试构建博客,失败,Grunt提示无法找到样式表。
查看控制台的日志后,定位到了几个原因。
SCSS样式表无法读取
控制台的日志里面,Grunt给出提示说无法找到样式表(Stylesheets),那么只需要寻找到那些可能涉及到读取样式表的代码即可。
在翻看了Grunt.js
后,我发现了读取样式表的代码,沿着代码指定的路径进行了检查,我发现样式表文件均正常存在,于是立即想到路径的指向可能存在问题,毕竟代码中使用的路径是相对路径,如果工作目录和期望的位置不一样的话,那么Grunt无法读取到样式表也是非常正常的。要验证这一想法,我复制了样式表文件夹的绝对路径,把原来的相对路径进行了替换,如此一来,即使工作目录和预期不符,绝对路径也能保证Grunt始终读取到应该读取的位置。
保存,构建,然后还是因为同样的问题失败。
需要读取的文件实打实地存在,并且使用绝对路径也不能解决问题,这些情况都说明了程序始终都没有读取到目标文件夹,并且没有读取到的原因(暂时)不是路径错误或是目标文件不存在。经过一番辛苦的排查,最终确定是Grunt版本更新后,原Grunt.js
中包含的一些写法已经过时,其中包括指定路径时使用的语法,于是按照最新的语法指南,更新了一遍Grunt.js
中的语法。
保存,构建,还是错误,不过这次是因为新的问题。
未定义的对象
在SCSS中指定了vertical-rhythm选择器:
1 | @extend %vertical-rhythm; |
然而在实际构建过程中提示vertical-rhythm未定义,这个问题比较奇怪,为其添加!optional
后,整个博客站点与早期构建的站点可以说是并无二异,正如图形学第一定律里面所述的那样,如果它看上去是对的,那么它就是对的。既然有没有这个玩意都正常工作,那么就(暂时)不必折腾,因此我最终只是为其添加了!optional
关键字来解决问题。
保存,构建,还是不行,又是新的问题。
spawn EINVAL
这个问题是因为Windows下高版本Node.JS所专有的问题,至少看他人描述此问题时是这么说的。
为了解决(CVE-2024-27980) - (HIGH)问题,Node.JS限制了Windows下调用child_process.spawn时传入文件的行为,换句话说,如果在没有显式指定{ shell: true }
的情况下为child_process.spawn传入了文件,那么Node.JS将会报错。如果显式指定了前面所述的参数,则意味着开发者自行确保所传入的文件不存在执行高危代码的可能。
虽然说最终的构建和部署是在GitHub Pages上,不出意外,构建时用的系统还是Ubuntu,但推送代码之前,总归是要在使用Windows的本地电脑上预览的,所以解决这个问题还是很有必要的,除非我有钱买一块新的硬盘扩容,并且往里面装某个Linux发行版系统,那样的话说不定就不用管这个问题了。
经过一番排查,这个问题出现在grunt-jekyll上,通过修改相应的js文件,为里面调用spawn函数的代码额外增加了{ shell : true }
参数,如此一来就能在Windows上进行预览了。
光是修改依赖里面的代码显然是不行的,哪天一个更新就会覆盖掉原来的代码,为了解决这一问题,引入了patch-package,可以在每次更新的时候根据需要,自动进行修补,尽可能保证更新依赖的同时保留原有的补丁,发给其他人用的时候,其他人也能够很方便地打上你用的补丁。
保存,构建,成功。这个有着十余年历史的社团博客如今终于更新成功。
是@use还是@import
虽然说博客更新成功了,但是在预览期间,SCSS通过控制台给出了警告,说@import将被逐步废弃,不再使用。原本作为更新计划的一部分,我打算将这些代码重写一遍,但是我很快发现这并不可行,@import默认作用于全局作用域,没有命名空间,而@use则有局部作用域,应当通过命名空间访问,这种描述可能比较抽象,具体化到本项目上,就是说许多代码访问变量的形式都需要适配成有命名空间的形式,这就像是改写一个全程依赖using namespace std
完成的C++项目,使其不是直接访问对象,而是使用std::
来访问对象一样,这当然也不是很复杂,但工作量实在是太大了,几乎每个文件都需要做出大量的修改。考虑到这是一个影响比较明显的更新,我猜官方应该会有什么工具可以自动完成迁移工作。果不其然,确实有这么一个工具叫SASS-Migrator,它可以自动化地改进,将SCSS文件从Node Sass迁移到Dart Sass。我尝试将这个工具下载下来,然而迁移工作并不顺利,不管怎么处理都会不断有新的问题,既然如此大费周章,不如直接手动迁移所有文件,然而规模实在是太过庞大,我实在没有太多精力一个一个迁移完毕,尤其是还会有“牵一发而动全身”的问题,一个文件的修改可能会让原本能工作的SCSS文件失效,要完全迁移,工作量绝对不小,所以还是先搁置了。
部署
经过一番掰扯,总算修复完成,不过,博客的部署方式仍然还是手动构建+自动部署的形式,也就是说,每次写完博文后,用户需要自行使用Grunt,基于源代码分支构建出博客文件,然后放到部署分支,最终将两个分支一起推送到GitHub仓库上,要说这还是太麻烦了一点,毕竟现在我们有GitHub Action的存在,很多操作完全不用手动处理了,于是我立即写了一个脚本,在每次推送、合并代码的时候,GitHub Action都会立即采取行动,将最新的源代码分支构建成博客,然后将这部分内容推到博客分支,博客分支一有更新,GitHub Pages就会自动更新页面,完成部署,整个过程不需要人工干涉。然而,每次GitHub Action会在PR合并时进行部署检查,并将结果反馈出来,当PR提交人没有仓库的读写权限时,则部署检查会始终失败,只有当合并进入代码以后,部署检查才会成功,简单看了一下原因,是因为权限的问题。当有权限的人合并了PR,并且产生了合并记录(Merge pull request #x from yyy/zzz),那么就能正常地构建并部署,也许这个问题不难处理,不过考虑到我没有社团博客仓库的权限,搞起来不是非常方便,指挥仓库管理员处理问题也不太好,所以这个问题的终极解决方案是——相信后人的智慧。