VuePress 部落格優化之 last updated 最後更新時間如何設定

冴羽發表於2022-01-13

前言

《一篇帶你用 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 git@github.com: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,對作者也是一種鼓勵。

相關文章