Skip to content

vitepress的配置合并。

一、概述

当建立一个vitepress站点后,得到的默认配置文件如下:

typescript
import { defineConfig } from 'vitepress'

// https://vitepress.dev/reference/site-config
export default defineConfig({
  title: "mist docs",
  description: "vitepress-theme-mist docs",
  themeConfig: {
    // https://vitepress.dev/reference/default-theme-config
    nav: [
      { text: 'Home', link: '/' },
      { text: 'Examples', link: '/markdown-examples' }
    ],

    sidebar: [
      {
        text: 'Examples',
        items: [
          { text: 'Markdown Examples', link: '/markdown-examples' },
          { text: 'Runtime API Examples', link: '/api-examples' }
        ]
      }
    ],

    socialLinks: [
      { icon: 'github', link: 'https://github.com/vuejs/vitepress' }
    ]
  }
})

而在主题开发中,往往需要提供一些配置来丰富主题的功能,最简单的是和 VitePress 的 themeConfig 配置合在一起:

typescript
// .vitepress/config.mts
import { defineConfig } from "vitepress";

export default defineConfig({
  // ...
  themeConfig: {
    // vitepress 配置
    // 自定义主题配置
  },
});

然后在组件里通过 useData 获取 themeConfig 的内容:

vue
<script setup lang="ts">
import { useData } from "vitepress";

// 获取 themeConfig
const { theme } = useData;

// 获取自定义主题配置项
const xx = theme.value.xxx;
</script>

<template></template>

当主题需要添加一个 head 或者 vite 插件,需要让用户修改 VitePress 的配置,这样极其不方便。因此可以先将主题配置抽离出来,然后使用 extends 来合并主题配置。

二、extends 合并配置

VitePress 提供了 extends 来合并外界传来的配置项,比如外界的配置提供了部分 head 内容,并且在 VitePress 也配置了 head,则最终合并为一个全新的 head,而不是覆盖。

1. extends

Tips:VitePress 的配置项只有为对象/数组时可以合并,如果配置项为一个固定的值或者函数,则以 VitePress 的配置项为主。

extends 合并主题配置的使用方式如下:

typescript
import { defineConfig } from "vitepress";

// 主题配置
const MistConfig = {};

// vitepress 配置
export default defineConfig({
  extends: MistConfig,
  // ...
  themeConfig: {
    // ...
  },
});

在 VitePress 配置中通过 extends 可以将主题配置合并到 VitePress 配置里,也就是说完全可以在主题配置里添加 VitePress 的配置项,但是不能反过来。

2. 配置合并示例

2.1 分别配置

typescript
// .vitepress/config.mts
import { defineConfig } from "vitepress";

// 主题配置
const myThemeConfig = { themeConfig: { MistTheme: true } };

// VitePress 配置
export default defineConfig({
  extends: myThemeConfig,
  base: "/",
});

2.2 统一配置

typescript
// .vitepress/config.mts
import { defineConfig } from "vitepress";

// 主题配置 + VitePress 配置
const myThemeConfig = { base: "/", themeConfig: { MistTheme: true } };

export default defineConfig({
  extends: myThemeConfig,
});

3. 实例分析

3.1 Dataloader组件

为了方便的看到最终配置项,这里编写一个获取数据的组件,我们在.vitepress/theme/Datalog.vue中添加以下内容:

vue
<template>
  <div class="data-loader">
    <p>页面数据:</p>
    <pre>{{ JSON.stringify(pageData, null, 2) }}</pre>
    
    <p>站点数据:</p>
    <pre>{{ JSON.stringify(siteData, null, 2) }}</pre>
  </div>
</template>

<script setup>
import { useData } from 'vitepress'

const { site, page } = useData()
const siteData = site.value
const pageData = page.value
</script>

<style scoped>
.data-loader {
  border: 1px solid #eee;
  padding: 1rem;
  margin: 1rem 0;
  border-radius: 4px;
  font-size: 0.8rem;
}
pre {
  background: #e6e6e6;
  padding: 0.5rem;
  border-radius: 4px;
  overflow-x: auto;
  font-size: 0.8rem;
}
</style>

