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, поэтому вы можете импортировать API Node и зависимости npm по мере необходимости.

Затем вы можете импортировать данные из этого файла в .md страницы и .vue компоненты, используя именованный экспорт data:

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() {
    // получение удаленных данных
    return (await fetch('...')).json()
  }
}

Данные из локальных файлов

Когда вам нужно сгенерировать данные на основе локальных файлов, вы должны использовать опцию watch в загрузчике данных, чтобы изменения в этих файлах могли вызывать горячие обновления.

Опция watch также удобна тем, что вы можете использовать шаблоны glob для сопоставления нескольких файлов. Шаблоны могут быть относительными по отношению к самому файлу загрузчика, и функция load() получит сопоставленные файлы как абсолютные пути.

Следующий пример показывает загрузку файлов CSV и их преобразование в JSON с использованием csv-parse. Поскольку этот файл выполняется только во время сборки, вы не будете отправлять парсер CSV клиенту!

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

export default {
  watch: ['./data/*.csv'],
  load(watchedFiles) {
    // watchedFiles будет массивом абсолютных путей сопоставленных файлов.
    // генерируем массив метаданных блог-постов, который можно использовать для отображения
    // списка в макете темы
    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 {
  // сопоставленный 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 в клиентском бандле, поэтому мы должны быть осторожны с его размером. Вот пример использования данных для создания минимальной индексной страницы блога:

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

<template>
  <h1>Все блог-посты</h1>
  <ul>
    <li v-for="post in posts">
      <a :href="post.url">{{ post.frontmatter.title }}</a>
      <span>от {{ post.frontmatter.author }}</span>
    </li>
  </ul>
</template>

Опции

По умолчанию данные могут не соответствовать всем потребностям - вы можете включить преобразование данных с помощью опций:

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

export default createContentLoader('posts/*.md', {
  includeSrc: true, // включать исходный код markdown?
  render: true,     // включать отрендеренный полный HTML страницы?
  excerpt: true,    // включать выдержку?
  transform(rawData) {
    // сортируйте, фильтруйте или преобразуйте сырые данные по своему усмотрению.
    // окончательный результат будет отправлен клиенту.
    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.

API createContentLoader также может быть использован внутри хуков сборки:

js
// .vitepress/config.js
export default {
  async buildEnd() {
    const posts = await createContentLoader('posts/*.md').load()
    // генерируем файлы на основе метаданных постов, например, 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

  /**
   * Преобразовать данные. Обратите внимание, что данные будут встроены в JSON в клиентском
   * бандле, если импортированы из компонентов или файлов markdown.
   */
  transform?: (data: ContentData[]) => T | Promise<T>
}

Типизированные загрузчики данных

При использовании TypeScript вы можете типизировать ваш загрузчик и экспорт data следующим образом:

ts
import { defineLoader } from 'vitepress'

export interface Data {
  // тип данных
}

declare const data: Data
export { data }

export default defineLoader({
  // опции загрузчика с проверкой типов
  watch: ['...'],
  async load(): Promise<Data> {
    // ...
  }
})

Конфигурация

Чтобы получить информацию о конфигурации внутри загрузчика, вы можете использовать код вроде этого:

ts
import type { SiteConfig } from 'vitepress'

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

Содержание доступно по лицензии MIT