搜索 K
主题
@vaultkit/vitepress-plugin-giscus 是 giscus(基于 GitHub Discussions 的评论系统)的轻量 Vue 集成。它不引官方的 @giscus/vue——那层 web component 封装对这里的场景是纯开销——而是直接注入 giscus 官方 client.js,并针对 VitePress 这类 SPA 场景解决三大痛点:明暗主题热同步、路由切换重挂、视口门控保 bfcache。
组件本身框架无关:不 import vitepress,明暗态与页面标识全部由宿主经 props 驱动,任何 Vue 3 SSR 站点都能用。本文覆盖全部 props / options、giscus.app 上生成四个 ID 的完整步骤,以及三大痛点的设计细节。本站文章末尾的评论区就是它驱动的。
giscus 把评论存进 GitHub Discussions:无自建后端、数据在自己仓库里、评论者用 GitHub 账号登录——技术博客的理想选择。但把官方 <script> 嵌进 VitePress 有三个坑:
mapping: pathname 时 iframe 在加载期读父页 URL,Layout 常驻组件不销毁,评论会一直停在首次挂载的页面SubframeIsNavigating 为由破坏 bfcache 资格(前进后退失去秒开)本包用约 130 行源码把三个坑全部填掉,零额外依赖。
组件需要 repo / repoId / category / categoryId 四个参数,完整生成步骤:
选一个公开仓库(可以就是博客仓库,也可以专门建一个评论仓库)。私有仓库 giscus 无法读取。
访问 github.com/apps/giscus 安装,授权访问上述仓库(建议只授权所需仓库而非全部)。
仓库 Settings → General → Features,勾选 Discussions。然后在 Discussions 页建一个专用分类,官方建议用 Announcements 类型——只有仓库维护者与 giscus 能创建新讨论,防止路人手工开帖干扰映射。
打开 giscus.app,在「仓库」一栏填入 owner/repo。校验通过后选择映射方式与 Discussion 分类,页面下方「启用 giscus」处生成的 <script> 标签里就有 data-repo-id(R_ 开头)与 data-category-id(DIC_ 开头)——把四个值抄进组件 props 即可,其余 data-* 属性不用抄(本组件的 options 已覆盖)。
pnpm add -D @vaultkit/vitepress-plugin-giscus使用 @vaultkit/vitepress-theme-vault 主题则开箱即带,站点配置里填四个 ID 就能点亮评论区。
推荐挂在 Layout 的 #doc-after 插槽(正文之后),用 useData / useRoute 接线明暗态与路由:
<script setup lang="ts">
import { GiscusComments } from '@vaultkit/vitepress-plugin-giscus'
import { useData, useRoute } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
const { isDark, frontmatter } = useData()
const route = useRoute()
</script>
<template>
<DefaultTheme.Layout>
<template #doc-after>
<GiscusComments
v-if="frontmatter.comments !== false"
repo="owner/repo"
repo-id="R_kgDOxxxxxxx"
category="Announcements"
category-id="DIC_kwDOxxxxxxx"
:dark="isDark"
:page-key="route.path"
:options="{ lang: 'zh-CN' }"
aria-label="评论"
/>
</template>
</DefaultTheme.Layout>
</template>isDark 接 dark、route.path 接 pageKey,两根线接好后主题同步与路由重挂全自动。是否渲染的门控(如 frontmatter 关评论)交给宿主 v-if。
本包发布的是含 .vue 源文件的产物,消费方 VitePress 项目必须把它加进 vite.ssr.noExternal,否则 SSR 构建会报错:
// .vitepress/config.ts
import { defineConfig } from 'vitepress'
export default defineConfig({
vite: { ssr: { noExternal: ['@vaultkit/vitepress-plugin-giscus'] } },
})| 导出 | 形态 | 说明 |
|---|---|---|
GiscusComments | Vue 组件(具名 + 默认导出) | 评论区本体 |
GiscusOptions | 类型 | options prop 的形状 |
string,必填repo 是 owner/repo 形式;repoId 与 categoryId 是 GraphQL node ID(分别 R_ / DIC_ 开头),不是仓库名或分类名——填错的典型症状是评论区显示「giscus is not installed on this repository」或干脆空白。boolean,默认 falseuseData().isDark 即可。组件刻意不自己探测暗色(比如读 html.dark 类)——探测方式因主题而异,由宿主注入才是框架无关的正解。string,默认 ''useRoute().path。这是 SPA 集成的命门(原理见设计机制一节),不接这根线的后果是:切换文章后评论区还是上一篇的。GiscusOptions,默认 {}data-* 配置的可覆盖子集,八个字段逐一说明:| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
mapping | string | 'pathname' | 页面 ↔ Discussion 的映射方式。pathname 最稳(URL 即身份);也可用 url / title / og:title 等 giscus 支持的值 |
strict | boolean | true | 严格匹配(哈希校验),防止标题相近的页面串评论。注意本组件缺省开启,比 giscus 官方缺省更严 |
reactionsEnabled | boolean | true | 主帖顶部的表情反应行 |
inputPosition | 'top' | 'bottom' | 'top' | 输入框在评论列表上方还是下方 |
lang | string | 'en' | giscus 界面语言,中文站点传 'zh-CN' |
loading | 'lazy' | 'eager' | 'lazy' | iframe 的加载策略(在组件自己的视口门控之上再叠一层浏览器级懒加载) |
lightTheme | string | 'light' | 亮色主题名(giscus 内置主题名或自托管 CSS 的 URL) |
darkTheme | string | 'transparent_dark' | 暗色主题名。缺省透明底,融入站点自身的暗色背景 |
定制示例:
<GiscusComments
repo="owner/repo"
repo-id="R_xxx"
category="Announcements"
category-id="DIC_xxx"
:dark="isDark"
:page-key="route.path"
:options="{
lang: 'zh-CN',
inputPosition: 'bottom',
reactionsEnabled: false,
darkTheme: 'noborder_dark',
}"
/>另外有一项被组件写死:data-emit-metadata 恒为 '0'——元数据回传对评论区场景没有用处,关闭省一条消息通道。
string,默认 'Comments'<section> 容器的 aria-label,屏幕阅读器的地标导航靠它定位评论区。中文站点传 aria-label="评论"。giscus iframe 支持运行时接收配置消息。组件 watch 计算出的主题名(dark 与 lightTheme / darkTheme 共同决定),变化时向 iframe postMessage:
iframe?.contentWindow?.postMessage(
{ giscus: { setConfig: { theme: theme.value } } },
'https://giscus.app',
)切换明暗时评论区原地换肤,不重新加载 iframe、已输入一半的评论草稿不丢。target origin 锁死 https://giscus.app,消息不会泄漏给其他源。
mapping: pathname 时,giscus iframe 在加载期读取父页 URL 来决定挂哪条 Discussion。VitePress 的 Layout 是常驻组件,SPA 路由切换时不会销毁重建——iframe 一旦加载完就再也不看 URL 了。所以组件 watch(pageKey),变化时清空容器、重新走一遍注入流程,让 iframe 以新 URL 重新加载。这就是宿主必须接 :page-key="route.path" 的原因。
评论区在文章末尾,绝大多数读者的首屏根本看不到它。挂载即注入 script 有两笔隐性成本:giscus iframe 在页面加载期就开始导航,跟正文图片抢带宽;且加载中的子 iframe 会让 Chrome 以 SubframeIsNavigating 为由取消页面的 bfcache 资格——用户点出去再按返回键,本该秒开的页面变成完整重载。
组件的解法是 IntersectionObserver 布防:容器进入「视口下方 500px」范围才真正注入 script(提前量保证滚到评论区时它已就绪);pageKey 变化后重置门控,新页面的评论区同样等待滚近;不支持 IntersectionObserver 的老环境直接注入兜底。
组件模板只有一个空 <section> 容器,SSR 输出确定性 HTML,script 注入只在客户端视口门控后发生——没有水合差异,也没有服务端请求 giscus 的可能。
lightTheme / darkTheme 可以传完整 URL(如 https://example.com/giscus-custom.css),做到评论区与站点视觉完全统一frontmatter.blogPost + 配置门控)Q:评论区空白 / 报「not installed」? 逐项核对:仓库公开、giscus app 已安装并授权该仓库、Discussions 已开启、四个 ID 与 giscus.app 生成值完全一致(repoId 是 R_ 开头的 node ID,不是仓库名)。
Q:切换暗色后评论区没跟上?dark prop 没接线。确认传的是响应式的 isDark(useData() 解构出的 ref),而不是一次性布尔值。
Q:切换文章后评论还是上一篇的?pageKey 没接线,传 route.path 即可。
Q:为什么滚到底才加载评论? 这是刻意的视口门控(提前 500px),保首屏带宽与 bfcache。想恢复「挂载即加载」目前没有开关——门控的收益远大于成本。