summaryrefslogtreecommitdiff
path: root/packages/integrations/mdx/src/astro-data-utils.ts
blob: bfbc7446142d96851493900b787f4ac9a55ecd27 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import { name as isValidIdentifierName } from 'estree-util-is-identifier-name';
import type { VFile } from 'vfile';
import type { MdxjsEsm } from 'mdast-util-mdx';
import type { MarkdownAstroData } from 'astro';
import type { Data } from 'vfile';
import { jsToTreeNode } from './utils.js';

export function remarkInitializeAstroData() {
	return function (tree: any, vfile: VFile) {
		if (!vfile.data.astro) {
			vfile.data.astro = { frontmatter: {} };
		}
	};
}

export function rehypeApplyFrontmatterExport(
	pageFrontmatter: Record<string, any>,
	exportName = 'frontmatter'
) {
	return function (tree: any, vfile: VFile) {
		if (!isValidIdentifierName(exportName)) {
			throw new Error(
				`[MDX] ${JSON.stringify(
					exportName
				)} is not a valid frontmatter export name! Make sure "frontmatterOptions.name" could be used as a JS export (i.e. "export const frontmatterName = ...")`
			);
		}
		const { frontmatter: injectedFrontmatter } = safelyGetAstroData(vfile.data);
		const frontmatter = { ...injectedFrontmatter, ...pageFrontmatter };
		let exportNodes: MdxjsEsm[] = [];
		if (!exportName) {
			exportNodes = Object.entries(frontmatter).map(([k, v]) => {
				if (!isValidIdentifierName(k)) {
					throw new Error(
						`[MDX] A remark or rehype plugin tried to inject ${JSON.stringify(
							k
						)} as a top-level export, which is not a valid export name.`
					);
				}
				return jsToTreeNode(`export const ${k} = ${JSON.stringify(v)};`);
			});
		} else {
			exportNodes = [jsToTreeNode(`export const ${exportName} = ${JSON.stringify(frontmatter)};`)];
		}
		tree.children = exportNodes.concat(tree.children);
	};
}

/**
 * Copied from markdown utils
 * @see "vite-plugin-utils"
 */
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;
}

/**
 * Copied from markdown utils
 * @see "vite-plugin-utils"
 */
export function safelyGetAstroData(vfileData: Data): MarkdownAstroData {
	const { astro } = vfileData;

	if (!astro) return { frontmatter: {} };
	if (!isValidAstroData(astro)) {
		throw Error(
			`[MDX] A remark or rehype plugin tried to add invalid frontmatter. Ensure "astro.frontmatter" is a JSON object!`
		);
	}

	return astro;
}