Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VuePress 博客优化之 last updated 最后更新时间如何设置 #244

Open
mqyqingfeng opened this issue Jan 7, 2022 · 4 comments
Open

Comments

@mqyqingfeng
Copy link
Owner

前言

《一篇带你用 VuePress + Github Pages 搭建博客》中,我们使用 VuePress 搭建了一个博客,但是浏览最终搭建的站点:TypeScript4 中文文档,我们会发现,在每篇文章的底部,并没有像 VuePress 官方文档那样,出现最后更新的时间:

这篇我们来探究下如何实现最后更新时间。

官方自带

查阅 VuePress 的官方文档,我们可以知道,VuePress 自带显示最后更新时间的插件,在默认主题下,无需安装本插件,因为 VuePress 的 core 中已经包含此插件。

你可以在 config.js 文件中直接使用:

// .vuepress/config.js
module.exports = {
  themeConfig: {
    lastUpdated: '上次更新', // string | boolean
  }
}

注意,themeConfig.lastUpdated 默认是关闭的,在给定一个字符串后,它会作为前缀显示(默认值是:Last Updated)。

在这个示例代码中,我们设置 lastUpdated 的值为 "上次更新",最终的效果应该跟上面的 VuePress 官网的截图是一致的。

在这里,也给了一个使用须知:

由于 lastUpdated 是基于 git 的, 所以你只能在一个基于 git 的项目中启用它。此外,由于使用的时间戳来自 git commit,因此它将仅在给定页的第一次提交之后显示,并且仅在该页面后续提交更改时更新。

那我们按照文档配置一下试试,结果发现并没有出现所谓的最后更新时间,因为没有内容显示,显得这里的空白也很大:

查看插件源码

那这是为什么呢?回顾下我们代码编译和提交的过程,其实我们是在本地写好源码,然后执行构建命令,将构建后的结果 git init,然后强制提交到远程仓库。这从我们的 deploy.sh 脚本文件可以看出:

npm run docs:build

cd docs/.vuepress/dist

git init
git add -A
git commit -m 'deploy'

git push -f [email protected]:mqyqingfeng/learn-typescript.git master:gh-pages

那应该没有什么问题呀,我们就是作为 git 仓库提交的,也提交了一个 commit,为什么会显示不出来呢?

而且说起来,last updated 最后更新时间是怎么生成的呢?他怎么就根据 git 的提交记录自动生成时间了呢?

为了解决这个问题,也为了满足我们的好奇心,我们不妨去看下这个 npm 包 @vuepress/plugin-last-updated源码,结果发现它的源码意外的简单:

const path = require('path')
const spawn = require('cross-spawn')

/**
 * @type {import('@vuepress/types').Plugin}
 */
module.exports = (options = {}, context) => ({
  extendPageData ($page) {
    const { transformer, dateOptions } = options
    const timestamp = getGitLastUpdatedTimeStamp($page._filePath)
    const $lang = $page._computed.$lang
    if (timestamp) {
      const lastUpdated = typeof transformer === 'function'
        ? transformer(timestamp, $lang)
        : defaultTransformer(timestamp, $lang, dateOptions)
      $page.lastUpdated = lastUpdated
      $page.lastUpdatedTimestamp = timestamp
    }
  }
})

function defaultTransformer (timestamp, lang, dateOptions) {
  return new Date(timestamp).toLocaleString(lang, dateOptions)
}

function getGitLastUpdatedTimeStamp (filePath) {
  let lastUpdated
  try {
    lastUpdated = parseInt(spawn.sync(
      'git',
      ['log', '-1', '--format=%at', path.basename(filePath)],
      { cwd: path.dirname(filePath) }
    ).stdout.toString('utf-8')) * 1000
  } catch (e) { /* do not handle for now */ }
  return lastUpdated
}

通过源码,我们可以发现,之所以能生成时间,其实是通过利用 git log 命令获取时间,然后写入 $page 全局属性中。

$page

那 $page 是什么呢,我们查看官方文档 - 全局计算属性章节:

在 VuePress 中,内置了一些核心的计算属性,以供默认主题或自定义主题使用。

