ChoDocs 的 VitePress 插件折腾记录
如果你也想拥有和 chodocs.cn 一样的 vitepress 文档效果,不妨从这篇文章看起。
事先声明:在自己配置之前查看一下是否版本和我目前是一致的,一般而言版本号相差个位数影响不大,如果相差较大建议升级一下 vitepress 版本并结合官方文档修改。
另:本文档目前是开源的,一些改动可能会在未来的更新中被修改。其次,因为个人时间有限,如果你发现了一些问题或者相关配置文档难以理解不清楚怎么配置,欢迎提 issue 或者 pr。代码其实已经开源了,我想爱折腾的各位根据文件引用关系即可完成 cv 操作。
相关说明
对于下文中的一些 npm 包,如果你有时间可以来提提 pr 写上安装代码,方便自己也帮助他人,我会非常感谢的。
TODO
- 一些 npm 包的安装代码
- 详细说明
配置 Vite
核心文件源代码在这里:
示例代码
以下是示例代码,也许是过去的某个版本的,并非一定是最新的,最新代码请查看上述源代码。
文件路径 docs/vite.config.ts
,如下:
遇到提示说需要安装的包,可参考 chodocs 的
package.json
文件中的devDependencies
部分,按需安装即可。
import { resolve } from 'node:path'
import { defineConfig } from 'vite'
import UnoCSS from 'unocss/vite'
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
import Components from 'unplugin-vue-components/vite'
import { MarkdownTransform } from './.vitepress/plugins/markdownTransform'
export default defineConfig(async () => {
return {
server: {
hmr: {
overlay: false,
},
fs: {
allow: [resolve(__dirname, '..')],
},
},
plugins: [
// custom
MarkdownTransform(),
// plugins
Components({
dirs: resolve(__dirname, '.vitepress/theme/components'),
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
resolvers: [
IconsResolver({
componentPrefix: '',
}),
],
dts: './.vitepress/components.d.ts',
transformer: 'vue3',
}),
Icons({
compiler: 'vue3',
autoInstall: true,
defaultStyle: 'display: inline-block',
}),
UnoCSS(),
],
}
})
配置 UnoCSS
本文档几乎 95% 以上的样式都是使用 UnoCSS,在上述配置文件中已经配置好了,如果你有特殊样式配置,可以参考项目根目录下的 unocss.config.ts
,可进行相关修改。
目前 UnoCSS 官网上线了,地址在:
可查阅官方文档使用。
文档显示页面信息和底部贡献者
上述 Vite 配置文件中,我们看到了有一个 MarkdownTransform
函数,这个函数是用来处理 markdown 文件的,主要是为了在每个文档页面的底部显示贡献者信息和版权信息,以及在每个文档页面的顶部显示阅读时间和字数。
核心文件源代码在这里,可以点击查看:
示例代码
以下是示例代码,也许是过去的某个版本的,并非一定是最新的,最新代码请查看上述源代码。
文件路径 docs/.vitepress/plugins/markdownTransform.ts
,如下:
import type { Plugin } from 'vite'
import { replacer } from '../../../scripts/utils'
import { getReadingTime } from './../theme/utils'
export function MarkdownTransform(): Plugin {
return {
name: 'chodocs-md-transform',
enforce: 'pre',
async transform(code, id) {
if (!id.match(/\.md\b/))
return null
// convert links to relative
code = code.replace(/https?:\/\/chodocs\.cn\//g, '/')
const [_name, i] = id.split('/').slice(-2)
// cut index.md
if (_name === 'docs' && i === 'index.md')
return code
const { footer } = await getDocsMarkdown()
code = replacer(code, footer, 'FOOTER', 'tail')
const { readTime, words } = getReadingTime(code)
code = code.replace(
/(#\s.+?\n)/,
`$1\n\n<PageInfo readTime="${readTime}" words="${words}"/>\n`
)
return code
},
}
}
export async function getDocsMarkdown() {
const ContributorsSection = `## Contributors
<Contributors/>`
const CopyRightSection = `
<CopyRight/>`
const footer = `${ContributorsSection}\n${CopyRightSection}\n`
return {
footer,
}
}
这里其实就是一个 Vite 的插件,在 docs/vite.config.ts
中引入了这个插件。
上述代码我高亮了三行代码,你会发现这里其实就是引入了 Vue 组件,为什么没见到导入呢?
因为我们在上述的「配置 Vite」 代码中引入了 unplugin-vue-components/vite
,如下:
import Components from 'unplugin-vue-components/vite'
这个插件的作用就是自动导入 Vue 组件,所以我们在上述代码中直接使用了 Contributors
和 CopyRight
这两个组件,而不需要导入。
Contributors 组件
这个组件是用来显示贡献者信息的,核心文件源代码在这里:
CopyRight 组件
这个组件是用来显示版权信息的,核心文件源代码在这里:
文档页面的顶部信息组件
这个组件用来显示阅读时间和字数等,核心文件源代码在这里:
文章阅读数统计
在上述的文档页面顶部信息组件里面其实已经看到了,如下代码所示:
<div class="flex gap-[4px] items-center">
<ph:eye-fill />
阅读量:<span id="busuanzi_container_page_pv">
<span id="busuanzi_value_page_pv" />
</span>
</div>
我这里使用的是 busuanzi
,不过有时候会出现不稳定情况,即不显示阅读数据,小问题。
我们需要在 docs/.vitepress/theme/index.ts
路径下引入 busuanzi
,如下:
// xxx
import busuanzi from 'busuanzi.pure.js'
const theme: Theme = {
...DefaultTheme,
enhanceApp({ router }: EnhanceAppContext) {
// ...
if (inBrowser) {
router.onAfterRouteChanged = (to) => {
busuanzi.fetch()
}
}
},
// xxx
}
export default theme
这样也是希望能够在路由切换的时候重新获取阅读数据。
文档内图片放大镜 🔍 效果
核心插件使用的是 mediumZoom
,具体引入方式同样也是在 docs/.vitepress/theme/index.ts
下,如下所示:
// xxx
import mediumZoom from 'medium-zoom'
const theme: Theme = {
...DefaultTheme,
enhanceApp({ router }: EnhanceAppContext) {
// ...
},
setup() {
const route = useRoute()
const initZoom = () => {
mediumZoom('.main img', { background: 'var(--vp-c-bg)' })
}
onMounted(() => {
initZoom()
})
watch(
() => route.path,
() => nextTick(() => initZoom())
)
},
}
export default theme
文档支持 pwa
pwa 使用的是 vite-plugin-pwa
,具体引入方式可见这里:
import fg from 'fast-glob'
import { resolve } from 'pathe'
import type { VitePWAOptions } from 'vite-plugin-pwa'
import {
description,
githubSourceContentRegex,
googleFontRegex,
googleStaticFontRegex,
jsdelivrCDNRegex,
name,
} from '../meta'
/**
* Vite Plugin PWA uses Workbox library to build the service worker
* can find more information on Workbox section.
* @see https://vite-plugin-pwa.netlify.app/
*/
export const pwa: Partial<VitePWAOptions> = {
outDir: '../dist',
registerType: 'autoUpdate',
// include all static assets under public/
includeAssets: fg.sync('**/*.{png,svg,gif,ico,txt}', {
cwd: resolve(__dirname, '../../public'),
}),
manifest: {
id: '/',
name,
short_name: name,
description,
theme_color: '#06f',
icons: [
{
src: '/images/icons/apple-touch-120x120.png',
sizes: '120x120',
type: 'image/png',
},
{
src: '/images/icons/android-chrome-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/images/icons/android-chrome-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
},
workbox: {
navigateFallbackDenylist: [/^\/new$/],
globPatterns: ['**/*.{js,css,webp,png,svg,gif,ico,woff2}'],
navigateFallback: null,
runtimeCaching: [
{
urlPattern: googleFontRegex,
handler: 'CacheFirst',
options: {
cacheName: 'google-font-style-cache',
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
{
urlPattern: googleStaticFontRegex,
handler: 'CacheFirst',
options: {
cacheName: 'google-fonts-cache',
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
{
urlPattern: jsdelivrCDNRegex,
handler: 'CacheFirst',
options: {
cacheName: 'jsdelivr-cdn-cache',
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
{
urlPattern: githubSourceContentRegex,
handler: 'CacheFirst',
options: {
cacheName: 'githubusercontent-images-cache',
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
],
},
}
基本上你只需要修改部分文件以及描述信息即可,文档内通用。
文档支持 rss 订阅
这个是前不久做好的功能,核心是使用的 feed
库生成的,具体效果可见 /feed.xml
。
核心代码可见这里:
以下是示例代码:
import path from 'node:path'
import { writeFileSync } from 'node:fs'
import { Feed } from 'feed'
import { type SiteConfig, createContentLoader } from 'vitepress'
import { site as baseUrl, description, name } from '../meta'
function reName(name: string) {
if (!name)
name = 'Choi Yang'
return name === 'Choi Yang' ? 'Chocolate1999' : name
}
function getGithubLink(name: string) {
return `https://github.com/${reName(name)}`
}
export async function genFeed(config: SiteConfig) {
const feed = new Feed({
title: name,
description,
id: baseUrl,
link: baseUrl,
language: 'zh-CN',
image: '/chodocs-logo.svg',
favicon: `${baseUrl}/favicon.ico`,
copyright: 'Copyright (c) 2022-present, Chocolate and ChoDocs contributors',
})
const posts = await createContentLoader('**/*.md', {
excerpt: true,
render: true,
}).load()
posts.sort(
(a, b) =>
+new Date(b.frontmatter?.date as string)
- +new Date(a.frontmatter?.date as string)
)
for (const { url, frontmatter, html } of posts) {
let postTitle = '无题'
postTitle = html?.match(/<h1 id=(.*)>(.*?)<a .*?>/)?.[2] || postTitle
feed.addItem({
title: frontmatter?.title || postTitle,
id: `${baseUrl}${url.slice(1)}`,
link: `${baseUrl}${url.slice(1)}`,
guid: `${baseUrl}${url.slice(1)}`,
description: html,
content: html,
author: [
{
name: frontmatter?.author || 'Choi Yang',
link: frontmatter?.author
? getGithubLink(frontmatter?.author)
: undefined,
},
],
date: frontmatter?.date || new Date('2021-07-01'),
})
}
writeFileSync(path.join(config.outDir, 'feed.xml'), feed.rss2())
}
核心思路是读取文件,根据文件内容生成 feed
,然后写入到 feed.xml
文件中,关键是相关字段一定要完整,缺一不可,不然生成的 xml 文件能显示但是无法订阅。
谷歌分析和百度统计
这个主要是观察网站数据使用,当然 ChoDocs 之后还会集成 umami,以下提供一下对应的文件路径:
- 谷歌分析:
docs/.vitepress/theme/plugins/googleAnalytics.ts
- 百度统计:
docs/.vitepress/theme/plugins/baidutongji.ts
结语
目前文档还在不断完善,主要还是提供一点思路,如果你在配置过程中遇到了问题,还请提相关 issue,如果解决了也欢迎来提交 pr,参与这个项目在首页会有贡献者头像展示。