From 9d9a37bcb6a377a037bb80a18e56a18302c52440 Mon Sep 17 00:00:00 2001 From: lllleolin-max Date: Mon, 14 Sep 2026 02:04:23 +0800 Subject: [PATCH] fix(markdown): preserve page data when injecting scripts (#5440) --- .../unit/node/markdownToVue-pageData.test.ts | 73 +++++++++++++++++++ src/node/markdownToVue.ts | 6 +- 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 __tests__/unit/node/markdownToVue-pageData.test.ts diff --git a/__tests__/unit/node/markdownToVue-pageData.test.ts b/__tests__/unit/node/markdownToVue-pageData.test.ts new file mode 100644 index 000000000..8dcf09a31 --- /dev/null +++ b/__tests__/unit/node/markdownToVue-pageData.test.ts @@ -0,0 +1,73 @@ +import { mkdtemp, rm, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import path from 'node:path' + +import { resolveConfig } from 'node/config' +import { disposeMdItInstance } from 'node/markdown/markdown' +import { createMarkdownToVueRenderFn } from 'node/markdownToVue' +import { compileScript, parse } from 'vue/compiler-sfc' + +describe('page data script serialization', () => { + let root: string + let file: string + let render: Awaited> + + beforeAll(async () => { + root = await mkdtemp(path.join(tmpdir(), 'vitepress-page-data-')) + file = path.join(root, 'index.md') + await writeFile(file, '# Page\n') + const siteConfig = await resolveConfig(root, 'build', 'production') + disposeMdItInstance() + render = await createMarkdownToVueRenderFn( + root, + { cache: false }, + '/', + false, + false, + siteConfig + ) + }) + + afterAll(async () => { + disposeMdItInstance() + await rm(root, { recursive: true, force: true }) + }) + + describe.each([ + ['no script', ''], + ['named export', ''], + [ + 'default export', + '' + ], + ['script setup', ''] + ])('%s', (_, script) => { + test.each([ + 'Document tags literally', + "Regular-expression substitutions: $&, $`, $' and $$" + ])('preserves %j', async (description) => { + const src = [ + '---', + `description: ${JSON.stringify(description)}`, + '---', + '', + '# Page', + '', + script + ].join('\n') + + const result = await render(src, file) + const { descriptor, errors } = parse(result.vueSrc, { filename: file }) + + expect(errors).toEqual([]) + expect(() => compileScript(descriptor, { id: 'page-data' })).not.toThrow() + const module = await import( + 'data:text/javascript;base64,' + + Buffer.from(descriptor.script!.content).toString('base64') + ) + expect(module.__pageData).toEqual(result.pageData) + expect(module.__pageData.description).toBe(description) + expect(module.__pageData.frontmatter.description).toBe(description) + }) + }) +}) diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts index 661af7951..e21e27a32 100644 --- a/src/node/markdownToVue.ts +++ b/src/node/markdownToVue.ts @@ -322,9 +322,10 @@ export async function createMarkdownToVueRenderFn( } function injectPageDataCode(tags: string[], data: PageData) { + // Keep HTML closing tags in page data from terminating the SFC script. const code = `\nexport const __pageData = JSON.parse(${JSON.stringify( JSON.stringify(data) - )})` + ).replace(/ { return ( @@ -344,7 +345,8 @@ function injectPageDataCode(tags: string[], data: PageData) { defaultExportRE.test(tagSrc) || namedDefaultExportRE.test(tagSrc) tags[existingScriptIndex] = tagSrc.replace( scriptRE, - code + + () => + code + (hasDefaultExport ? `` : `\nexport default {name:${JSON.stringify(data.relativePath)}}`) +