diff options
Diffstat (limited to 'packages/markdown/remark/src')
-rw-r--r-- | packages/markdown/remark/src/frontmatter-injection.ts | 34 | ||||
-rw-r--r-- | packages/markdown/remark/src/frontmatter.ts | 9 | ||||
-rw-r--r-- | packages/markdown/remark/src/index.ts | 33 | ||||
-rw-r--r-- | packages/markdown/remark/src/internal.ts | 1 | ||||
-rw-r--r-- | packages/markdown/remark/src/rehype-collect-headings.ts | 28 | ||||
-rw-r--r-- | packages/markdown/remark/src/rehype-images.ts | 6 | ||||
-rw-r--r-- | packages/markdown/remark/src/remark-collect-images.ts | 7 | ||||
-rw-r--r-- | packages/markdown/remark/src/types.ts | 24 |
8 files changed, 55 insertions, 87 deletions
diff --git a/packages/markdown/remark/src/frontmatter-injection.ts b/packages/markdown/remark/src/frontmatter-injection.ts deleted file mode 100644 index 91b98ebcb..000000000 --- a/packages/markdown/remark/src/frontmatter-injection.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { VFileData as Data, VFile } from 'vfile'; -import type { MarkdownAstroData } from './types.js'; - -function isValidAstroData(obj: unknown): obj is MarkdownAstroData { - if (typeof obj === 'object' && obj !== null && obj.hasOwnProperty('frontmatter')) { - const { frontmatter } = obj as any; - try { - // ensure frontmatter is JSON-serializable - JSON.stringify(frontmatter); - } catch { - return false; - } - return typeof frontmatter === 'object' && frontmatter !== null; - } - return false; -} - -export class InvalidAstroDataError extends TypeError {} - -export function safelyGetAstroData(vfileData: Data): MarkdownAstroData | InvalidAstroDataError { - const { astro } = vfileData; - - if (!astro || !isValidAstroData(astro)) { - return new InvalidAstroDataError(); - } - - return astro; -} - -export function setVfileFrontmatter(vfile: VFile, frontmatter: Record<string, any>) { - vfile.data ??= {}; - vfile.data.astro ??= {}; - (vfile.data.astro as any).frontmatter = frontmatter; -} diff --git a/packages/markdown/remark/src/frontmatter.ts b/packages/markdown/remark/src/frontmatter.ts new file mode 100644 index 000000000..d8828a6fe --- /dev/null +++ b/packages/markdown/remark/src/frontmatter.ts @@ -0,0 +1,9 @@ +export function isFrontmatterValid(frontmatter: Record<string, any>) { + try { + // ensure frontmatter is JSON-serializable + JSON.stringify(frontmatter); + } catch { + return false; + } + return typeof frontmatter === 'object' && frontmatter !== null; +} diff --git a/packages/markdown/remark/src/index.ts b/packages/markdown/remark/src/index.ts index 0ed2aaca1..ad52dc16f 100644 --- a/packages/markdown/remark/src/index.ts +++ b/packages/markdown/remark/src/index.ts @@ -1,10 +1,5 @@ -import type { AstroMarkdownOptions, MarkdownProcessor, MarkdownVFile } from './types.js'; +import type { AstroMarkdownOptions, MarkdownProcessor } from './types.js'; -import { - InvalidAstroDataError, - safelyGetAstroData, - setVfileFrontmatter, -} from './frontmatter-injection.js'; import { loadPlugins } from './load-plugins.js'; import { rehypeHeadingIds } from './rehype-collect-headings.js'; import { rehypePrism } from './rehype-prism.js'; @@ -21,11 +16,11 @@ import { unified } from 'unified'; import { VFile } from 'vfile'; import { rehypeImages } from './rehype-images.js'; -export { InvalidAstroDataError, setVfileFrontmatter } from './frontmatter-injection.js'; export { rehypeHeadingIds } from './rehype-collect-headings.js'; export { remarkCollectImages } from './remark-collect-images.js'; export { rehypePrism } from './rehype-prism.js'; export { rehypeShiki } from './rehype-shiki.js'; +export { isFrontmatterValid } from './frontmatter.js'; export { createShikiHighlighter, type ShikiHighlighter, @@ -128,10 +123,17 @@ export async function createMarkdownProcessor( return { async render(content, renderOpts) { - const vfile = new VFile({ value: content, path: renderOpts?.fileURL }); - setVfileFrontmatter(vfile, renderOpts?.frontmatter ?? {}); + const vfile = new VFile({ + value: content, + path: renderOpts?.fileURL, + data: { + astro: { + frontmatter: renderOpts?.frontmatter ?? {}, + }, + }, + }); - const result: MarkdownVFile = await parser.process(vfile).catch((err) => { + const result = await parser.process(vfile).catch((err) => { // Ensure that the error message contains the input filename // to make it easier for the user to fix the issue err = prefixError(err, `Failed to parse Markdown file "${vfile.path}"`); @@ -140,17 +142,12 @@ export async function createMarkdownProcessor( throw err; }); - const astroData = safelyGetAstroData(result.data); - if (astroData instanceof InvalidAstroDataError) { - throw astroData; - } - return { code: String(result.value), metadata: { - headings: result.data.__astroHeadings ?? [], - imagePaths: result.data.imagePaths ?? new Set(), - frontmatter: astroData.frontmatter ?? {}, + headings: result.data.astro?.headings ?? [], + imagePaths: result.data.astro?.imagePaths ?? [], + frontmatter: result.data.astro?.frontmatter ?? {}, }, }; }, diff --git a/packages/markdown/remark/src/internal.ts b/packages/markdown/remark/src/internal.ts deleted file mode 100644 index 6201ef62f..000000000 --- a/packages/markdown/remark/src/internal.ts +++ /dev/null @@ -1 +0,0 @@ -export { InvalidAstroDataError, safelyGetAstroData } from './frontmatter-injection.js'; diff --git a/packages/markdown/remark/src/rehype-collect-headings.ts b/packages/markdown/remark/src/rehype-collect-headings.ts index 05afae1ba..ab2113f49 100644 --- a/packages/markdown/remark/src/rehype-collect-headings.ts +++ b/packages/markdown/remark/src/rehype-collect-headings.ts @@ -3,19 +3,18 @@ import Slugger from 'github-slugger'; import type { MdxTextExpression } from 'mdast-util-mdx-expression'; import type { Node } from 'unist'; import { visit } from 'unist-util-visit'; - -import { InvalidAstroDataError, safelyGetAstroData } from './frontmatter-injection.js'; -import type { MarkdownAstroData, MarkdownHeading, MarkdownVFile, RehypePlugin } from './types.js'; +import type { VFile } from 'vfile'; +import type { MarkdownHeading, RehypePlugin } from './types.js'; const rawNodeTypes = new Set(['text', 'raw', 'mdxTextExpression']); const codeTagNames = new Set(['code', 'pre']); export function rehypeHeadingIds(): ReturnType<RehypePlugin> { - return function (tree, file: MarkdownVFile) { + return function (tree, file) { const headings: MarkdownHeading[] = []; + const frontmatter = file.data.astro?.frontmatter; const slugger = new Slugger(); const isMDX = isMDXFile(file); - const astroData = safelyGetAstroData(file.data); visit(tree, (node) => { if (node.type !== 'element') return; const { tagName } = node; @@ -37,10 +36,13 @@ export function rehypeHeadingIds(): ReturnType<RehypePlugin> { if (rawNodeTypes.has(child.type)) { if (isMDX || codeTagNames.has(parent.tagName)) { let value = child.value; - if (isMdxTextExpression(child) && !(astroData instanceof InvalidAstroDataError)) { + if (isMdxTextExpression(child) && frontmatter) { const frontmatterPath = getMdxFrontmatterVariablePath(child); if (Array.isArray(frontmatterPath) && frontmatterPath.length > 0) { - const frontmatterValue = getMdxFrontmatterVariableValue(astroData, frontmatterPath); + const frontmatterValue = getMdxFrontmatterVariableValue( + frontmatter, + frontmatterPath, + ); if (typeof frontmatterValue === 'string') { value = frontmatterValue; } @@ -65,11 +67,12 @@ export function rehypeHeadingIds(): ReturnType<RehypePlugin> { headings.push({ depth, slug: node.properties.id, text }); }); - file.data.__astroHeadings = headings; + file.data.astro ??= {}; + file.data.astro.headings = headings; }; } -function isMDXFile(file: MarkdownVFile) { +function isMDXFile(file: VFile) { return Boolean(file.history[0]?.endsWith('.mdx')); } @@ -109,8 +112,8 @@ function getMdxFrontmatterVariablePath(node: MdxTextExpression): string[] | Erro return expressionPath.reverse(); } -function getMdxFrontmatterVariableValue(astroData: MarkdownAstroData, path: string[]) { - let value: MdxFrontmatterVariableValue = astroData.frontmatter; +function getMdxFrontmatterVariableValue(frontmatter: Record<string, any>, path: string[]) { + let value = frontmatter; for (const key of path) { if (!value[key]) return undefined; @@ -124,6 +127,3 @@ function getMdxFrontmatterVariableValue(astroData: MarkdownAstroData, path: stri function isMdxTextExpression(node: Node): node is MdxTextExpression { return node.type === 'mdxTextExpression'; } - -type MdxFrontmatterVariableValue = - MarkdownAstroData['frontmatter'][keyof MarkdownAstroData['frontmatter']]; diff --git a/packages/markdown/remark/src/rehype-images.ts b/packages/markdown/remark/src/rehype-images.ts index 01e5aa6d6..11d33df9c 100644 --- a/packages/markdown/remark/src/rehype-images.ts +++ b/packages/markdown/remark/src/rehype-images.ts @@ -1,9 +1,9 @@ import { visit } from 'unist-util-visit'; -import type { MarkdownVFile } from './types.js'; +import type { VFile } from 'vfile'; export function rehypeImages() { return () => - function (tree: any, file: MarkdownVFile) { + function (tree: any, file: VFile) { const imageOccurrenceMap = new Map(); visit(tree, (node) => { @@ -13,7 +13,7 @@ export function rehypeImages() { if (node.properties?.src) { node.properties.src = decodeURI(node.properties.src); - if (file.data.imagePaths?.has(node.properties.src)) { + if (file.data.astro?.imagePaths?.includes(node.properties.src)) { const { ...props } = node.properties; // Initialize or increment occurrence count for this image diff --git a/packages/markdown/remark/src/remark-collect-images.ts b/packages/markdown/remark/src/remark-collect-images.ts index 22774d5f1..f09f1c580 100644 --- a/packages/markdown/remark/src/remark-collect-images.ts +++ b/packages/markdown/remark/src/remark-collect-images.ts @@ -1,10 +1,10 @@ import type { Image, ImageReference } from 'mdast'; import { definitions } from 'mdast-util-definitions'; import { visit } from 'unist-util-visit'; -import type { MarkdownVFile } from './types.js'; +import type { VFile } from 'vfile'; export function remarkCollectImages() { - return function (tree: any, vfile: MarkdownVFile) { + return function (tree: any, vfile: VFile) { if (typeof vfile?.path !== 'string') return; const definition = definitions(tree); @@ -22,7 +22,8 @@ export function remarkCollectImages() { } }); - vfile.data.imagePaths = imagePaths; + vfile.data.astro ??= {}; + vfile.data.astro.imagePaths = Array.from(imagePaths); }; } diff --git a/packages/markdown/remark/src/types.ts b/packages/markdown/remark/src/types.ts index 4a1263e50..6134bdf8a 100644 --- a/packages/markdown/remark/src/types.ts +++ b/packages/markdown/remark/src/types.ts @@ -3,14 +3,19 @@ import type * as mdast from 'mdast'; import type { Options as RemarkRehypeOptions } from 'remark-rehype'; import type { BuiltinTheme } from 'shiki'; import type * as unified from 'unified'; -import type { DataMap, VFile } from 'vfile'; import type { CreateShikiHighlighterOptions, ShikiHighlighterHighlightOptions } from './shiki.js'; export type { Node } from 'unist'; -export type MarkdownAstroData = { - frontmatter: Record<string, any>; -}; +declare module 'vfile' { + interface DataMap { + astro: { + headings?: MarkdownHeading[]; + imagePaths?: string[]; + frontmatter?: Record<string, any>; + }; + } +} export type RemarkPlugin<PluginParameters extends any[] = any[]> = unified.Plugin< PluginParameters, @@ -62,7 +67,7 @@ export interface MarkdownProcessorRenderResult { code: string; metadata: { headings: MarkdownHeading[]; - imagePaths: Set<string>; + imagePaths: string[]; frontmatter: Record<string, any>; }; } @@ -72,12 +77,3 @@ export interface MarkdownHeading { slug: string; text: string; } - -// TODO: Remove `MarkdownVFile` and move all additional properties to `DataMap` instead -export interface MarkdownVFile extends VFile { - data: Record<string, unknown> & - Partial<DataMap> & { - __astroHeadings?: MarkdownHeading[]; - imagePaths?: Set<string>; - }; -} |