mirror of https://github.com/vuejs/vitepress
parent
905f58b2a8
commit
d2838e3755
@ -0,0 +1,9 @@
|
||||
---
|
||||
title: bar
|
||||
---
|
||||
|
||||
Hello
|
||||
|
||||
---
|
||||
|
||||
world
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
title: foo
|
||||
---
|
||||
|
||||
Hello
|
||||
|
||||
---
|
||||
|
||||
world
|
@ -0,0 +1,13 @@
|
||||
import { createContentLoader } from 'vitepress'
|
||||
|
||||
export default createContentLoader('data-loading/content/*.md', {
|
||||
includeSrc: true,
|
||||
excerpt: true,
|
||||
render: true,
|
||||
transform(data) {
|
||||
return data.map((item) => ({
|
||||
...item,
|
||||
transformed: true
|
||||
}))
|
||||
}
|
||||
})
|
@ -0,0 +1,10 @@
|
||||
# Static Data
|
||||
|
||||
<script setup lang="ts">
|
||||
import { data } from './basic.data.js'
|
||||
import { data as contentData } from './contentLoader.data.js'
|
||||
</script>
|
||||
|
||||
<pre id="basic">{{ data }}</pre>
|
||||
|
||||
<pre id="content">{{ contentData }}</pre>
|
@ -0,0 +1,42 @@
|
||||
describe('static data file support in vite 3', () => {
|
||||
beforeAll(async () => {
|
||||
await goto('/data-loading/data')
|
||||
})
|
||||
|
||||
test('render correct content', async () => {
|
||||
expect(await page.textContent('pre#basic')).toMatchInlineSnapshot(`
|
||||
"[
|
||||
{
|
||||
\\"foo\\": true
|
||||
},
|
||||
{
|
||||
\\"bar\\": true
|
||||
}
|
||||
]"
|
||||
`)
|
||||
expect(await page.textContent('pre#content')).toMatchInlineSnapshot(`
|
||||
"[
|
||||
{
|
||||
\\"src\\": \\"---\\\\ntitle: bar\\\\n---\\\\n\\\\nHello\\\\n\\\\n---\\\\n\\\\nworld\\\\n\\",
|
||||
\\"html\\": \\"<p>Hello</p>\\\\n<hr>\\\\n<p>world</p>\\\\n\\",
|
||||
\\"frontmatter\\": {
|
||||
\\"title\\": \\"bar\\"
|
||||
},
|
||||
\\"excerpt\\": \\"<p>Hello</p>\\\\n\\",
|
||||
\\"url\\": \\"/data-loading/content/bar.html\\",
|
||||
\\"transformed\\": true
|
||||
},
|
||||
{
|
||||
\\"src\\": \\"---\\\\ntitle: foo\\\\n---\\\\n\\\\nHello\\\\n\\\\n---\\\\n\\\\nworld\\\\n\\",
|
||||
\\"html\\": \\"<p>Hello</p>\\\\n<hr>\\\\n<p>world</p>\\\\n\\",
|
||||
\\"frontmatter\\": {
|
||||
\\"title\\": \\"foo\\"
|
||||
},
|
||||
\\"excerpt\\": \\"<p>Hello</p>\\\\n\\",
|
||||
\\"url\\": \\"/data-loading/content/foo.html\\",
|
||||
\\"transformed\\": true
|
||||
}
|
||||
]"
|
||||
`)
|
||||
})
|
||||
})
|
@ -1,14 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`static data file support in vite 3 > render correct content 1`] = `
|
||||
[
|
||||
"[
|
||||
{
|
||||
\\"foo\\": true
|
||||
},
|
||||
{
|
||||
\\"bar\\": true
|
||||
}
|
||||
]",
|
||||
]
|
||||
`;
|
@ -1,7 +0,0 @@
|
||||
# Static Data
|
||||
|
||||
<script setup lang="ts">
|
||||
import { data } from './static.data.js'
|
||||
</script>
|
||||
|
||||
{{ data }}
|
@ -1,12 +0,0 @@
|
||||
describe('static data file support in vite 3', () => {
|
||||
beforeAll(async () => {
|
||||
await goto('/static-data/data')
|
||||
})
|
||||
|
||||
test('render correct content', async () => {
|
||||
const pLocator = page.locator('.VPContent p')
|
||||
|
||||
const pContents = await pLocator.allTextContents()
|
||||
expect(pContents).toMatchSnapshot()
|
||||
})
|
||||
})
|
@ -0,0 +1,140 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import glob from 'fast-glob'
|
||||
import type { SiteConfig } from './config'
|
||||
import matter from 'gray-matter'
|
||||
import { normalizePath } from 'vite'
|
||||
import { createMarkdownRenderer, type MarkdownRenderer } from './markdown'
|
||||
|
||||
export interface ContentOptions<T = ContentData[]> {
|
||||
/**
|
||||
* Include src?
|
||||
* default: false
|
||||
*/
|
||||
includeSrc?: boolean
|
||||
/**
|
||||
* Render src to HTML and include in data?
|
||||
* default: false
|
||||
*/
|
||||
render?: boolean
|
||||
/**
|
||||
* Whether to parse and include excerpt (rendered as HTML)
|
||||
* default: false
|
||||
*/
|
||||
excerpt?: boolean
|
||||
/**
|
||||
* 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>
|
||||
}
|
||||
|
||||
export interface ContentData {
|
||||
url: string
|
||||
src: string | undefined
|
||||
html: string | undefined
|
||||
frontmatter: Record<string, any>
|
||||
excerpt: string | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a loader object that can be directly used as the default export
|
||||
* of a data loader file.
|
||||
*/
|
||||
export function createContentLoader<T = ContentData[]>(
|
||||
/**
|
||||
* files to glob / watch - relative to <project root>
|
||||
*/
|
||||
pattern: string | string[],
|
||||
{
|
||||
includeSrc,
|
||||
render,
|
||||
excerpt: renderExcerpt,
|
||||
transform
|
||||
}: ContentOptions<T> = {}
|
||||
): {
|
||||
watch: string | string[]
|
||||
load: () => Promise<T>
|
||||
} {
|
||||
const config: SiteConfig = (global as any).VITEPRESS_CONFIG
|
||||
if (!config) {
|
||||
throw new Error(
|
||||
'content loader invoked without an active vitepress process, ' +
|
||||
'or before vitepress config is resolved.'
|
||||
)
|
||||
}
|
||||
|
||||
if (typeof pattern === 'string') pattern = [pattern]
|
||||
pattern = pattern.map((p) => normalizePath(path.join(config.root, p)))
|
||||
|
||||
let md: MarkdownRenderer
|
||||
|
||||
const cache = new Map<
|
||||
string,
|
||||
{
|
||||
data: any
|
||||
timestamp: number
|
||||
}
|
||||
>()
|
||||
|
||||
return {
|
||||
watch: pattern,
|
||||
async load(files?: string[]) {
|
||||
if (!files) {
|
||||
// the loader is being called directly, do a fresh glob
|
||||
files = (
|
||||
await glob(pattern, {
|
||||
ignore: ['**/node_modules/**', '**/dist/**']
|
||||
})
|
||||
).sort()
|
||||
}
|
||||
|
||||
md =
|
||||
md ||
|
||||
(await createMarkdownRenderer(
|
||||
config.srcDir,
|
||||
config.markdown,
|
||||
config.site.base,
|
||||
config.logger
|
||||
))
|
||||
|
||||
const raw: ContentData[] = []
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.md')) {
|
||||
continue
|
||||
}
|
||||
const timestamp = fs.statSync(file).mtimeMs
|
||||
const cached = cache.get(file)
|
||||
if (cached && timestamp === cached.timestamp) {
|
||||
raw.push(cached.data)
|
||||
} else {
|
||||
const src = fs.readFileSync(file, 'utf-8')
|
||||
const { data: frontmatter, excerpt } = matter(src, {
|
||||
excerpt: true
|
||||
})
|
||||
const url =
|
||||
'/' +
|
||||
normalizePath(path.relative(config.root, file)).replace(
|
||||
/\.md$/,
|
||||
config.cleanUrls ? '' : '.html'
|
||||
)
|
||||
const html = render ? md.render(src) : undefined
|
||||
const renderedExcerpt = renderExcerpt
|
||||
? excerpt && md.render(excerpt)
|
||||
: undefined
|
||||
const data: ContentData = {
|
||||
src: includeSrc ? src : undefined,
|
||||
html,
|
||||
frontmatter,
|
||||
excerpt: renderedExcerpt,
|
||||
url
|
||||
}
|
||||
cache.set(file, { data, timestamp })
|
||||
raw.push(data)
|
||||
}
|
||||
}
|
||||
return (transform ? transform(raw) : raw) as any
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue