给 Gataby 文章添加修改日期

Devrsi0n ▴ 
更新于 

封面来自

用 Gatsby 框架开发博客是一件颇为惬意的事情,可以很轻易的使用 React 社区的很多资源,定制化自由度颇高。最近想要给博客文章添加最近更新时间戳功能,但这个功能 Gatsby 框架和插件并不能直接提供(issue),基于 Git 仓库本地 Markdown 文件的博客都会面临这个问题,下面是我的解决方案,思路也适用于类似的 static site generator.

如果你是用的 contenful 这类 CMS 平台就简单了,可以找下平台 API 文档,一般可以直接调用。

方案一

给每个文档 md/mdx 添加一个新的字段 - updatedAt,每次改完 md 文件手动更新这个字段。

例如:

md
1---
2title: 给 Gataby 文件添加修改时间
3author: devrsi0n
4date: 2020-03-20
5updatedAt: 2020-03-21
6excerpt: 自动生成指定文件的修改时间戳,添加到 Gatsby Node
7hero: ./images/hero.jpg
8---

这个方案成本较高,增加了写文档的隐形成本,而且也很容易忘记。显然这个方案不够优雅。

方案二

给 Gatsby Node 增加 updatedAt 字段,每次 GraphQL 查询时自动获取这个字段。现在问题变成了怎么给 Gatsby Node 新增字段,以及怎么获取每个文档的最近更新时间。第一个问题好解决,翻一翻 Gatsby 文档就能轻松找到,第二个问题比较麻烦。

mtime

首先尝试直接读取文件 mtime,这个 Gatsby 文件标准字段,获取文件的最近更新时间。

graphiql example

但这个方案有很大的弊端,每次重新 git clone 之后所有文件最近更新时间 mtime 都变成克隆的时刻,这是 git 的一个 bug,参考stackoverflow 链接。如果你可以保证你只在同一个本地仓库写文章,那这个方案或许也能用。

考虑总有意外情况,比如:换电脑,甚至电脑丢了,抑或文章接收其他读者修改的 Pull Request,这个方案也不 OK。

Git log

既然 git 本身不会把最近更新时间写到文件,那直接从 git log 读取指定文件的最近 commit 时间也是个办法。参考命令如下:

bash
1# the committer time of the last commit
2git log -1 --pretty=format:%cI -- package.json
3# the committer time of the most recent commit
4git log --pretty=format:%cI -- package.json | sort | tail -n 1

但这个方案也有个问题,在 zeitnetlify 这类自动构建部署平台,为了提高构建速度,构建过程并不会完整克隆整个仓库,而只拉取最新 commit 代码。

Gatsby 插件 gatsby-transformer-gitinfo 也是做类似事情,只是并没有解决构建时的问题。

最终方案

最终方案是解决平台构建时因不会 git clone 导致不能获取文件的最近更新时间。方法很简单,每次 git staged 时把 md/mdx 文件最近更新时间写入 JSON 文件,并提交 git,最后 Gatsby 构建时从 JSON 读取最近更新时间。

生成文件最近更新时间 JSON

这里没有用 git log 提取时间,因为 git staged 时还没有 commit,参考脚本如下(代码位于本仓库 ./tasks/update-post-timestamps.js`):

update-post-timestamps.js

js
1const moment = require('moment-timezone');
2const path = require('path');
3const fs = require('fs').promises;
4const { promisify } = require('util');
5const exec = promisify(require('child_process').exec);
6const postTimestamps = require('../src/gatsby/node/postTimestamps.json');
7
8/**
9 * This function called at git staged
10 */
11(async function() {
12 // Show staged files only
13 const { stdout: gitDiff } = await exec('git diff --staged --name-only');
14 const stagedFiles = gitDiff.matchAll(/content\/[\S]+\/[\S]+.mdx?/gm);
15 if (stagedFiles.length === 0) {
16 // MDX files are not modified
17 return;
18 }
19
20 const statPromises = [];
21 for (const [file] of stagedFiles) {
22 statPromises.push(fs.stat(file).then(stat => ({ stat, file })));
23 }
24 const statList = await Promise.all(statPromises);
25 for (const { stat, file } of statList) {
26 const updatedAt = moment.tz(stat.mtime, 'Asia/Shanghai').format();
27 postTimestamps[file] = {
28 updatedAt,
29 };
30 }
31
32 const targetFilePath = path.resolve(
33 __dirname,
34 '../src/gatsby/node/postTimestamps.json'
35 );
36 await fs.writeFile(targetFilePath, JSON.stringify(postTimestamps, null, 2));
37 await exec(`git add ${targetFilePath}`);
38})();

⚠️ 注意 这段代码应该在 git staged 时执行,避免代码重新克隆之后所有文件的最近更新时间被覆写的 Bug。JS 添加 git hook 也很简单,参考如下。

安装依赖, npm i -S husky lint-staged,package.json 添加配置如下:

package.json

json
1{
2 //...
3 "husky": {
4 "hooks": {
5 // git commit 之前执行 lint-staged
6 "pre-commit": "lint-staged"
7 }
8 },
9 "lint-staged": {
10 // lint-staged 匹配到 mdx/md 文件执行该命令
11 "**/*.{mdx,md}": [
12 "node ./tasks/update-post-timestamps.js"
13 ]
14 }
15}

Gatsby 节点添加 updatedAt 字段

平台构建时,从 JSON 读取最近更新时间,在 gatsby-node.js 添加 Gataby Node Field 即可。

gatsby-node.js

js
1const postTimestamps = require('./postTimestamps.json');
2
3module.exports.onCreateNode = async function onCreateNode({ node, actions }) {
4 const { createNodeField } = actions;
5
6 if (node.internal.type !== `File`) {
7 return;
8 }
9 const relativePath = path.relative(process.cwd(), node.fileAbsolutePath);
10 const timestamp = postTimestamps[relativePath];
11 createNodeField({
12 node,
13 name: `updatedAt`,
14 // 如果没有找到给一个不可能的值,避免冗余的判断
15 value:
16 timestamp
17 ? timestamp.updatedAt
18 : '1992-10-15T10:53:18+08:00'
19 });
20}

至此,自动生成文章最近更新时间的功能完成,希望能帮到遇到类似问题的读者。

在 GitHub 上编辑此文

其他文章

🚀 提高博客页面性能

通用 web 网站提速技巧

2019-11-15

使用 Electron 时 GitHub Actions 执行失败

GitHub Actions 是 GitHub 官方近期推出的免费自动化软件开发流程服务,可用来替代 circleci/travis 这类第三方服务,而且因为榜上微软这个金主,GitHub 的官方服务构建速度和易用度都挺不错的。比如下面著名 iOS 大神 onevcat…

2019-11-10
© 2019 – 2020 devrsi0n
Link to $https://bit.ly/2NcAZQZLink to $https://github.com/devrsi0nLink to $https://weibo.com/qianmofeiyu