对于每个页面,都会有一个 $page 计算属性,官方还给了一个 $page 的示例:

{
  "title": "Global Computed",
  "frontmatter": {
    "sidebar": "auto"
  },
  "regularPath": "/zh/miscellaneous/global-computed.html",
  "key": "v-bc9a3e3f9692d",
  "path": "/zh/miscellaneous/global-computed.html",
  "headers": [
    {
      "level": 2,
      "title": "$site",
      "slug": "site"
    },
    {
      "level": 2,
      "title": "$page",
      "slug": "page"
    },
    ...
  ]
}

我们由此可以想到,VuePress 其实针对每个页面都生成了一个 $page 计算属性,然后在多个地方展示计算属性的值,那突破口就来了,如果我们像源码那样,直接向 $page 写入一个 lastUpdated 属性呢?

Markdown 中使用 Vue

我们每个页面都是根据 markdown 文件生成的,我们怎么在一个 markdown 文件中向 $page 写入一个 lastUpdated 属性呢,还好在 VuePress 中,markdown 是可以直接使用 Vue 语法的,这里使用 vuepress-theme-reco 主题的示例代码作为讲解:

// 在 markdown 文案中直接写

<p class="demo" :class="$style.example"></p>

<style module>
.example {
  color: #41b883;
}
</style>

<script>
export default {
  props: ['slot-key'],
  mounted () {
    document.querySelector(`.${this.$style.example}`)
      .textContent = '这个块是被内联的脚本渲染的,样式也采用了内联样式。'
  }
}
</script>

在页面的展示效果如下:

那么我们直接在 Markdown 中直接写入如下的 Vue 代码:

<script>
export default {
    mounted () {
      this.$page.lastUpdated = "2022/1/6 下午6:09:09";
    }
  }
</script>

我们本地运行代码,然后查看页面,就会发现真的成功写入了最后更新时间:

Vue 组件

但是这样每篇都插入一段 Vue 代码,很麻烦呀,我可以勉强接受手动更新一下时间,但我不能接受每次那么多篇都要手动改一下呀,即便用批量替换也有点麻烦,就没有更优化的方式吗?

那我们可以将这段 Vue 代码抽离成一个 Vue 组件,将具体的时间封装在组件中,然后每个 md 文件引入这个组件即可,而 VuePress 也是可以支持引入组件的。查看下官方文档

所有在 .vuepress/components 中找到的 *.vue 文件将会自动地被注册为全局的异步组件,如:

.
└─ .vuepress
   └─ components
      ├─ demo-1.vue
      ├─ OtherComponent.vue
      └─ Foo
         └─ Bar.vue

你可以直接使用这些组件在任意的 Markdown 文件中(组件名是通过文件名取到的):

<demo-1/>
<OtherComponent/>
<Foo-Bar/>

那我们就在 .vuepress 文件夹下新建一个 components 文件夹,然后创建一个 LastUpdated.vue 文件,代码为:

<template>
</template>

<script>
export default {
  props: ['slot-key'],
  mounted () {
      this.$page.lastUpdated = "2022/1/6 下午2:00:00";
  }
};
</script>

然后我们在具体要使用这个时间的 md 文件里写入:

<LastUpdated />

自然也是成功的写入了时间:

这样我们每次修改 LastUpdated 组件里的时间,所有引入该组件的地方时间都会改变。

Markdown

这个问题看似是通过曲线救国的方式解决了,就是每次都需要手动更新一次,而且所有引用的地方都会更新,如果要实现某个地方局部更新,还要自己写入时间,其实还是有点麻烦的。

让我们再回顾下官方文档里的介绍:

由于 lastUpdated 是基于 git 的, 所以你只能在一个基于 git 的项目中启用它。此外,由于使用的时间戳来自 git commit,因此它将仅在给定页的第一次提交之后显示,并且仅在该页面后续提交更改时更新。

还有官方文档里的这句

lastUpdated 是这个文件最后一次 git 提交的 UNIX 时间戳