然后在.vitepress/theme/index.ts中注册这个组件:

typescript
// https://vitepress.dev/guide/custom-theme
import { h } from 'vue'
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import './style.css'
import Dataloader from './Dataloader.vue'
export default {
  extends: DefaultTheme,
  Layout: () => {
    return h(DefaultTheme.Layout, null, {
      // https://vitepress.dev/guide/extending-default-theme#layout-slots
    })
  },
  enhanceApp({ app, router, siteData }) {
	app.component('Dataloader', Dataloader);
    // ...
  }
} satisfies Theme

这个时候就可以通过<Dataloader />在md文档中获取页面数据和站点数据了,新建的站点默认的配置项数据如下:

json
{
  "lang": "en-US",
  "dir": "ltr",
  "title": "My Awesome Project",
  "description": "A VitePress Site",
  "base": "/",
  "head": [
    [
      "script",
      {
        "id": "check-dark-mode"
      },
      ";(() => {\n               const preference = localStorage.getItem('vitepress-theme-appearance') || 'auto'\n               const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches\n               if (!preference || preference === 'auto' ? prefersDark : preference === 'dark')\n                 document.documentElement.classList.add('dark')\n             })()"
    ],
    [
      "script",
      {
        "id": "check-mac-os"
      },
      "document.documentElement.classList.toggle('mac', /Mac|iPhone|iPod|iPad/i.test(navigator.platform))"
    ]
  ],
  "router": {
    "prefetchLinks": true
  },
  "appearance": true,
  "themeConfig": {
    "nav": [
      {
        "text": "Home",
        "link": "/"
      },
      {
        "text": "Examples",
        "link": "/markdown-examples"
      }
    ],
    "sidebar": [
      {
        "text": "Examples",
        "items": [
          {
            "text": "Markdown Examples",
            "link": "/markdown-examples"
          },
          {
            "text": "Runtime API Examples",
            "link": "/api-examples"
          }
        ]
      }
    ],
    "socialLinks": [
      {
        "icon": "github",
        "link": "https://github.com/vuejs/vitepress"
      }
    ]
  },
  "locales": {},
  "scrollOffset": 134,
  "cleanUrls": false,
  "localeIndex": "root"
}

3.2 分别配置实例

我们修改.vitepress/config.mts

typescript
import { defineConfig } from 'vitepress'

const myThemeConfig = {
  themeConfig: {
    footer: {
      message: '前路漫漫亦灿灿',
      copyright: 'Copyright © 2019-present 苏木'
    },
  }
};

// https://vitepress.dev/reference/site-config
export default defineConfig({
  extends: myThemeConfig,
  title: "My Awesome Project",
  description: "A VitePress Site",
})

为避免数据过长,这里删除导航栏和侧边栏,直接从主页进入md文档页面查看数据,这里添加了footer配置项,可以注意观察一下页脚的位置,然后我们会得到以下配置项:

json
{
  "lang": "en-US",
  "dir": "ltr",
  "title": "My Awesome Project",
  "description": "A VitePress Site",
  "base": "/",
  //"head": [ ... ] 这里省略head,默认的数据太长了
  "router": {
    "prefetchLinks": true
  },
  "appearance": true,
  "themeConfig": {
    "footer": {
      "message": "前路漫漫亦灿灿",
      "copyright": "Copyright © 2019-present 苏木"
    }
  },
  "locales": {},
  "scrollOffset": 134,
  "cleanUrls": false,
  "localeIndex": "root"
}

3.3 统一配置实例

我们修改.vitepress/config.mts

typescript
import { defineConfig } from 'vitepress'

const myThemeConfig = {
  title: "My Awesome Project",
  description: "A VitePress Site",
  base: '/',
  themeConfig: {
    footer: {
      message: '前路漫漫亦灿灿',
      copyright: 'Copyright © 2019-present 苏木'
    },
  }
};

// https://vitepress.dev/reference/site-config
export default defineConfig({
  extends: myThemeConfig,
})

这里又在站点配置中添加了base的配置项,还是一样直接从主页进入md文档页面查看数据,这里添加了footer配置项,可以注意观察一下页脚的位置,然后我们会得到以下配置项:

