mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
MDX: Use custom util to parse frontmatter
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-mdx': patch
|
||||
---
|
||||
|
||||
Use custom util to parse frontmatter
|
||||
@@ -54,7 +54,6 @@
|
||||
"chokidar": "^4.0.3",
|
||||
"esbuild": "^0.25.5",
|
||||
"estree-util-value-to-estree": "^3.4.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lru-cache": "^11.1.0",
|
||||
"picocolors": "^1.1.1",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as path from 'node:path';
|
||||
import { parse } from 'node:querystring';
|
||||
import grayMatter from 'gray-matter';
|
||||
import { type LoaderContext } from 'webpack';
|
||||
import { getConfigHash, loadConfig, type LoadedConfig } from '@/utils/config';
|
||||
import { buildMDX } from '@/utils/build-mdx';
|
||||
import { getGitTimestamp } from './utils/git-timestamp';
|
||||
import { validate, ValidationError } from '@/utils/schema';
|
||||
import { fumaMatter } from '@/utils/fuma-matter';
|
||||
|
||||
export interface Options {
|
||||
configPath: string;
|
||||
@@ -26,7 +26,7 @@ export default async function loader(
|
||||
const context = this.context;
|
||||
const filePath = this.resourcePath;
|
||||
const { configPath, outDir } = this.getOptions();
|
||||
const matter = grayMatter(source);
|
||||
const matter = fumaMatter(source);
|
||||
|
||||
// notice that `resourceQuery` can be missing (e.g. `page.mdx`)
|
||||
const {
|
||||
@@ -48,12 +48,13 @@ export default async function loader(
|
||||
collection = undefined;
|
||||
}
|
||||
|
||||
let data = matter.data;
|
||||
const mdxOptions =
|
||||
collection?.mdxOptions ?? (await loadDefaultOptions(config));
|
||||
|
||||
if (collection?.schema) {
|
||||
try {
|
||||
matter.data = (await validate(
|
||||
data = await validate(
|
||||
collection.schema,
|
||||
matter.data,
|
||||
{
|
||||
@@ -61,7 +62,7 @@ export default async function loader(
|
||||
path: filePath,
|
||||
},
|
||||
`invalid frontmatter in ${filePath}`,
|
||||
)) as Record<string, unknown>;
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) {
|
||||
return callback(new Error(e.toStringFormatted()));
|
||||
@@ -89,7 +90,7 @@ export default async function loader(
|
||||
development: this.mode === 'development',
|
||||
...mdxOptions,
|
||||
filePath,
|
||||
frontmatter: matter.data,
|
||||
frontmatter: data as Record<string, unknown>,
|
||||
data: {
|
||||
lastModified: timestamp,
|
||||
},
|
||||
|
||||
@@ -7,10 +7,10 @@ import type { LoadedConfig } from '@/utils/config';
|
||||
import type { DocCollection, MetaCollection } from '@/config';
|
||||
import { validate } from '@/utils/schema';
|
||||
import { fileCache } from '@/map/file-cache';
|
||||
import matter from 'gray-matter';
|
||||
import type { AsyncRuntimeFile } from '@/runtime/types';
|
||||
import { load } from 'js-yaml';
|
||||
import { getGitTimestamp } from '@/utils/git-timestamp';
|
||||
import { fumaMatter } from '@/utils/fuma-matter';
|
||||
|
||||
async function readFileWithCache(file: string): Promise<string> {
|
||||
const cached = fileCache.read<string>('read-file', file);
|
||||
@@ -102,17 +102,18 @@ export async function generateJS(
|
||||
}
|
||||
|
||||
const entries = files.map(async (file) => {
|
||||
const parsed = matter(
|
||||
const parsed = fumaMatter(
|
||||
await readFileWithCache(file.absolutePath).catch(() => ''),
|
||||
);
|
||||
let data = parsed.data;
|
||||
|
||||
if (collection.schema) {
|
||||
parsed.data = (await validate(
|
||||
data = await validate(
|
||||
collection.schema,
|
||||
parsed.data,
|
||||
{ path: file.absolutePath, source: parsed.content },
|
||||
`invalid frontmatter in ${file.absolutePath}`,
|
||||
)) as Record<string, unknown>;
|
||||
);
|
||||
}
|
||||
|
||||
let lastModified: Date | undefined;
|
||||
@@ -123,7 +124,7 @@ export async function generateJS(
|
||||
return JSON.stringify({
|
||||
info: file,
|
||||
lastModified,
|
||||
data: parsed.data,
|
||||
data: data as Record<string, unknown>,
|
||||
content: parsed.content,
|
||||
} satisfies AsyncRuntimeFile);
|
||||
});
|
||||
|
||||
@@ -3,8 +3,8 @@ import { visit } from 'unist-util-visit';
|
||||
import type { Code, Root, RootContent } from 'mdast';
|
||||
import * as path from 'node:path';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import matter from 'gray-matter';
|
||||
import type { CompilerOptions } from '@/utils/build-mdx';
|
||||
import { fumaMatter } from '@/utils/fuma-matter';
|
||||
|
||||
function flattenNode(node: RootContent): string {
|
||||
if ('children' in node)
|
||||
@@ -67,6 +67,7 @@ export function remarkInclude(this: Processor): Transformer<Root, Root> {
|
||||
queue.push(
|
||||
fs
|
||||
.readFile(targetPath)
|
||||
.then((buffer) => buffer.toString())
|
||||
.then(async (content) => {
|
||||
compiler?.addDependency(targetPath);
|
||||
|
||||
@@ -83,7 +84,7 @@ export function remarkInclude(this: Processor): Transformer<Root, Root> {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = processor.parse(matter(content).content);
|
||||
const parsed = processor.parse(fumaMatter(content).content);
|
||||
|
||||
await update(parsed as Root, targetPath, processor, compiler);
|
||||
Object.assign(
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Inspired by https://github.com/jonschlinkert/gray-matter
|
||||
*/
|
||||
import { LRUCache } from 'lru-cache';
|
||||
import { load } from 'js-yaml';
|
||||
|
||||
const cache = new LRUCache<string, Output>({
|
||||
max: 200,
|
||||
});
|
||||
|
||||
interface Output {
|
||||
matter: string;
|
||||
content: string;
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse frontmatter, it supports only yaml format
|
||||
*/
|
||||
export function fumaMatter(input: string): Output {
|
||||
if (input === '') {
|
||||
return { data: {}, content: input, matter: '' };
|
||||
}
|
||||
|
||||
const cached = cache.get(input);
|
||||
if (cached) return cached;
|
||||
const result = parseMatter(input);
|
||||
cache.set(input, result);
|
||||
|
||||
// avoid mutation
|
||||
return structuredClone(result);
|
||||
}
|
||||
|
||||
const delimiter = '---';
|
||||
|
||||
function parseMatter(str: string): Output {
|
||||
const output: Output = { matter: '', data: {}, content: str };
|
||||
const open = delimiter + '\n';
|
||||
const close = '\n' + delimiter;
|
||||
|
||||
if (!str.startsWith(open)) {
|
||||
return output;
|
||||
}
|
||||
|
||||
str = str.slice(open.length);
|
||||
const len = str.length;
|
||||
|
||||
let closeIdx = str.indexOf(close);
|
||||
if (closeIdx === -1) {
|
||||
closeIdx = len;
|
||||
}
|
||||
|
||||
// get the raw front-matter block
|
||||
output.matter = str.slice(0, closeIdx);
|
||||
output.content = str.slice(closeIdx + close.length);
|
||||
|
||||
const loaded = load(output.matter);
|
||||
output.data = loaded ?? {};
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -153,7 +153,7 @@ export function SearchDialogFooter(props: ComponentProps<'div'>) {
|
||||
<div
|
||||
{...props}
|
||||
className={cn(
|
||||
'border-t backdrop-brightness-130 p-3 empty:hidden',
|
||||
'border-t bg-fd-secondary/50 p-3 empty:hidden',
|
||||
props.className,
|
||||
)}
|
||||
/>
|
||||
|
||||
Generated
+3
-6
Reference in New Issue
Block a user