vitepress的配置合并。
一、概述
当建立一个vitepress站点后,得到的默认配置文件如下:
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 配置合在一起:
// .vitepress/config.mts
import { defineConfig } from "vitepress";
export default defineConfig({
// ...
themeConfig: {
// vitepress 配置
// 自定义主题配置
},
});
然后在组件里通过 useData
获取 themeConfig
的内容:
<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
合并主题配置的使用方式如下:
import { defineConfig } from "vitepress";
// 主题配置
const MistConfig = {};
// vitepress 配置
export default defineConfig({
extends: MistConfig,
// ...
themeConfig: {
// ...
},
});
在 VitePress 配置中通过 extends
可以将主题配置合并到 VitePress 配置里,也就是说完全可以在主题配置里添加 VitePress 的配置项,但是不能反过来。
2. 配置合并示例
2.1 分别配置
// .vitepress/config.mts
import { defineConfig } from "vitepress";
// 主题配置
const myThemeConfig = { themeConfig: { MistTheme: true } };
// VitePress 配置
export default defineConfig({
extends: myThemeConfig,
base: "/",
});
2.2 统一配置
// .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
中添加以下内容:
<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
中注册这个组件:
// 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文档中获取页面数据和站点数据了,新建的站点默认的配置项数据如下:
{
"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
:
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配置项,可以注意观察一下页脚的位置,然后我们会得到以下配置项:
{
"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
:
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配置项,可以注意观察一下页脚的位置,然后我们会得到以下配置项:
{
"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
:
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: '莫道桑榆晚 为霞尚满天',
},
}
})
然后会得到以下数据:
{
"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 默认的配置,则可以提供一个函数来返回主题配置:
// .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 时,返回包含 ignoreDeadLinks
、head
、vite
和 themeConfig
属性的配置对象。
2. 类型问题?
这里我有一个疑问,mistThemeConfig
的类型是 MistThemeConfig & UserConfig<DefaultTheme.Config>
,为什么可以赋值给 themeConfig
属性?
2.1 TypeScript 类型兼容性基础
2.1.1 结构化类型系统(Structural Typing)
TypeScript 使用结构化类型系统,也称为"鸭子类型"(Duck Typing)。这意味着:
"如果一个对象看起来像鸭子,叫声像鸭子,那么它就是鸭子。"
在 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)源类型可以包含额外的属性
// 示例 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)
交叉类型使用 &
符号,将多个类型合并为一个类型。新类型将包含所有类型的所有属性。
// 示例 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 接口的继承层次如下
// 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 特定的配置属性
继承的实际影响:
// 当我们使用 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 的实际类型
通过分析函数,可以发现类型兼容性的真正原因:
// 在我们的代码中
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
类型兼容性的真正原因:
// mistThemeConfig 的类型:MistThemeConfig & UserConfig<DefaultTheme.Config>
// themeConfig 的类型:any (因为返回类型 UserConfig 使用默认泛型参数)
// ✅ 任何类型都可以赋值给 any 类型!
themeConfig: mistThemeConfig // 这是合法的,因为目标类型是 any
2.4 交叉类型的作用
虽然交叉类型不是类型兼容性的主要原因,但它在这个场景中仍然起到了重要作用:
// 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 验证:如果明确指定返回类型
让我们验证我们的理解,修改函数签名明确指定返回类型的泛型参数:
// 修改后的函数签名
export default function getThemeConfig(
config: MistThemeConfig & UserConfig<DefaultTheme.Config> = {}
): UserConfig<DefaultTheme.Config> { // 明确指定泛型参数
// ...
return {
ignoreDeadLinks: true,
head: [],
vite: { plugins: [] },
themeConfig: mistThemeConfig, // ❌ 这将导致类型错误!
};
}
这个时候语法检测就会报错:
[{
"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
包含额外的useTheme
和themeName
属性
2.6 完整的类型兼容性解释
现在我们可以给出完整的类型兼容性解释:
// 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
// .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
引入该函数:
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 配置分析
按照上面的配置,这个时候,我们会得到下面的数据:
{
"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
中:
useTheme: true,
themeName: "mist",
themeConfig: {
footer: {
message: '前路漫漫亦灿灿',
},
}
包括在.vitepress/config.mts
中对themeConfig的配置,它其实并没有合并到最终的themeConfig中,而是成为了themeConfig的themeConfig配置项:
{
"themeConfig": {
"useTheme": true,
"themeName": "mist",
"themeConfig": {
"footer": {
"message": "前路漫漫亦灿灿"
}
},
"footer": {
"message": "莫道桑榆晚 为霞尚满天"
}
},
}
四、开发实例
1. 源码
docs-site/vitepress-theme-mist at 3e5d81e9b7dff0c94a46f993bd8fe8355c2c4a25
这里其实是有bug的,不过后来已经修复了,具体原理可以看第三部分。
2. 工作区外示例
首先安装vitepress-theme-mist
:
npm i -D ..\vitepress-theme-mist\dist\vitepress-theme-mist\
npm i vitepress-theme-mist
然后在.vitepress/config.mts
中添加以下内容:
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
中添加:
import { defineMistConfig } from "../../../packages/config";
然后就和上面一样:
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映射过了:
{
//......
"paths": { // 路径映射,用于模块解析的别名配置
"@mist/*": ["packages/*"] // 将 @mist/* 映射到 packages/* 目录
}
}
直接用这个@mist不就可以了,当我们尝试使用包导入的方式:
import { defineMistConfig } from "@mist/config";
会出现以下错误:
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.json
的 main
字段,当前 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 文件而非源文件