json
{
  "lang": "en-US",
  "dir": "ltr",
  "title": "My Awesome Project",
  "description": "A VitePress Site",
  "base": "/",
  //"head": [...],
  "router": {
    "prefetchLinks": true
  },
  "appearance": true,
  "themeConfig": {
    "footer": {
      "message": "前路漫漫亦灿灿",
      "copyright": "Copyright © 2019-present 苏木"
    }
  },
  "locales": {},
  "scrollOffset": 134,
  "cleanUrls": false,
  "localeIndex": "root"
}

3.4 两个地方都配置

我们修改.vitepress/config.mts

typescript
import { defineConfig } from 'vitepress'

const myThemeConfig = {
  title: "myThemeConfig title",
  description: "myThemeConfig desc",
  themeConfig: {
    footer: {
      message: 'myThemeConfig footer',
      copyright: 'Copyright © 2019-present 苏木'
    },
  }
};

// https://vitepress.dev/reference/site-config
export default defineConfig({
  extends: myThemeConfig,
  title: "My Awesome Project",
  description: "A VitePress Site",
  themeConfig: {
    footer: {
      message: '莫道桑榆晚 为霞尚满天',
    },
  }
})

然后会得到以下数据:

json
{
  "lang": "en-US",
  "dir": "ltr",
  "title": "My Awesome Project",
  "description": "A VitePress Site",
  "base": "/",
  //"head": [...],
  "router": {
    "prefetchLinks": true
  },
  "appearance": true,
  "themeConfig": {
    "footer": {
      "message": "莫道桑榆晚 为霞尚满天",
      "copyright": "Copyright © 2019-present 苏木"
    }
  },
  "locales": {},
  "scrollOffset": 134,
  "cleanUrls": false,
  "localeIndex": "root"
}

然后就会发现,两个配置合并了,而且defineConfig中的配置优先级更高。

三、函数式构建配置

1. getThemeConfig()

在主题配置里,如果要使用 Vite 插件或者想要修改 VitePress 默认的配置,则可以提供一个函数来返回主题配置:

typescript
// .vitepress/myThemeConfig.ts
import type { DefaultTheme, UserConfig } from "vitepress";

interface MistThemeConfig {
  useTheme?: boolean; // 是否开启主题
  themeName?: string;
  // ...
}

export default function getThemeConfig(config: MistThemeConfig & UserConfig<DefaultTheme.Config> = {}): UserConfig {
  // 获取用户的配置,进行逻辑处理
  const {...mistThemeConfig } = config;

  if (!mistThemeConfig.useTheme) return {};

  return {
    // ignoreDeadLinks 默认值修改为 true,当用户在 VitePress 手动改为 false 才为 false
    ignoreDeadLinks: true,
    // 添加主题需要的 head 信息
    head: [],
    vite: {
      // 添加主题需要的 Vite 插件
      plugins: [],
    },
    themeConfig: mistThemeConfig,
  };
}

1.1 函数分析

我们来分析一下这个函数,

  • 传入参数:config: MistThemeConfig & UserConfig<DefaultTheme.Config> = {}

(1)MistThemeConfig & UserConfig<DefaultTheme.Config>:使用 TypeScript 的交叉类型(Intersection Types)。

(2)& 符号表示将两个类型合并成一个类型,参数必须同时满足两个类型的所有属性。

(3)MistThemeConfig是自定义的主题配置接口,包含 useTheme、themeName 等主题特定属性。

(4)UserConfig<DefaultTheme.Config>:VitePress 的用户配置类型,泛型参数指定使用默认主题配置。

(5)后面的= {}:参数默认值,当调用函数时不传参数时使用空对象作为默认值。

  • 返回值为 UserConfig类型或者为空

(1): UserConfig:表示函数的返回类型注解(Return Type Annotation),UserConfig:来自 Vitepress 的类型,定义了 VitePress 配置对象的结构。这个注解确保函数返回的对象必须符合 VitePress 的配置规范。

(2)当 useTheme 为 false 时,返回空对象 {}(也是有效的 UserConfig 类型)。当 useTheme 为 true 时,返回包含 ignoreDeadLinksheadvitethemeConfig 属性的配置对象。

2. 类型问题?

这里我有一个疑问,mistThemeConfig 的类型是 MistThemeConfig & UserConfig<DefaultTheme.Config>,为什么可以赋值给 themeConfig 属性?

2.1 TypeScript 类型兼容性基础

2.1.1 结构化类型系统(Structural Typing)

TypeScript 使用结构化类型系统,也称为"鸭子类型"(Duck Typing)。这意味着:

"如果一个对象看起来像鸭子,叫声像鸭子,那么它就是鸭子。"

在 TypeScript 中,类型的兼容性不依赖于类型的名称,而依赖于类型的结构

typescript
// 示例 1:基本的结构化类型
interface Point {
  x: number;
  y: number;
}

interface Point2D {
  x: number;
  y: number;
}

let point: Point = { x: 1, y: 2 };
let point2D: Point2D = { x: 3, y: 4 };

// 兼容,因为结构相同
point = point2D; // ✅ 正确
point2D = point; // ✅ 正确
2.1.2 类型兼容性规则

TypeScript 的类型兼容性遵循以下基本规则:

(1)源类型必须至少包含目标类型的所有属性

(2)源类型的属性类型必须与目标类型兼容

(3)源类型可以包含额外的属性

typescript
// 示例 2:类型兼容性规则
interface Animal {
  name: string;
  age: number;
}

interface Dog {
  name: string;
  age: number;
  breed: string; // 额外属性
}

let animal: Animal = { name: "动物", age: 5 };
let dog: Dog = { name: "旺财", age: 3, breed: "金毛" };

// ✅ Dog 包含 Animal 的所有属性,所以可以赋值
animal = dog; 

// ❌ Animal 缺少 Dog 的 breed 属性,所以不能赋值
// dog = animal; // 错误
2.1.3 交叉类型(Intersection Types)

交叉类型使用 & 符号,将多个类型合并为一个类型。新类型将包含所有类型的所有属性。

typescript
// 示例 3:交叉类型
interface A {
  a: string;
}

interface B {
  b: number;
}

type C = A & B; // C 必须同时包含 a 和 b 属性

let c: C = { a: "hello", b: 42 };

// 交叉类型的应用
interface Configurable {
  debug?: boolean;
}

interface Loggable {
  logLevel: 'info' | 'warn' | 'error';
}

type AppConfig = Configurable & Loggable;

let appConfig: AppConfig = {
  debug: true,
  logLevel: 'info'
};

2.2 VitePress 配置中的类型兼容性

2.2.1 VitePress 类型定义的深层分析

让我们深入分析 VitePress 配置中的类型定义和继承关系,UserConfig 接口的继承层次如下

typescript
// VitePress 的实际类型定义(来自源码)
interface LocaleSpecificConfig<ThemeConfig = any> {
  lang?: string;
  title?: string;
  description?: string;
  themeConfig?: ThemeConfig;  // 关键:themeConfig 的类型是泛型参数 ThemeConfig
  // ... 其他属性
}

interface UserConfig<ThemeConfig = any> extends LocaleSpecificConfig<ThemeConfig> {
  extends?: RawConfigExports<ThemeConfig>;
  base?: string;
  srcDir?: string;
  // ... 其他 VitePress 配置属性
}

LocaleSpecificConfig<ThemeConfig>:是基础配置接口,定义了 themeConfig?: ThemeConfig 属性,泛型参数 ThemeConfig 默认为 any

UserConfig<ThemeConfig> extends LocaleSpecificConfig<ThemeConfig>:继承自 LocaleSpecificConfig,通过继承获得了 themeConfig?: ThemeConfig 属性,泛型参数 ThemeConfig 默认为 any,添加了更多 VitePress 特定的配置属性

继承的实际影响:

typescript
// 当我们使用 UserConfig<DefaultTheme.Config> 时:
interface UserConfig<DefaultTheme.Config> extends LocaleSpecificConfig<DefaultTheme.Config> {
  // 继承了 themeConfig?: DefaultTheme.Config
  // themeConfig 的类型是 DefaultTheme.Config
}

// 当我们使用 UserConfig(无泛型参数)时:
interface UserConfig extends LocaleSpecificConfig<any> {
  // 继承了 themeConfig?: any
  // themeConfig 的类型是 any
}

2.3 themeConfig 的实际类型

通过分析函数,可以发现类型兼容性的真正原因:

typescript
// 在我们的代码中
export default function getThemeConfig(
  config: MistThemeConfig & UserConfig<DefaultTheme.Config> = {}
): UserConfig {  // ⚠️ 注意:返回类型没有指定泛型参数
  // ...
  return {
    ignoreDeadLinks: true,
    head: [],
    vite: { plugins: [] },
    themeConfig: mistThemeConfig, // ✅ 这里可以赋值的原因
  };
}
  • 参数类型:UserConfig<DefaultTheme.Config>,这明确指定了 ThemeConfig 泛型参数为 DefaultTheme.Config,这意味着在参数中,themeConfig 期望 DefaultTheme.Config 类型的值

  • 返回类型:UserConfig(没有泛型参数),使用默认泛型参数 ThemeConfig = any,这意味着在返回值中,themeConfig 的类型是 any

类型兼容性的真正原因

typescript
// mistThemeConfig 的类型:MistThemeConfig & UserConfig<DefaultTheme.Config>
// themeConfig 的类型:any  (因为返回类型 UserConfig 使用默认泛型参数)

// ✅ 任何类型都可以赋值给 any 类型!
themeConfig: mistThemeConfig  // 这是合法的,因为目标类型是 any

2.4 交叉类型的作用

虽然交叉类型不是类型兼容性的主要原因,但它在这个场景中仍然起到了重要作用:

typescript
// MistThemeConfig & UserConfig<DefaultTheme.Config> 的创建
type CombinedConfig = MistThemeConfig & UserConfig<DefaultTheme.Config>;

// 实际的 CombinedConfig 对象结构
const combinedConfig: CombinedConfig = {
  // 来自 MistThemeConfig
  useTheme: true,
  themeName: "mist",
  
  // 来自 UserConfig<DefaultTheme.Config>
  title: "我的文档",
  description: "文档描述",
  themeConfig: {
    // 这里是 DefaultTheme.Config 的内容
    nav: [...],
    sidebar: {...},
    footer: { message: "页脚信息" }
  }
};

交叉类型确保了 mistThemeConfig 包含了所有必要的属性,但不是类型兼容性的决定性因素

2.5 验证:如果明确指定返回类型

让我们验证我们的理解,修改函数签名明确指定返回类型的泛型参数:

typescript
// 修改后的函数签名
export default function getThemeConfig(
  config: MistThemeConfig & UserConfig<DefaultTheme.Config> = {}
): UserConfig<DefaultTheme.Config> {  // 明确指定泛型参数
  // ...
  return {
    ignoreDeadLinks: true,
    head: [],
    vite: { plugins: [] },
    themeConfig: mistThemeConfig, // ❌ 这将导致类型错误!
  };
}

这个时候语法检测就会报错:

bash
[{
	"resource": "/D:/sumu_blog/config-test/src/.vitepress/myThemeConfig.ts",
	"owner": "typescript",
	"code": "2322",
	"severity": 8,
	"message": "不能将类型“{ useTheme?: boolean | undefined; themeName?: string | undefined; extends?: RawConfigExports<Config> | undefined; base?: string | undefined; srcDir?: string | undefined; ... 33 more ...; themeConfig?: Config | undefined; }”分配给类型“Config”。\n  属性“lastUpdated”的类型不兼容。\n    不能将类型“boolean | undefined”分配给类型“LastUpdatedOptions | undefined”。\n      类型“false”与类型“LastUpdatedOptions”不具有相同的属性。",
	"source": "ts",
	"startLineNumber": 74,
	"startColumn": 5,
	"endLineNumber": 74,
	"endColumn": 16,
	"relatedInformation": [
		{
			"startLineNumber": 161,
			"startColumn": 3,
			"endLineNumber": 161,
			"endColumn": 14,
			"message": "所需类型来自属性 \"themeConfig\",在此处的 \"UserConfig<Config>\" 类型上声明该属性",
			"resource": "/d:/sumu_blog/config-test/node_modules/vitepress/types/shared.d.ts"
		}
	]
}]

TypeScript 错误分析:

  • themeConfig 期望 DefaultTheme.Config 类型
  • mistThemeConfig 的类型是 MistThemeConfig & UserConfig<DefaultTheme.Config>
  • 两者类型不匹配,因为 mistThemeConfig 包含额外的 useThemethemeName 属性

2.6 完整的类型兼容性解释

现在我们可以给出完整的类型兼容性解释:

typescript
// mistThemeConfig 的类型:MistThemeConfig & UserConfig<DefaultTheme.Config>
// themeConfig 的类型:any (因为返回类型 UserConfig 使用默认泛型参数)

// 类型兼容性的完整链条:
// 1. UserConfig 继承自 LocaleSpecificConfig<ThemeConfig>
// 2. LocaleSpecificConfig 定义了 themeConfig?: ThemeConfig
// 3. 当 UserConfig 没有指定泛型参数时,ThemeConfig = any
// 4. 因此 themeConfig 的类型为 any
// 5. 任何类型都可以赋值给 any 类型
// 6. 所以 mistThemeConfig 可以赋值给 themeConfig

重要结论:

这个类型兼容性并不是因为 TypeScript 的结构化类型系统或交叉类型的复杂特性,而是因为返回类型使用了默认的 any 泛型参数。这是一种"类型逃逸"机制,绕过了严格的类型检查。实际示例演示

3. vitepress使用示例

3.1 myThemeConfig.ts

typescript
// .vitepress/myThemeConfig.ts
import type { DefaultTheme, UserConfig } from "vitepress";

interface MistThemeConfig {
  useTheme?: boolean; // 是否开启主题
  themeName?: string;
  // ...
}

export default function getThemeConfig(config: MistThemeConfig & UserConfig<DefaultTheme.Config> = {}): UserConfig {
  // 获取用户的配置,进行逻辑处理
  const {...mistThemeConfig } = config;

  if (!mistThemeConfig.useTheme) return {};

  return {
    // ignoreDeadLinks 默认值修改为 true,当用户在 VitePress 手动改为 false 才为 false
    ignoreDeadLinks: true,
    // 添加主题需要的 head 信息
    head: [],
    vite: {
      // 添加主题需要的 Vite 插件
      plugins: [],
    },
    themeConfig: mistThemeConfig,
  };
}

3.2 config.mts

.vitepress/config.mts 引入该函数:

typescript
import { defineConfig } from 'vitepress'

import getThemeConfig from "./myThemeConfig";

const myThemeConfig = getThemeConfig({ 
  useTheme: true,
  themeName: "mist",
  themeConfig: {
    footer: {
      message: '前路漫漫亦灿灿',
    },
  }
});

// https://vitepress.dev/reference/site-config
export default defineConfig({
  extends: myThemeConfig,
  title: "My Awesome Project",
  description: "A VitePress Site",
  themeConfig: {
    footer: {
      message: '莫道桑榆晚 为霞尚满天',
    },
  }
})

3.3 配置分析

按照上面的配置,这个时候,我们会得到下面的数据:

json
{
  "lang": "en-US",
  "dir": "ltr",
  "title": "My Awesome Project",
  "description": "A VitePress Site",
  "base": "/",
  //"head": [...]
  "router": {
    "prefetchLinks": true
  },
  "appearance": true,
  "themeConfig": {
    "useTheme": true,
    "themeName": "mist",
    "themeConfig": {
      "footer": {
        "message": "前路漫漫亦灿灿"
      }
    },
    "footer": {
      "message": "莫道桑榆晚 为霞尚满天"
    }
  },
  "locales": {},
  "scrollOffset": 134,
  "cleanUrls": false,
  "localeIndex": "root"
}

可以看到,下面的内容都在最终的themeConfig中:

typescript
  useTheme: true,
  themeName: "mist",
  themeConfig: {
    footer: {
      message: '前路漫漫亦灿灿',
    },
  }

包括在.vitepress/config.mts 中对themeConfig的配置,它其实并没有合并到最终的themeConfig中,而是成为了themeConfig的themeConfig配置项:

