fix content loader

pull/4808/head
Divyansh Singh 3 months ago
parent fb22f0f2ef
commit da8fff4aac

@ -1,10 +1,16 @@
import fs from 'fs-extra' import fs from 'fs-extra'
import matter from 'gray-matter' import matter from 'gray-matter'
import path from 'node:path' import path from 'node:path'
import { glob, type GlobOptions } from 'tinyglobby'
import { normalizePath } from 'vite' import { normalizePath } from 'vite'
import type { SiteConfig } from './config' import type { SiteConfig } from './config'
import { createMarkdownRenderer } from './markdown/markdown' import { createMarkdownRenderer } from './markdown/markdown'
import type { LoaderModule } from './plugins/staticDataPlugin'
import type { Awaitable } from './shared'
import {
getWatchedFiles,
normalizeWatchPatterns,
type GlobOptions
} from './utils/glob'
export interface ContentOptions<T = ContentData[]> { export interface ContentOptions<T = ContentData[]> {
/** /**
@ -48,12 +54,10 @@ export interface ContentOptions<T = ContentData[]> {
* Transform the data. Note the data will be inlined as JSON in the client * Transform the data. Note the data will be inlined as JSON in the client
* bundle if imported from components or markdown files. * bundle if imported from components or markdown files.
*/ */
transform?: (data: ContentData[]) => T | Promise<T> transform?: (data: ContentData[]) => Awaitable<T>
/** /**
* Options to pass to `tinyglobby`. * Options to pass to `tinyglobby` and `picomatch` for globbing.
* You'll need to manually specify `node_modules` and `dist` in
* `globOptions.ignore` if you've overridden it.
*/ */
globOptions?: GlobOptions globOptions?: GlobOptions
} }
@ -74,18 +78,9 @@ export function createContentLoader<T = ContentData[]>(
/** /**
* files to glob / watch - relative to srcDir * files to glob / watch - relative to srcDir
*/ */
pattern: string | string[], watch: string | string[],
{ options: ContentOptions<T> = {}
includeSrc, ): LoaderModule<T> {
render,
excerpt: renderExcerpt,
transform,
globOptions
}: ContentOptions<T> = {}
): {
watch: string | string[]
load: () => Promise<T>
} {
const config: SiteConfig = (global as any).VITEPRESS_CONFIG const config: SiteConfig = (global as any).VITEPRESS_CONFIG
if (!config) { if (!config) {
throw new Error( throw new Error(
@ -94,24 +89,17 @@ export function createContentLoader<T = ContentData[]>(
) )
} }
if (typeof pattern === 'string') pattern = [pattern]
pattern = pattern.map((p) => normalizePath(path.join(config.srcDir, p)))
const cache = new Map<string, { data: any; timestamp: number }>() const cache = new Map<string, { data: any; timestamp: number }>()
watch = normalizeWatchPatterns(watch, config.srcDir)
return { return {
watch: pattern, watch,
options: { globOptions: options.globOptions },
async load(files?: string[]) { async load(files?: string[]) {
if (!files) { // the loader is being called directly, do a fresh glob
// the loader is being called directly, do a fresh glob if (!files) files = await getWatchedFiles(watch, options.globOptions)
files = (
await glob(pattern, {
ignore: ['**/node_modules/**', '**/dist/**'],
expandDirectories: false,
...globOptions
})
).sort()
}
const md = await createMarkdownRenderer( const md = await createMarkdownRenderer(
config.srcDir, config.srcDir,
@ -123,43 +111,51 @@ export function createContentLoader<T = ContentData[]>(
const raw: ContentData[] = [] const raw: ContentData[] = []
for (const file of files) { for (const file of files) {
if (!file.endsWith('.md')) { if (!file.endsWith('.md')) continue
continue
}
const timestamp = fs.statSync(file).mtimeMs const timestamp = fs.statSync(file).mtimeMs
const cached = cache.get(file) const cached = cache.get(file)
if (cached && timestamp === cached.timestamp) { if (cached && timestamp === cached.timestamp) {
raw.push(cached.data) raw.push(cached.data)
} else { } else {
//
const src = fs.readFileSync(file, 'utf-8') const src = fs.readFileSync(file, 'utf-8')
const renderExcerpt = options.excerpt
const { data: frontmatter, excerpt } = matter( const { data: frontmatter, excerpt } = matter(
src, src,
// @ts-expect-error gray-matter types are wrong
typeof renderExcerpt === 'string' typeof renderExcerpt === 'string'
? { excerpt_separator: renderExcerpt } ? { excerpt_separator: renderExcerpt }
: { excerpt: renderExcerpt } : { excerpt: renderExcerpt as any } // gray-matter types are wrong
) )
const url = const url =
'/' + '/' +
normalizePath(path.relative(config.srcDir, file)) normalizePath(path.relative(config.srcDir, file))
.replace(/(^|\/)index\.md$/, '$1') .replace(/(^|\/)index\.md$/, '$1')
.replace(/\.md$/, config.cleanUrls ? '' : '.html') .replace(/\.md$/, config.cleanUrls ? '' : '.html')
const html = render ? await md.renderAsync(src) : undefined
const html = options.render ? await md.renderAsync(src) : undefined
const renderedExcerpt = renderExcerpt const renderedExcerpt = renderExcerpt
? excerpt && (await md.renderAsync(excerpt)) ? excerpt && (await md.renderAsync(excerpt))
: undefined : undefined
const data: ContentData = { const data: ContentData = {
src: includeSrc ? src : undefined, src: options.includeSrc ? src : undefined,
html, html,
frontmatter, frontmatter,
excerpt: renderedExcerpt, excerpt: renderedExcerpt,
url url
} }
cache.set(file, { data, timestamp }) cache.set(file, { data, timestamp })
raw.push(data) raw.push(data)
} }
} }
return (transform ? transform(raw) : raw) as any
return options.transform?.(raw) ?? (raw as T)
} }
} }
} }

Loading…
Cancel
Save