You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vitepress/docs/zh/guide/data-loading.md

248 lines
7.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 构建时数据加载 {#build-time-data-loading}
VitePress 提供了**数据加载**的功能,它允许加载任意数据并从页面或组件中导入它。数据加载**只在构建时**执行:最终的数据将被序列化为 JavaScript 包中的 JSON。
数据加载可以被用于获取远程数据,也可以基于本地文件生成元数据。例如,可以使用数据加载来解析所有本地 API 页面并自动生成所有 API 入口的索引。
## 基本用法 {#basic-usage}
一个用于数据加载的文件必须以 `.data.js``.data.ts` 结尾。该文件应该提供一个默认导出的对象,该对象具有 `load()` 方法:
```js
// example.data.js
export default {
load() {
return {
hello: 'world'
}
}
}
```
数据加载模块只在 Node.js 中执行,因此可以按需导入 Node API 和 npm 依赖。
然后,可以在 `.md` 页面和 `.vue` 组件中使用 `data` 具名导出从该文件中导入数据:
```vue
<script setup>
import { data } from './example.data.js'
</script>
<pre>{{ data }}</pre>
```
输出:
```json
{
"hello": "world"
}
```
你会注意到 data loader 本身并没有导出 `data`。这是因为 VitePress 在后台调用了 `load()` 方法,并通过名为 `data` 的具名导出隐式地暴露了结果。
即使它是异步的,这也是有效的:
```js
export default {
async load() {
// 获取远程数据
return (await fetch('...')).json()
}
}
```
## 使用本地文件生成数据 {#data-from-local-files}
当需要基于本地文件生成数据时,需要在 data loader 中使用 `watch` 选项,以便这些文件改动时可以触发热更新。
`watch` 选项也很方便,因为可以使用 [glob 模式](https://github.com/mrmlnc/fast-glob#pattern-syntax) 匹配多个文件。模式可以相对于数据加载文件本身,`load()` 函数将接收匹配文件的绝对路径。
下面的例子展示了如何使用 [csv-parse](https://github.com/adaltas/node-csv/tree/master/packages/csv-parse/) 加载 CSV 文件并将其转换为 JSON。因为此文件仅在构建时执行因此不会将 CSV 解析器发送到客户端。
```js
import fs from 'node:fs'
import { parse } from 'csv-parse/sync'
export default {
watch: ['./data/*.csv'],
load(watchedFiles) {
// watchFiles 是一个所匹配文件的绝对路径的数组。
// 生成一个博客文章元数据数组
// 可用于在主题布局中呈现列表。
return watchedFiles.map((file) => {
return parse(fs.readFileSync(file, 'utf-8'), {
columns: true,
skip_empty_lines: true
})
})
}
}
```
## `createContentLoader`
当构建一个内容为主的站点时,我们经常需要创建一个“归档”或“索引”页面:一个我们可以列出内容中的所有可用条目的页面,例如博客文章或 API 页面。我们**可以**直接使用数据加载 API 实现这一点但由于这会经常使用VitePress 还提供了一个 `createContentLoader` 辅助函数来简化这个过程:
```js
// posts.data.js
import { createContentLoader } from 'vitepress'
export default createContentLoader('posts/*.md', /* options */)
```
该辅助函数接受一个相对于[项目根目录](./routing#project-root)的 glob 模式,并返回一个 `{ watch, load }` 数据加载对象,该对象可以用作数据加载文件中的默认导出。它还基于文件修改时间戳实现了缓存以提高开发性能。
请注意,数据加载仅适用于 Markdown 文件——匹配的非 Markdown 文件将被跳过。
加载的数据将是一个类型为 `ContentData[]` 的数组:
```ts
interface ContentData {
// 页面的映射 URL如 /posts/hello.html不包括 base
// 手动迭代或使用自定义 `transform` 来标准化路径
url: string
// 页面的 frontmatter 数据
frontmatter: Record<string, any>
// 只有启用了相关选项,才会出现以下内容
// 我们将在下面讨论它们
src: string | undefined
html: string | undefined
excerpt: string | undefined
}
```
默认情况下只提供 `url``frontmatter`。这是因为加载的数据将作为 JSON 内联在客户端 bundle 中,我们需要谨慎考虑其大小。下面的例子展示了如何使用数据构建最小的博客索引页面:
```vue
<script setup>
import { data as posts } from './posts.data.js'
</script>
<template>
<h1>All Blog Posts</h1>
<ul>
<li v-for="post of posts">
<a :href="post.url">{{ post.frontmatter.title }}</a>
<span>by {{ post.frontmatter.author }}</span>
</li>
</ul>
</template>
```
### 选项 {#options}
默认数据可能不适合所有需求——可以选择使用选项转换数据:
```js
// posts.data.js
import { createContentLoader } from 'vitepress'
export default createContentLoader('posts/*.md', {
includeSrc: true, // 包含原始 markdown 源?
render: true, // 包含渲染的整页 HTML?
excerpt: true, // 包含摘录?
transform(rawData) {
// 根据需要对原始数据进行 map、sort 或 filter
// 最终的结果是将发送给客户端的内容
return rawData.sort((a, b) => {
return +new Date(b.frontmatter.date) - +new Date(a.frontmatter.date)
}).map((page) => {
page.src // 原始 markdown 源
page.html // 渲染的整页 HTML
page.excerpt // 渲染的摘录 HTML第一个 `---` 上面的内容)
return {/* ... */}
})
}
})
```
查看它在 [Vue.js 博客](https://github.com/vuejs/blog/blob/main/.vitepress/theme/posts.data.ts)中是如何使用的。
`createContentLoader` API 也可以在[构建钩子](/reference/site-config#build-hooks)中使用:
```js
// .vitepress/config.js
export default {
async buildEnd() {
const posts = await createContentLoader('posts/*.md').load()
// 根据 posts 元数据生成文件,如 RSS 订阅源
}
}
```
**类型**
```ts
interface ContentOptions<T = ContentData[]> {
/**
* 包含 src?
* @default false
*/
includeSrc?: boolean
/**
* 将 src 渲染为 HTML 并包含在数据中?
* @default false
*/
render?: boolean
/**
* 如果为 `boolean`,是否解析并包含摘录? (呈现为 HTML)
*
* 如果为 `function`,则控制如何从内容中提取摘录
*
* 如果为 `string`,则定义用于提取摘录的自定义分隔符
* 如果 `excerpt` 为 `true`,则默认分隔符为 `---`
*
* @see https://github.com/jonschlinkert/gray-matter#optionsexcerpt
* @see https://github.com/jonschlinkert/gray-matter#optionsexcerpt_separator
*
* @default false
*/
excerpt?:
| boolean
| ((file: { data: { [key: string]: any }; content: string; excerpt?: string }, options?: any) => void)
| string
/**
* 转换数据。请注意,如果从组件或 Markdown 文件导入,数据将以 JSON 形式内联到客户端包中
*/
transform?: (data: ContentData[]) => T | Promise<T>
}
```
## 为 data loader 导出类型 {#typed-data-loaders}
当使用 TypeScript 时,可以像这样为 loader 和 `data` 导出类型:
```ts
import { defineLoader } from 'vitepress'
export interface Data {
// data 类型
}
declare const data: Data
export { data }
export default defineLoader({
// 类型检查加载器选项
watch: ['...'],
async load(): Promise<Data> {
// ...
}
})
```
## 配置 {#configuration}
要获取 data loader 中的配置信息,可以使用如下代码:
```ts
import type { SiteConfig } from 'vitepress'
const config: SiteConfig = (globalThis as any).VITEPRESS_CONFIG
```