那这句里的 「这个文件」到底是指哪个文件呢?回想下我们的提交方式,我们每次只提交了编译后的文件,编译的文件为 HTML 文件,虽然它是以 git 仓库的形式提交的,但在它编译完后就应该已经是静态的了,不可能是在运行 HTML 文件的时候再通过 git 仓库获取最后提交时间的吧,所以这个文件不应该是指编译后的文件,那排除掉编译后的文件,其实我们就可以想到,这里的文件指的其实应该是你编写的 markdown 文件。在你执行编译命令的时候,从 git log 中获取时间,写入编译后的代码中。

这也可以解释为什么我们按照官方文档配置后,没有显示时间,因为我们的源码确实不是 git 仓库,我们只是把编译后的代码生成了一个代码仓库提交上去,那自然是获取不到时间。

所以我们进入源码的根目录,然后执行 git init,要注意,如果要生成最后更新时间,需要:

  1. 仓库 git init
  2. 文件至少 commit 一次

但当我们 git add 的时候,可能会报错,会提示你已经添加了另外一个 git 仓库在你当前的仓库:

You've added another git repository inside your current repository.

这是因为我们编译生成的 dist 目录也是一个 git 仓库,但其实解决方式很简单,我们直接用一个 .gitignore 文件 将 dist 目录忽略掉不就好了,这是 .gitignore 文件写入的内容:

node_modules
dist

我们 commit 后再运行代码,就会看到每篇文章都会出现最后更新时间:

Last updated

如果你想更改时间前面的 Last Updated: 这段字符,你可以在 config.js 写入:

module.exports = {
    themeConfig: {
        lastUpdated: '上次更新'
    }
}

展示的效果就会变为:

更改格式

如果你想要更改时间展示的格式,参照 @vuepress/plugin-last-updated 的官方文档,你可以这样写:

const moment = require('moment');

module.exports = {
  plugins: [
    [
      '@vuepress/last-updated',
      {
        transformer: (timestamp, lang) => {
          // 不要忘了安装 moment
          const moment = require('moment')
          moment.locale(lang)
          return moment(timestamp).fromNow()
        }
      }
    ]
  ]
}

展示的效果为:

至此,虽然绕了一圈,但还是简单的解决了这个问题。

系列文章

博客搭建系列是我至今写的唯一一个偏实战的系列教程,讲解如何使用 VuePress 搭建博客,并部署到 GitHub、Gitee、个人服务器等平台。

  1. 一篇带你用 VuePress + GitHub Pages 搭建博客
  2. 一篇教你代码同步 GitHub 和 Gitee
  3. 还不会用 GitHub Actions ?看看这篇
  4. Gitee 如何自动部署 Pages?还是用 GitHub Actions!
  5. 一份前端够用的 Linux 命令
  6. 一份简单够用的 Nginx Location 配置讲解
  7. 一篇教你博客如何部署到自己的服务器

微信:「mqyqingfeng」,加我进冴羽唯一的读者群。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者 有所启发,欢迎 star,对作者也是一种鼓励。

@fangcuidong
Copy link

✅ 已添加

@1360151219
Copy link

这个太棒了,我目前一直以来都是自己手动改文章时间的 😀

@bit15k
Copy link

bit15k commented Jan 26, 2022

last update没看懂,我自动编译后推送后所有文档最后编辑时间永远都是最近提交的时间,醉了

@Peter-JXL
Copy link

冴羽哥您好,我测试了下,发现如下情况:

  1. 不使用主题的情况下,直接在config.js里配置 lastUpdated: '上次更新', 这一行,就可以正常显示更新时间的;而且每个文档的时间都不同;
  2. 用reco主题的话,就不能正常显示更新时间…………
  3. 所以这个可能是主题的bug
  4. 2022年8月6日 16:23:20 我看官网里的使用方法是 这样的,在我使用reco主题的情况下,仍能正常显示,
module.exports = {
plugins: ['@vuepress/last-updated']
}

综上,可以用第4点的方法解决更新时间的问题,像自己看源码与深入看VuePress文档 并且写组件的话,还是挺花时间的,还好有冴羽哥大佬,给我们看到了遇到问题时要怎么解决

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants