vitepress 的配置文件是位于 .vitepress/config.ts
下的,我们需要在这个文件中默认导出一个 UserConfig
类型的对象,但是这个配置文件是如何反映到客户端上的呢?我们从零开始看一下。
在开发阶段,我们首先输入命令 pnpm run docs:dev
启动一个开发服务器,然后在浏览器打开首页,这个过程向服务器请求文件的顺序为:
index.html
client/app/index.js
client/app/data.js
@siteData
客户端初始化
我们这里介绍的初始化过程是从文档加载起,到向服务器请求配置文件结束,不包含其他诸如路由初始化等。下面是我们执行初始化过程的栈帧
plaintext
initData (data.ts:74)
createApp (index.ts:75)
(匿名) (index.ts:169)
我们从匿名函数开始执行(实际上就是入口脚本的全局作用域代码),调用了 createApp
来创建一个 app,而在这个过程中我们会调用 initData
来初始化站点配置
向服务器请求配置
前三步都是正常的文档和脚本请求,只有最后一个是虚拟模块请求,我们通过配置 vite
开发服务器,使用了一个 vitePressPlugin
插件,这个插件包含 resolveId
和 load
钩子,前者用来解析客户端 @siteData
,后者用来实际加载这个模块的内容,从而来最终返回需要的 siteData
。
resolveId
没有什么好讲的,主要看 load
,他的定义如下:
typescript
// plugin.ts
load(id) {
if (id === SITE_DATA_REQUEST_PATH) {
let data = siteData
data = serializeFunctions(data)
return `${deserializeFunctions};export default deserializeFunctions(JSON.parse(${JSON.stringify(
JSON.stringify(data)
)}))`
}
},
可以看到有一个 siteData
变量,我们先不管他,先看一下我们这个虚拟模块到底最终返回了什么,可以看到显示对 siteData
进行了序列化的操作,然后返回了一个字符串(最终需要浏览器执行的脚本),这个返回的字符串中还贴心地包含了一个反序列化的函数代码,等该文件传送到客户端的时候浏览器直接用该函数反序列化 siteData
对象,从而获取站点数据。
处理传送过来的配置
我们从服务端返回到客户端,看一下导入 siteData
后发生了什么。
导入后,我们调用的 initData
函数使用了这个 siteData
,并且将其包裹在一个 VitePressData
类型的对象中返回
TypeScript
export function initData(route: Route): VitePressData {
const site = computed(() =>
resolveSiteDataByRoute(siteDataRef.value, route.data.relativePath)
)
// ...
return {
site,
theme: computed(() => site.value.themeConfig),
page: computed(() => route.data),
// ...
}
}
并最终将这个返回的对象作为一个 symbol 注入到创建的 app 中。
typescript
export async function createApp() {
const router = newRouter();
const app = newApp();
app.provide(RouterSymbol, router);
const data = initData(router.route);
app.provide(dataSymbol, data);
return { app, router, data };
}
总结
从用户打开浏览器并且输入 localhost:5173
后,浏览器首先请求了一个 index.html
,并且执行其中的脚本,引入了 client/app/app.js
和 client/app/data.js
。
在执行 data.js
的过程中,浏览器会解析一个 import siteData from '@siteData'
引入指令,开发服务器接收到 @site
的请求,会交由 VitepressPlugin
进行处理,并最终返回被序列化后的用户配置数据,并且包含一个反序列化的函数。
客户端接收到用户数据后,将其赋值给 siteData
变量,并最终通过 vue 的 provide
API 将其注入到第一步创建的 APP 中,从而可以使用 useData
来引入用户的配置数据。