Skip to content

开发思路

摘要

只要会编写 Vue组件,那么就可以发挥您天马行空的想象力,来构建自己的主题。

2025-03-17 @Teek

本系列为 主题开发,主要介绍 Teek 的开发思路,当然这只是提供思路,不会细到每一个文件的逻辑讲解。

在阅读完本系列内容后,您可以去阅读 Teek 的源码,了解 Teek 的实现思路,也许对您的主题开发之路有些帮助。

基于 VitePress 开发一个主题是非常简单的,在 自定义主题拓展默认主题已经详细的介绍了如何开发一个主题。

Layout 函数

VitePress 默认内置了一套主题,如果觉得内置主题的功能不满足需求或者想额外添加一些功能,可以编写组件来替换/拓展 VitePress 主题。

首先 VitePress 必须需要接收一个 Layout函数,该函数需要返回一个 vue组件作为 入口组件

ts
importDefaultTheme from"vitepress/theme";exportdefault{extends:DefaultTheme,Layout:,enhanceApp({app,router,siteData}) {},};

Layout实现一个组件主要有 2 种方式:

  1. h函数 + .vue组件
ts
importDefaultTheme from"vitepress/theme";importMyComponent from"./MyComponent.vue";import{h } from"vue";exportdefault{extends:DefaultTheme,Layout:() =>h(MyComponent),enhanceApp({app,router,siteData}) {},};
  1. defineComponent函数生成 vue组件
ts
importDefaultTheme from"vitepress/theme";importMyComponent from"./MyComponent.vue";import{h } from"vue";exportdefault{extends:DefaultTheme,Layout:defineComponent({name:"ConfigProvider",setup(_,{slots}) {return() =>h(MyComponent,null,slots);},}),enhanceApp({app,router,siteData}) {},};

可以看到,defineComponent函数的返回值还是使用了 h函数 + .vue组件,但是这样好处在于可以添加一些全局逻辑或往所有组件里注入常用数据,因为这是在所有组件加载前执行的逻辑。

比如 Teek 注入了文章信息数据、并开启一些监听器:

ts
importDefaultTheme from"vitepress/theme";importMyComponent from"./MyComponent.vue";import{h,typeComponent } from"vue";constconfigProvider=(Layout:Component) =>{returndefineComponent({name:"ConfigProvider",setup(_,{slots}) {const{theme} =useData();provide(postsContext,theme.value.posts);usePermalink().startWatch();useAnchorScroll().startWatch();useViewTransition();return() =>h(Layout,null,slots);},});};exportdefault{extends:DefaultTheme,Layout:configProvider(MyComponent),enhanceApp({app,router,siteData}) {},};

相比较直接用 h函数来构建组件,defineComponent函数更灵活,多了一个中间层方便实现一些逻辑。

入口组件

阅读内容前,您需要了解 VitePress 提供了哪些插槽,请看 布局插槽

Teek 并不是完全脱离 VitePress 的主题,而是基于 VitePress 的主题开发,所以 Teek 在入口组件里继承 VitePress 的 Layout组件,并通过 VitePress 提供的插槽来实现功能。

Teek 的入口文件伴随着迭代,已经有很多内容产出,这里给出 Teek 早期的模板:

vue
<scriptsetuplang="ts"name="TeekLayout">importDefaultTheme from"vitepress/theme";import{useData } from"vitepress";const{theme} =useData();const{teekTheme=true} =theme.value;constusedSlots=["home-hero-before","nav-bar-content-after",];</script><template><!-- 使用 Teek 主题 --><templatev-if="teekTheme"><DefaultTheme.Layout><template#home-hero-before><slotname="home-hero-before"/><!-- 自定义首页 --></template><!-- 在 VP 的不同插槽自定义不同内容 --><template#xx></template><!-- 未使用的其他 VP 插槽 --><templatev-for="name inObject.keys($slots).filter(name=>!usedSlots.includes(name))":key="name"#[name]="slotData"><slot:name="name"v-bind="slotData"></slot></template></DefaultTheme.Layout></template><templatev-else><DefaultTheme.Layout><templatev-for="(_,name) in$slots":key="name"#[name]="slotData"><slot:name="name"v-bind="slotData"></slot></template></DefaultTheme.Layout></template></template>

Teek 从 theme中取出一个配置项 teekTheme,如果为 true,则使用 Teek 主题,否则使用 VitePress 的默认主题。

提示

theme为项目里 .vitepress/config.mts里的 themeConfig内容。

如果您完全不需要基于 VitePress 的主题开发,则不需要使用 DefaultTheme.Layout组件,直接在该组件写入自己的内容即可,这也意味着您只是基于 Vite 环境构建您的专属风格,您将自己实现首页、侧边栏、导航栏、CSS 样式等,只有 Markdown 解析的内容不需要自己实现,VitePress 已经提供了全局组件 <Content />来渲染 Markdown 内容。

提示

如果想完全脱离 VitePress 主题,在 Layout函数处不要添加 extends:DefaultTheme

