跳至内容

构建时数据加载

VitePress 提供了一个名为 **数据加载器** 的功能,它允许您加载任意数据并从页面或组件中导入它。数据加载仅在 **构建时** 执行:最终的 JavaScript 包中将以 JSON 格式序列化结果数据。

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

基本用法

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

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

加载器模块仅在 Node.js 中进行评估,因此您可以根据需要导入 Node API 和 npm 依赖项。

然后,您可以使用 data 命名导出在 .md 页面和 .vue 组件中从该文件导入数据

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

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

输出

json
{
  "hello": "world"
}

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

即使加载器是异步的,这也适用

js
export default {
  async load() {
    // fetch remote data
    return (await fetch('...')).json()
  }
}

来自本地文件的数据

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

watch 选项也很方便,因为您可以使用 glob 模式 来匹配多个文件。这些模式可以相对于加载器文件本身,load() 函数将接收匹配的文件作为绝对路径。

以下示例显示了加载 CSV 文件并使用 csv-parse 将其转换为 JSON。由于此文件仅在构建时执行,因此您不会将 CSV 解析器发送到客户端!

js
import fs from 'node:fs'
import { parse } from 'csv-parse/sync'

export default {
  watch: ['./data/*.csv'],
  load(watchedFiles) {
    // watchedFiles will be an array of absolute paths of the matched files.
    // generate an array of blog post metadata that can be used to render
    // a list in the theme layout
    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 */)

该助手采用相对于 源目录 的 glob 模式,并返回一个 { watch, load } 数据加载器对象,该对象可用作数据加载器文件中的默认导出。它还实现了基于文件修改时间戳的缓存以提高开发性能。

请注意,加载器仅适用于 Markdown 文件 - 匹配的非 Markdown 文件将被跳过。

加载的数据将是一个类型为 ContentData[] 的数组

ts
interface ContentData {
  // mapped URL for the page. e.g. /posts/hello.html (does not include base)
  // manually iterate or use custom `transform` to normalize the paths
  url: string
  // frontmatter data of the page
  frontmatter: Record<string, any>

  // the following are only present if relevant options are enabled
  // we will discuss them below
  src: string | undefined
  html: string | undefined
  excerpt: string | undefined
}

默认情况下,仅提供 urlfrontmatter。这是因为加载的数据将作为 JSON 内联到客户端包中,因此我们需要谨慎处理其大小。以下是如何使用数据构建最小博客索引页面的示例

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>

选项

默认数据可能不适合所有需求 - 您可以选择使用选项来转换数据

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

export default createContentLoader('posts/*.md', {
  includeSrc: true, // include raw markdown source?
  render: true,     // include rendered full page HTML?
  excerpt: true,    // include excerpt?
  transform(rawData) {
    // map, sort, or filter the raw data as you wish.
    // the final result is what will be shipped to the client.
    return rawData.sort((a, b) => {
      return +new Date(b.frontmatter.date) - +new Date(a.frontmatter.date)
    }).map((page) => {
      page.src     // raw markdown source
      page.html    // rendered full page HTML
      page.excerpt // rendered excerpt HTML (content above first `---`)
      return {/* ... */}
    })
  }
})

查看它在 Vue.js 博客 中的用法。

createContentLoader API 也可以在 构建钩子 中使用

js
// .vitepress/config.js
export default {
  async buildEnd() {
    const posts = await createContentLoader('posts/*.md').load()
    // generate files based on posts metadata, e.g. RSS feed
  }
}

类型

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

  /**
   * Render src to HTML and include in data?
   * @default false
   */
  render?: boolean

  /**
   * If `boolean`, whether to parse and include excerpt? (rendered as HTML)
   *
   * If `function`, control how the excerpt is extracted from the content.
   *
   * If `string`, define a custom separator to be used for extracting the
   * excerpt. Default separator is `---` if `excerpt` is `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

  /**
   * Transform the data. Note the data will be inlined as JSON in the client
   * bundle if imported from components or markdown files.
   */
  transform?: (data: ContentData[]) => T | Promise<T>
}

类型化数据加载器

在使用 TypeScript 时,您可以像这样为加载器和 data 导出类型

ts
import { defineLoader } from 'vitepress'

export interface Data {
  // data type
}

declare const data: Data
export { data }

export default defineLoader({
  // type checked loader options
  watch: ['...'],
  async load(): Promise<Data> {
    // ...
  }
})

配置

要在加载器中获取配置信息,您可以使用以下代码

ts
import type { SiteConfig } from 'vitepress'

const config: SiteConfig = (globalThis as any).VITEPRESS_CONFIG