Skip to content

数据加载

VitePress 提供了一个数据加载功能,允许你加载任意数据并从页面或组件中导入它。数据加载只在构建时执行:生成的数据将作为 JSON 序列化在最终的 JavaScript 包中。

数据加载可以用来获取远程数据,也可以基于本地文件生成元数据。例如,你可以使用数据加载来解析所有本地 API 页面并自动生成所有 API 条目的索引。

基本用法

数据加载文件必须以 .data.js.data.ts 结尾。该文件应该提供一个默认导出的对象,该对象具有 load() 方法:

js
// example.data.js
export default {
  load() {
    return {
      hello: 'world'
    }
  }
}

数据加载模块只在 Node.js 中评估,因此你可以根据需要导入 Node.js API 和 npm 依赖项。

然后你可以使用 data 命名导出从任何页面导入此文件中的数据:

vue
<script setup>
import { data } from './example.data.js'
</script>

<pre>{{ data }}</pre>

输出:

json
{
  "hello": "world"
}

你会注意到数据加载器本身不导出 data。这是因为 VitePress 在幕后调用 load() 方法,并通过名为 data 的命名导出隐式公开结果。

即使加载器是异步的,这也有效:

js
// example.data.js
export default {
  async load() {
    // 加载远程数据
    return (await fetch('...')).json()
  }
}

从本地文件生成数据

当你需要基于本地文件生成数据时,你应该在数据加载器中使用 watch 选项,以便这些文件更改时可以触发热更新。

watch 选项也很方便,因为你可以使用 glob 模式 来匹配多个文件。模式可以是相对于加载器文件本身的,它们将相对于项目根目录解析。

js
// posts.data.js
import { createContentLoader } from 'vitepress'

export default createContentLoader('posts/*.md', {
  watch: ['./posts/**/*.md'],
  load() {
    // 返回从本地文件生成的数据
  }
})

createContentLoader

当构建以内容为中心的站点时,我们经常需要创建一个"存档"或"索引"页面:一个我们可以列出内容集合中所有可用条目的页面,例如博客文章或 API 页面。我们可以直接使用数据加载 API 来实现这一点,但由于这是一个常见的用例,VitePress 还提供了一个 createContentLoader 辅助程序来简化这一点:

js
// posts.data.js
import { createContentLoader } from 'vitepress'

export default createContentLoader('posts/*.md') // 包括所有 `posts/*.md`
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" :key="post.url">
      <a :href="post.url">{{ post.frontmatter.title }}</a>
      <span>by {{ post.frontmatter.author }}</span>
    </li>
  </ul>
</template>

辅助程序接受一个 glob 模式作为第一个参数,并返回一个 { url, frontmatter, excerpt } 对象数组。excerpt 是渲染的内容摘要(第一个 --- 分隔符之前的内容)。

声明监视文件

默认情况下,内容加载器将添加所有匹配的文件作为监视依赖项。这意味着对匹配文件的任何更改都将触发热重载。但是,在大型项目中,这可能会降低开发性能,特别是如果加载器包含大量文件。在这种情况下,你可以禁用监视并手动声明监视文件:

js
// posts.data.js
import { createContentLoader } from 'vitepress'

export default createContentLoader('posts/*.md', {
  includeSrc: false, // 不包括原始 markdown 源
  render: false,     // 不包括渲染的完整页面 HTML
  excerpt: false,    // 不包括摘要
  watch: false       // 禁用自动监视,手动声明监视文件
})

高级配置

createContentLoader 还接受一个选项对象作为第二个参数:

ts
interface ContentOptions<T = ContentData[]> {
  /**
   * 包括 src?
   * @default false
   */
  includeSrc?: boolean

  /**
   * 渲染 src 到 HTML 并包括在数据中?
   * @default false
   */
  render?: boolean

  /**
   * 如果 `boolean`,是否解析和包括摘要?(渲染为 HTML)
   *
   * 如果 `function`,控制如何从内容中提取摘要。
   *
   * 如果 `string`,定义用于提取摘要的自定义分隔符。
   * 默认分隔符是 `---`,如果你在 frontmatter 中使用它。
   *
   * @default false
   */
  excerpt?:
    | boolean
    | ((file: { data: { [key: string]: any }; content: string; filename: string; excerpt?: string }) => string)
    | string

  /**
   * 要监视的附加文件。
   *
   * 监视的文件路径相对于项目根目录。
   */
  watch?: string | string[]

  /**
   * 转换数据。注意数据将在客户端内联,
   * 所以保持有效负载尽可能小。
   */
  transform?: (data: ContentData[]) => T | Promise<T>
}

例如,要仅包括页面标题和 URL:

js
// posts.data.js
import { createContentLoader } from 'vitepress'

export default createContentLoader('posts/*.md', {
  includeSrc: false, // 不包括原始 markdown 源
  render: false,     // 不包括渲染的完整页面 HTML
  excerpt: false,    // 不包括摘要
  transform(rawData) {
    // 根据原始数据映射,排序,或过滤
    // 最终结果是将发送给客户端的内容
    return rawData.sort((a, b) => {
      return +new Date(b.frontmatter.date) - +new Date(a.frontmatter.date)
    }).map((page) => {
      return {
        title: page.frontmatter.title,
        url: page.url
      }
    })
  }
})

注意 transform 钩子不会在开发服务器中运行。但是,如果你需要在开发中预览结果,你可以添加 console.log(data) 来检查转换后的数据。

类型化数据加载器

使用 TypeScript 时,你可以像这样类型化加载器和 data 导出:

ts
// posts.data.ts
import { defineLoader } from 'vitepress'

export interface Post {
  title: string
  url: string
  date: {
    time: number
    string: string
  }
}

declare const data: Post[]
export { data }

export default defineLoader({
  async load(): Promise<Post[]> {
    // ...
  }
})

配置

要在配置文件中获取数据,你可以使用 loadData 辅助程序:

js
// .vitepress/config.js
import { defineConfig, loadData } from 'vitepress'

const posts = loadData('posts.data.js')

export default defineConfig({
  // ...
  themeConfig: {
    sidebar: [
      // ...
      {
        text: 'Blog Posts',
        items: posts.map((post) => ({
          text: post.frontmatter.title,
          link: post.url
        }))
      }
    ]
  }
})

vitepress开发指南