在模板里可以看到这样两段代码:

vue
<templatev-for="name inObject.keys($slots).filter(name=>!usedSlot.includes(name))":key="name"#[name]="slotData"><slot:name="name"v-bind="slotData"></slot></template><templatev-for="(_,name) in$slots":key="name"#[name]="slotData"><slot:name="name"v-bind="slotData"></slot></template>
  • 第一段代码:Teek 不仅自己使用 VitePress 的插槽,同时也允许用户使用 VitePress 的插槽,所以 Teek 先维护了已使用的插槽列表,然后通过了 v-for遍历所有 未使用VitePress 的插槽,并使用 #[name]="slotData"将插槽内容传递给 VitePress
  • 第二段代码:当不使用 Teek 主题时,则加载 VitePress 的默认主题,并使用 v-for遍历所有插槽,将插槽内容传递给 VitePress。

如果不通过 for循环,那么就需要这样写:

vue
<template><Layout><!-- layout --><template#layout-top><slotname="layout-top"/></template><template#layout-bottom><slotname="layout-bottom"/></template><!-- navbar --><template#nav-bar-title-before><slotname="nav-bar-title-before"/></template><template#nav-bar-title-after><slotname="nav-bar-title-after"/></template><template#nav-bar-content-before><slotname="nav-bar-content-before"/></template><template#nav-bar-content-after><slotname="nav-bar-content-after"/></template><template#nav-screen-content-before><slotname="nav-screen-content-before"/></template><template#nav-screen-content-after><slotname="nav-screen-content-after"/></template><!-- sidebar --><template#sidebar-nav-before><slotname="sidebar-nav-before"/></template><template#sidebar-nav-after><slotname="sidebar-nav-after"/></template><!-- page --><template#page-top><slotname="page-top"/></template><template#page-bottom><slotname="page-bottom"/></template><!-- 404 --><template#not-found><slotname="not-found"/></template><!-- home --><template#home-hero-info><slotname="home-hero-info"/></template><template#home-hero-image><slotname="home-hero-image"/></template><template#home-hero-before><slotname="home-hero-before"/></template><template#home-hero-after><slotname="home-hero-after"/></template><template#home-features-before><slotname="home-features-before"/></template><template#home-features-after><slotname="home-features-after"/></template><!-- doc --><template#doc-footer-before><slotname="doc-footer-before"/></template><template#doc-before><slotname="doc-before"/></template><template#doc-after><slotname="doc-after"/></template><template#doc-top><slotname="doc-top"/></template><template#doc-bottom><slotname="doc-bottom"/></template><!-- aside --><template#aside-top><slotname="aside-top"/></template><template#aside-bottom><slotname="aside-bottom"/></template><template#aside-outline-before><slotname="aside-outline-before"/></template><template#aside-outline-after><slotname="aside-outline-after"/></template><template#aside-ads-before><slotname="aside-ads-before"/></template><template#aside-ads-after><slotname="aside-ads-after"/></template></Layout></template>

可以看到 VitePress 提供的插槽非常多,这样写起来非常麻烦,因此建议使用 for循环方式。

接下来就可以根据自己的需求来写组件,然后传入 VitePress 的插槽中,Teek 也是在模板里不断补充内容才达到现在的效果。

假设您已经自定义了首页和评论区组件,则需要传入 VitePress 的插槽中,内容如下:

vue
<scriptsetuplang="ts"name="TeekLayout">importDefaultTheme from"vitepress/theme";import{useData } from"vitepress";importHomePost from"./components/HomePost.vue";importComment from"./components/Comment.vue";const{Layout} =DefaultTheme;const{theme} =useData();const{teekTheme=true} =theme.value;constusedSlots=["home-hero-before","nav-bar-content-after",];</script><template><!-- 使用 Teek 主题 --><templatev-if="teekTheme"><Layout><template#home-hero-before><slotname="home-hero-before"/><!-- 自定义首页 --><HomePost/></template><template#doc-after><slotname="doc-after"/><!-- 自定义评论区 --><Comment/></template><!-- 未使用的其他 VP 插槽 --><templatev-for="name inObject.keys($slots).filter(name=>!usedSlots.includes(name))":key="name"#[name]="slotData"><slot:name="name"v-bind="slotData"></slot></template></Layout></template><templatev-else><Layout><templatev-for="(_,name) in$slots":key="name"#[name]="slotData"><slot:name="name"v-bind="slotData"></slot></template></Layout></template></template>

如果您不了解每个插槽分别作用于什么位置,可以在每个插槽里加一段文字如 <div>${插槽名}</div>,然后在页面查看输出的内容。

引入主题

假设您已经开发好了一个主题,则需要在项目的 .vitepress/theme/index.ts文件中引入,如果没有请按照该路径依次创建。

ts
importTeek from"vitepress-theme-teek";exportdefault{extends:Teek,};

此时项目成功使用你的自定义主题。

最近更新