跳到內容

建置時間資料載入

VitePress 提供一個名為資料載入器的功能,讓你可以載入任意資料並從頁面或元件匯入。資料載入僅在建置時執行:產生的資料將以 JSON 格式序列化在最終的 JavaScript 程式包中。

資料載入器可用於擷取遠端資料,或根據本機檔案產生元資料。例如,你可以使用資料載入器解析所有本機 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 模式 來比對多個檔案。這些模式可以相對於 loader 檔案本身,而 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

在 MIT 授權下發布。