json
{
  "themeConfig": {
    "useTheme": true,
    "themeName": "mist",
    "themeConfig": {
      "footer": {
        "message": "前路漫漫亦灿灿"
      }
    },
    "footer": {
      "message": "莫道桑榆晚 为霞尚满天"
    }
  },
}

四、开发实例

1. 源码

docs-site/vitepress-theme-mist at 3e5d81e9b7dff0c94a46f993bd8fe8355c2c4a25

这里其实是有bug的,不过后来已经修复了,具体原理可以看第三部分。

2. 工作区外示例

首先安装vitepress-theme-mist

bash
npm i -D ..\vitepress-theme-mist\dist\vitepress-theme-mist\
npm i vitepress-theme-mist

然后在.vitepress/config.mts中添加以下内容:

typescript
import { defineMistConfig } from "vitepress-theme-mist/config"; 

const myThemeConfig = defineMistConfig({ 
    useTheme: true,
    themeConfig: {
      logo: '/favicon.svg', // 导航栏标题的logo
      footer: {
        message: '前路漫漫亦灿灿',
        copyright: 'Copyright © 2019-present 苏木'
      }
    }
  }
);

// https://vitepress.dev/reference/site-config
export default defineConfig({
  extends: myThemeConfig,
  //......
}

3. 工作区内示例

3.1 基本用法

然后就是工作区内的vitepress站点怎么使用?我们在.vitepress/config.mts中添加:

bash
import { defineMistConfig } from "../../../packages/config";

然后就和上面一样:

typescript
import { defineMistConfig } from "vitepress-theme-mist/config"; 

const myThemeConfig = defineMistConfig({ 
    useTheme: true,
    themeConfig: {
      logo: '/favicon.svg', // 导航栏标题的logo
      footer: {
        message: '前路漫漫亦灿灿',
        copyright: 'Copyright © 2019-present 苏木'
      }
    }
  }
);

// https://vitepress.dev/reference/site-config
export default defineConfig({
  extends: myThemeConfig,
  //......
})

3.2 一个问题

3.2.1 现象

上面要写三级的相对路径,但是想一下,我们前面不是在tsconfig.base.json映射过了:

json
{
  //......
  "paths": {                            // 路径映射,用于模块解析的别名配置
    "@mist/*": ["packages/*"]           // 将 @mist/* 映射到 packages/* 目录
  }
}

直接用这个@mist不就可以了,当我们尝试使用包导入的方式:

typescript
import { defineMistConfig } from "@mist/config";

会出现以下错误:

bash
failed to load config from D:\sumu_blog\vitepress-theme-mist\docs\src\.vitepress\config.mts
failed to start server. error:
Unknown file extension ".ts" for D:\sumu_blog\vitepress-theme-mist\packages\config\index.ts
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for D:\sumu_blog\vitepress-theme-mist\packages\config\index.ts
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:219:9)
    at defaultGetFormat (node:internal/modules/esm/get_format:245:36)
    at defaultLoad (node:internal/modules/esm/load:120:22)
    at async ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:580:32)
3.2.2 原因分析

这个问题的根本原因是 Node.js 无法直接执行 TypeScript 文件:

(1)相对路径导入能正常工作的原因

使用 import { defineMistConfig } from "../../../packages/config" 时,VitePress 开发服务器使用 Vite 的构建系统处理这些导入,Vite 通过 esbuild 即时编译 TypeScript 文件,所以可以正常工作。

(2)包导入失败的原因

使用 import { defineMistConfig } from "@mist/config" 时,Node.js 通过模块解析机制查找包,并查看packages/config/package.jsonmain 字段,当前 main 字段指向 index.ts,而 Node.js 无法直接执行 TypeScript 文件,因此抛出 "Unknown file extension" 错误。

3.2.3 解决方案

要解决这个问题,我们需要为 @mist/config 包提供编译后的 JavaScript 文件:

(1)修改 packages/config/package.json,添加构建配置和正确的入口点

(2)添加 TypeScript 构建脚本

(3)构建后,main 字段应指向编译后的 JavaScript 文件而非源文件