mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
MDX: redesign MDX presets & improve performance
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'fumadocs-mdx': major
|
||||
---
|
||||
|
||||
**Replace `getDefaultMDXOptions()` with `applyMdxPreset()`**
|
||||
|
||||
This allows Fumadocs MDX to support more presets in the future, and adjust presets for dynamic mode.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
'fumadocs-mdx': major
|
||||
---
|
||||
|
||||
**No longer generate `extractedReferences` by default**
|
||||
|
||||
You can enable it from `postprocess` option.
|
||||
|
||||
```ts
|
||||
// source.config.ts
|
||||
import { defineDocs } from 'fumadocs-mdx/config';
|
||||
|
||||
export default defineDocs({
|
||||
docs: {
|
||||
postprocess: {
|
||||
extractLinkReferences: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -7,6 +7,9 @@ import type {
|
||||
MetaCollection,
|
||||
} from '@/config/define';
|
||||
import picomatch from 'picomatch';
|
||||
import { applyMdxPreset } from '@/config/preset';
|
||||
|
||||
export type BuildEnvironment = 'bundler' | 'runtime';
|
||||
|
||||
export interface LoadedConfig {
|
||||
collectionList: CollectionItem[];
|
||||
@@ -15,7 +18,7 @@ export interface LoadedConfig {
|
||||
global: GlobalConfig;
|
||||
getMDXOptions(
|
||||
collection?: DocCollectionItem,
|
||||
environment?: 'bundler' | 'runtime',
|
||||
environment?: BuildEnvironment,
|
||||
): ProcessorOptions | Promise<ProcessorOptions>;
|
||||
}
|
||||
|
||||
@@ -158,19 +161,15 @@ export function buildConfig(config: Record<string, unknown>): LoadedConfig {
|
||||
|
||||
if (collection?.mdxOptions) {
|
||||
const optionsFn = collection.mdxOptions;
|
||||
result = typeof optionsFn === 'function' ? optionsFn() : optionsFn;
|
||||
result =
|
||||
typeof optionsFn === 'function' ? optionsFn(environment) : optionsFn;
|
||||
} else {
|
||||
result = (async () => {
|
||||
const optionsFn = this.global.mdxOptions;
|
||||
const options =
|
||||
typeof optionsFn === 'function' ? await optionsFn() : optionsFn;
|
||||
|
||||
if (options?.preset === 'minimal') return options;
|
||||
const { getDefaultMDXOptions } = await import('@/loaders/mdx/preset');
|
||||
return getDefaultMDXOptions({
|
||||
...options,
|
||||
_withoutBundler: environment === 'runtime',
|
||||
});
|
||||
return applyMdxPreset(options)(environment);
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
||||
import type { DefaultMDXOptions } from '@/loaders/mdx/preset';
|
||||
import type { MDXPresetOptions } from '@/config/preset';
|
||||
import type { ProcessorOptions } from '@mdx-js/mdx';
|
||||
import { frontmatterSchema, metaSchema } from '@/config/zod-4';
|
||||
import type { PostprocessOptions } from '@/loaders/mdx/remark-postprocess';
|
||||
import type { PluginOption } from '@/core';
|
||||
import type { BuildEnvironment } from './build';
|
||||
|
||||
export type CollectionSchema<Schema extends StandardSchemaV1, Context> =
|
||||
| Schema
|
||||
@@ -39,7 +40,9 @@ export interface DocCollection<
|
||||
type: 'doc';
|
||||
|
||||
postprocess?: Partial<PostprocessOptions>;
|
||||
mdxOptions?: ProcessorOptions | (() => Promise<ProcessorOptions>);
|
||||
mdxOptions?:
|
||||
| ProcessorOptions
|
||||
| ((environment: BuildEnvironment) => Promise<ProcessorOptions>);
|
||||
|
||||
/**
|
||||
* Load files with async
|
||||
@@ -65,12 +68,6 @@ export interface DocsCollection<
|
||||
meta: MetaCollection<MetaSchema>;
|
||||
}
|
||||
|
||||
type GlobalConfigMDXOptions =
|
||||
| ({ preset?: 'fumadocs' } & DefaultMDXOptions)
|
||||
| ({
|
||||
preset: 'minimal';
|
||||
} & ProcessorOptions);
|
||||
|
||||
export interface GlobalConfig {
|
||||
collections?: Record<string, AnyCollection>;
|
||||
plugins?: PluginOption[];
|
||||
@@ -78,9 +75,9 @@ export interface GlobalConfig {
|
||||
/**
|
||||
* Configure global MDX options
|
||||
*
|
||||
* @remarks `GlobalConfigMDXOptions`
|
||||
* @remarks `MDXPresetOptions`
|
||||
*/
|
||||
mdxOptions?: GlobalConfigMDXOptions | (() => Promise<GlobalConfigMDXOptions>);
|
||||
mdxOptions?: MDXPresetOptions | (() => Promise<MDXPresetOptions>);
|
||||
|
||||
/**
|
||||
* specify a directory to access & store cache (disabled during development mode).
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
export * from './define';
|
||||
export {
|
||||
getDefaultMDXOptions,
|
||||
type DefaultMDXOptions,
|
||||
} from '@/loaders/mdx/preset';
|
||||
export * from '@/config/preset';
|
||||
export * from './zod-4';
|
||||
export { remarkInclude } from '@/loaders/mdx/remark-include';
|
||||
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import type { ProcessorOptions } from '@mdx-js/mdx';
|
||||
import type { Pluggable } from 'unified';
|
||||
import type * as Plugins from 'fumadocs-core/mdx-plugins';
|
||||
import type { BuildEnvironment } from './build';
|
||||
|
||||
type ResolvePlugins = Pluggable[] | ((v: Pluggable[]) => Pluggable[]);
|
||||
|
||||
export type DefaultMDXOptions = Omit<
|
||||
NonNullable<ProcessorOptions>,
|
||||
'rehypePlugins' | 'remarkPlugins' | '_ctx'
|
||||
> & {
|
||||
rehypePlugins?: ResolvePlugins;
|
||||
remarkPlugins?: ResolvePlugins;
|
||||
|
||||
/**
|
||||
* Properties to export from `vfile.data`
|
||||
*/
|
||||
valueToExport?: string[];
|
||||
|
||||
remarkStructureOptions?: Plugins.StructureOptions | false;
|
||||
remarkHeadingOptions?: Plugins.RemarkHeadingOptions;
|
||||
remarkImageOptions?: Plugins.RemarkImageOptions | false;
|
||||
remarkCodeTabOptions?: Plugins.RemarkCodeTabOptions | false;
|
||||
remarkNpmOptions?: Plugins.RemarkNpmOptions | false;
|
||||
rehypeCodeOptions?: Plugins.RehypeCodeOptions | false;
|
||||
};
|
||||
|
||||
function pluginOption(
|
||||
def: (v: Pluggable[]) => (Pluggable | false)[],
|
||||
options: ResolvePlugins = [],
|
||||
): Pluggable[] {
|
||||
const list = def(Array.isArray(options) ? options : []).filter(
|
||||
Boolean,
|
||||
) as Pluggable[];
|
||||
|
||||
if (typeof options === 'function') {
|
||||
return options(list);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
export type MDXPresetOptions =
|
||||
| ({ preset?: 'fumadocs' } & DefaultMDXOptions)
|
||||
| ({
|
||||
preset: 'minimal';
|
||||
} & ProcessorOptions);
|
||||
|
||||
/**
|
||||
* apply MDX processor presets
|
||||
*/
|
||||
export function applyMdxPreset(
|
||||
options: MDXPresetOptions = {},
|
||||
): (environment: BuildEnvironment) => Promise<ProcessorOptions> {
|
||||
return async (environment = 'bundler') => {
|
||||
if (options.preset === 'minimal') return options;
|
||||
|
||||
const plugins = await import('fumadocs-core/mdx-plugins');
|
||||
const {
|
||||
valueToExport = [],
|
||||
rehypeCodeOptions,
|
||||
remarkImageOptions,
|
||||
remarkHeadingOptions,
|
||||
remarkStructureOptions,
|
||||
remarkCodeTabOptions,
|
||||
remarkNpmOptions,
|
||||
...mdxOptions
|
||||
} = options;
|
||||
|
||||
const remarkPlugins = pluginOption(
|
||||
(v) => [
|
||||
plugins.remarkGfm,
|
||||
[
|
||||
plugins.remarkHeading,
|
||||
{
|
||||
generateToc: false,
|
||||
...remarkHeadingOptions,
|
||||
},
|
||||
],
|
||||
remarkImageOptions !== false && [
|
||||
plugins.remarkImage,
|
||||
{
|
||||
...remarkImageOptions,
|
||||
useImport:
|
||||
remarkImageOptions?.useImport ?? environment === 'bundler',
|
||||
},
|
||||
],
|
||||
'remarkCodeTab' in plugins &&
|
||||
remarkCodeTabOptions !== false && [
|
||||
plugins.remarkCodeTab,
|
||||
remarkCodeTabOptions,
|
||||
],
|
||||
'remarkNpm' in plugins &&
|
||||
remarkNpmOptions !== false && [plugins.remarkNpm, remarkNpmOptions],
|
||||
...v,
|
||||
remarkStructureOptions !== false && [
|
||||
plugins.remarkStructure,
|
||||
remarkStructureOptions,
|
||||
],
|
||||
valueToExport.length > 0 &&
|
||||
(() => {
|
||||
return (_, file) => {
|
||||
file.data['mdx-export'] ??= [];
|
||||
|
||||
for (const name of valueToExport) {
|
||||
if (!(name in file.data)) continue;
|
||||
|
||||
file.data['mdx-export'].push({
|
||||
name,
|
||||
value: file.data[name],
|
||||
});
|
||||
}
|
||||
};
|
||||
}),
|
||||
],
|
||||
mdxOptions.remarkPlugins,
|
||||
);
|
||||
|
||||
const rehypePlugins = pluginOption(
|
||||
(v) => [
|
||||
rehypeCodeOptions !== false && [plugins.rehypeCode, rehypeCodeOptions],
|
||||
...v,
|
||||
plugins.rehypeToc,
|
||||
],
|
||||
mdxOptions.rehypePlugins,
|
||||
);
|
||||
|
||||
return {
|
||||
...mdxOptions,
|
||||
outputFormat:
|
||||
environment === 'runtime' ? 'function-body' : mdxOptions.outputFormat,
|
||||
remarkPlugins,
|
||||
rehypePlugins,
|
||||
};
|
||||
};
|
||||
}
|
||||
+87
-51
@@ -24,6 +24,15 @@ export interface PluginContext extends CoreOptions {
|
||||
core: Core;
|
||||
}
|
||||
|
||||
export type CompilationContext<Collection> = PluginContext &
|
||||
TransformOptions<Collection>;
|
||||
|
||||
export interface TransformOptions<Collection> {
|
||||
collection: Collection;
|
||||
filePath: string;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface Plugin {
|
||||
name?: string;
|
||||
|
||||
@@ -48,26 +57,30 @@ export interface Plugin {
|
||||
server: ServerContext,
|
||||
) => Awaitable<void>;
|
||||
|
||||
/**
|
||||
* Transform frontmatter/metadata
|
||||
*/
|
||||
metadata?: (
|
||||
this: PluginContext & {
|
||||
collection: DocCollectionItem | MetaCollectionItem;
|
||||
filePath: string;
|
||||
},
|
||||
data: unknown,
|
||||
) => Awaitable<unknown | void>;
|
||||
meta?: {
|
||||
/**
|
||||
* Transform metadata
|
||||
*/
|
||||
transform?: (
|
||||
this: CompilationContext<MetaCollectionItem>,
|
||||
data: unknown,
|
||||
) => Awaitable<unknown | void>;
|
||||
};
|
||||
|
||||
doc?: {
|
||||
/**
|
||||
* Transform frontmatter
|
||||
*/
|
||||
frontmatter?: (
|
||||
this: CompilationContext<DocCollectionItem>,
|
||||
data: Record<string, unknown>,
|
||||
) => Awaitable<Record<string, unknown> | void>;
|
||||
|
||||
/**
|
||||
* Transform `vfile` on compilation stage
|
||||
*/
|
||||
vfile?: (
|
||||
this: PluginContext & {
|
||||
collection: DocCollectionItem;
|
||||
filePath: string;
|
||||
},
|
||||
this: CompilationContext<DocCollectionItem>,
|
||||
file: VFile,
|
||||
) => Awaitable<VFile | void>;
|
||||
};
|
||||
@@ -121,6 +134,28 @@ export function createCore(
|
||||
let config: LoadedConfig;
|
||||
let plugins: Plugin[];
|
||||
|
||||
async function transformMetadata<T>(
|
||||
{
|
||||
collection,
|
||||
filePath,
|
||||
source,
|
||||
}: TransformOptions<DocCollectionItem | MetaCollectionItem>,
|
||||
data: unknown,
|
||||
): Promise<T> {
|
||||
if (collection.schema) {
|
||||
data = await validate(
|
||||
collection.schema,
|
||||
data,
|
||||
{ path: filePath, source },
|
||||
collection.type === 'doc'
|
||||
? `invalid frontmatter in ${filePath}`
|
||||
: `invalid data in ${filePath}`,
|
||||
);
|
||||
}
|
||||
|
||||
return data as T;
|
||||
}
|
||||
|
||||
const core = {
|
||||
_options: options,
|
||||
/**
|
||||
@@ -187,55 +222,56 @@ export function createCore(
|
||||
);
|
||||
console.log(`[MDX] generated files in ${performance.now() - start}ms`);
|
||||
},
|
||||
async metadata(
|
||||
collection: DocCollectionItem | MetaCollectionItem,
|
||||
filePath: string,
|
||||
source: string,
|
||||
data: unknown,
|
||||
) {
|
||||
if (collection.schema) {
|
||||
data = await validate(
|
||||
collection.schema,
|
||||
data,
|
||||
{ path: filePath, source },
|
||||
collection.type === 'doc'
|
||||
? `invalid frontmatter in ${filePath}`
|
||||
: `invalid data in ${filePath}`,
|
||||
);
|
||||
}
|
||||
|
||||
async transformMeta(
|
||||
options: TransformOptions<MetaCollectionItem>,
|
||||
data: unknown,
|
||||
): Promise<unknown> {
|
||||
const ctx = {
|
||||
...pluginContext,
|
||||
filePath,
|
||||
collection,
|
||||
...options,
|
||||
};
|
||||
|
||||
data = await transformMetadata(options, data);
|
||||
for (const plugin of plugins) {
|
||||
if (plugin.metadata)
|
||||
data = (await plugin.metadata.call(ctx, data)) ?? data;
|
||||
if (plugin.meta?.transform)
|
||||
data = (await plugin.meta.transform.call(ctx, data)) ?? data;
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
doc: {
|
||||
async vfile(
|
||||
collection: DocCollectionItem,
|
||||
filePath: string,
|
||||
file: VFile,
|
||||
): Promise<VFile> {
|
||||
const ctx = {
|
||||
...pluginContext,
|
||||
filePath,
|
||||
collection,
|
||||
};
|
||||
async transformFrontmatter(
|
||||
options: TransformOptions<DocCollectionItem>,
|
||||
data: Record<string, unknown>,
|
||||
): Promise<Record<string, unknown>> {
|
||||
const ctx = {
|
||||
...pluginContext,
|
||||
...options,
|
||||
};
|
||||
|
||||
for (const plugin of plugins) {
|
||||
if (plugin.doc?.vfile)
|
||||
file = (await plugin.doc.vfile.call(ctx, file)) ?? file;
|
||||
}
|
||||
data = await transformMetadata(options, data);
|
||||
for (const plugin of plugins) {
|
||||
if (plugin.doc?.frontmatter)
|
||||
data = (await plugin.doc.frontmatter.call(ctx, data)) ?? data;
|
||||
}
|
||||
|
||||
return file;
|
||||
},
|
||||
return data;
|
||||
},
|
||||
async transformVFile(
|
||||
options: TransformOptions<DocCollectionItem>,
|
||||
file: VFile,
|
||||
): Promise<VFile> {
|
||||
const ctx = {
|
||||
...pluginContext,
|
||||
...options,
|
||||
};
|
||||
|
||||
for (const plugin of plugins) {
|
||||
if (plugin.doc?.vfile)
|
||||
file = (await plugin.doc.vfile.call(ctx, file)) ?? file;
|
||||
}
|
||||
|
||||
return file;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface CompilerOptions {
|
||||
addDependency: (file: string) => void;
|
||||
}
|
||||
|
||||
// TODO: allow plugins to customise the type
|
||||
export interface CompiledMDXProperties<Frontmatter = Record<string, unknown>> {
|
||||
frontmatter: Frontmatter;
|
||||
structuredData: StructuredData;
|
||||
@@ -42,8 +43,17 @@ export interface CompiledMDXProperties<Frontmatter = Record<string, unknown>> {
|
||||
* Added by the `last-modified` plugin.
|
||||
*/
|
||||
lastModified?: Date;
|
||||
/**
|
||||
* Enable from `postprocess` option.
|
||||
*/
|
||||
extractedReferences?: ExtractedReference[];
|
||||
/**
|
||||
* Enable from `postprocess` option.
|
||||
*/
|
||||
_markdown?: string;
|
||||
/**
|
||||
* Enable from `postprocess` option.
|
||||
*/
|
||||
_mdast?: string;
|
||||
}
|
||||
|
||||
@@ -58,19 +68,12 @@ export interface FumadocsDataMap {
|
||||
*/
|
||||
'mdx-export'?: { name: string; value: unknown }[];
|
||||
|
||||
extractedReferences: ExtractedReference[];
|
||||
|
||||
/**
|
||||
* [Fumadocs MDX] The compiler object from loader
|
||||
*/
|
||||
_compiler?: CompilerOptions;
|
||||
|
||||
_getProcessor?: (format: 'md' | 'mdx') => Processor;
|
||||
|
||||
/**
|
||||
* [Fumadocs MDX] Processed Markdown content before `remark-rehype`.
|
||||
*/
|
||||
_markdown?: string;
|
||||
}
|
||||
|
||||
declare module 'vfile' {
|
||||
@@ -106,11 +109,7 @@ export async function buildMDX(
|
||||
valueToExport: [
|
||||
...(collection?.postprocess?.valueToExport ?? []),
|
||||
'structuredData',
|
||||
'extractedReferences',
|
||||
'frontmatter',
|
||||
'lastModified',
|
||||
'_markdown',
|
||||
'_mdast',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -139,7 +138,7 @@ export async function buildMDX(
|
||||
});
|
||||
|
||||
if (collection) {
|
||||
vfile = await core.doc.vfile(collection, filePath, vfile);
|
||||
vfile = await core.transformVFile({ collection, filePath, source }, vfile);
|
||||
}
|
||||
|
||||
return getProcessor(filePath.endsWith('.mdx') ? 'mdx' : 'md').process(vfile);
|
||||
|
||||
@@ -78,11 +78,9 @@ export function createMdxLoader(configLoader: ConfigLoader): Loader {
|
||||
}
|
||||
|
||||
if (docCollection) {
|
||||
matter.data = await configLoader.core.metadata(
|
||||
docCollection,
|
||||
filePath,
|
||||
value,
|
||||
matter.data,
|
||||
matter.data = await configLoader.core.transformFrontmatter(
|
||||
{ collection: docCollection, filePath, source: value },
|
||||
matter.data as Record<string, unknown>,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
import * as plugins from 'fumadocs-core/mdx-plugins';
|
||||
import type { ProcessorOptions } from '@mdx-js/mdx';
|
||||
import type { Pluggable } from 'unified';
|
||||
|
||||
type ResolvePlugins = Pluggable[] | ((v: Pluggable[]) => Pluggable[]);
|
||||
|
||||
export type DefaultMDXOptions = Omit<
|
||||
NonNullable<ProcessorOptions>,
|
||||
'rehypePlugins' | 'remarkPlugins' | '_ctx'
|
||||
> & {
|
||||
rehypePlugins?: ResolvePlugins;
|
||||
remarkPlugins?: ResolvePlugins;
|
||||
|
||||
/**
|
||||
* Properties to export from `vfile.data`
|
||||
*/
|
||||
valueToExport?: string[];
|
||||
|
||||
remarkStructureOptions?: plugins.StructureOptions | false;
|
||||
remarkHeadingOptions?: plugins.RemarkHeadingOptions;
|
||||
remarkImageOptions?: plugins.RemarkImageOptions | false;
|
||||
remarkCodeTabOptions?: plugins.RemarkCodeTabOptions | false;
|
||||
remarkNpmOptions?: plugins.RemarkNpmOptions | false;
|
||||
rehypeCodeOptions?: plugins.RehypeCodeOptions | false;
|
||||
|
||||
_withoutBundler?: boolean;
|
||||
};
|
||||
|
||||
function pluginOption(
|
||||
def: (v: Pluggable[]) => (Pluggable | false)[],
|
||||
options: ResolvePlugins = [],
|
||||
): Pluggable[] {
|
||||
const list = def(Array.isArray(options) ? options : []).filter(
|
||||
Boolean,
|
||||
) as Pluggable[];
|
||||
|
||||
if (typeof options === 'function') {
|
||||
return options(list);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
export function getDefaultMDXOptions({
|
||||
valueToExport = [],
|
||||
rehypeCodeOptions,
|
||||
remarkImageOptions,
|
||||
remarkHeadingOptions,
|
||||
remarkStructureOptions,
|
||||
remarkCodeTabOptions,
|
||||
remarkNpmOptions,
|
||||
_withoutBundler = false,
|
||||
...mdxOptions
|
||||
}: DefaultMDXOptions): ProcessorOptions {
|
||||
const remarkPlugins = pluginOption(
|
||||
(v) => [
|
||||
plugins.remarkGfm,
|
||||
[
|
||||
plugins.remarkHeading,
|
||||
{
|
||||
generateToc: false,
|
||||
...remarkHeadingOptions,
|
||||
},
|
||||
],
|
||||
remarkImageOptions !== false && [
|
||||
plugins.remarkImage,
|
||||
{
|
||||
...remarkImageOptions,
|
||||
useImport: _withoutBundler ? false : remarkImageOptions?.useImport,
|
||||
},
|
||||
],
|
||||
'remarkCodeTab' in plugins &&
|
||||
remarkCodeTabOptions !== false && [
|
||||
plugins.remarkCodeTab,
|
||||
remarkCodeTabOptions,
|
||||
],
|
||||
'remarkNpm' in plugins &&
|
||||
remarkNpmOptions !== false && [plugins.remarkNpm, remarkNpmOptions],
|
||||
...v,
|
||||
remarkStructureOptions !== false && [
|
||||
plugins.remarkStructure,
|
||||
remarkStructureOptions,
|
||||
],
|
||||
() => {
|
||||
return (_, file) => {
|
||||
file.data['mdx-export'] ??= [];
|
||||
|
||||
for (const name of valueToExport) {
|
||||
if (name in file.data)
|
||||
file.data['mdx-export'].push({ name, value: file.data[name] });
|
||||
}
|
||||
};
|
||||
},
|
||||
],
|
||||
mdxOptions.remarkPlugins,
|
||||
);
|
||||
|
||||
const rehypePlugins = pluginOption(
|
||||
(v) => [
|
||||
rehypeCodeOptions !== false && [plugins.rehypeCode, rehypeCodeOptions],
|
||||
...v,
|
||||
plugins.rehypeToc,
|
||||
],
|
||||
mdxOptions.rehypePlugins,
|
||||
);
|
||||
|
||||
return {
|
||||
...mdxOptions,
|
||||
outputFormat: _withoutBundler ? 'function-body' : mdxOptions.outputFormat,
|
||||
remarkPlugins,
|
||||
rehypePlugins,
|
||||
};
|
||||
}
|
||||
@@ -23,6 +23,11 @@ export interface PostprocessOptions {
|
||||
*/
|
||||
includeProcessedMarkdown?: boolean;
|
||||
|
||||
/**
|
||||
* extract link references, export via `extractedReferences`.
|
||||
*/
|
||||
extractLinkReferences?: boolean;
|
||||
|
||||
/**
|
||||
* store MDAST and export via `_mdast`.
|
||||
*/
|
||||
@@ -43,65 +48,80 @@ export function remarkPostprocess(
|
||||
_format,
|
||||
includeProcessedMarkdown = false,
|
||||
includeMDAST = false,
|
||||
extractLinkReferences = false,
|
||||
valueToExport = [],
|
||||
}: PostprocessOptions,
|
||||
): Transformer<Root, Root> {
|
||||
let _stringifyProcessor: Processor | undefined;
|
||||
const getStringifyProcessor = () => {
|
||||
if (_format === 'mdx') return this;
|
||||
|
||||
// force Markdown processor to stringify MDX nodes
|
||||
return (_stringifyProcessor ??= this().use(remarkMdx).freeze());
|
||||
return (_stringifyProcessor ??=
|
||||
_format === 'mdx'
|
||||
? this
|
||||
: // force Markdown processor to stringify MDX nodes
|
||||
this().use(remarkMdx).freeze());
|
||||
};
|
||||
|
||||
return (tree, file) => {
|
||||
let title: string | undefined;
|
||||
const urls: ExtractedReference[] = [];
|
||||
|
||||
visit(tree, ['heading', 'link'], (node) => {
|
||||
if (node.type === 'heading' && node.depth === 1) {
|
||||
title = flattenNode(node);
|
||||
}
|
||||
|
||||
if (node.type !== 'link') return;
|
||||
|
||||
urls.push({
|
||||
href: node.url,
|
||||
file.data.frontmatter ??= {};
|
||||
if (!file.data.frontmatter.title) {
|
||||
visit(tree, 'heading', (node) => {
|
||||
if (node.depth === 1) {
|
||||
file.data.frontmatter!.title = flattenNode(node);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return 'skip';
|
||||
});
|
||||
|
||||
if (title) {
|
||||
file.data.frontmatter ??= {};
|
||||
|
||||
if (!file.data.frontmatter.title) file.data.frontmatter.title = title;
|
||||
}
|
||||
|
||||
file.data.extractedReferences = urls;
|
||||
file.data['mdx-export'] ??= [];
|
||||
if (extractLinkReferences) {
|
||||
const urls: ExtractedReference[] = [];
|
||||
|
||||
visit(tree, 'link', (node) => {
|
||||
urls.push({
|
||||
href: node.url,
|
||||
});
|
||||
return 'skip';
|
||||
});
|
||||
|
||||
file.data['mdx-export'].push({
|
||||
name: 'extractedReferences',
|
||||
value: urls,
|
||||
});
|
||||
}
|
||||
|
||||
if (includeProcessedMarkdown) {
|
||||
const processor = getStringifyProcessor();
|
||||
|
||||
file.data._markdown = toMarkdown(tree, {
|
||||
const markdown = toMarkdown(tree, {
|
||||
...processor.data('settings'),
|
||||
// from https://github.com/remarkjs/remark/blob/main/packages/remark-stringify/lib/index.js
|
||||
extensions: processor.data('toMarkdownExtensions') || [],
|
||||
});
|
||||
|
||||
file.data['mdx-export'].push({
|
||||
name: '_markdown',
|
||||
value: markdown,
|
||||
});
|
||||
}
|
||||
|
||||
if (includeMDAST) {
|
||||
const options = includeMDAST === true ? {} : includeMDAST;
|
||||
|
||||
file.data._mdast = JSON.stringify(
|
||||
const mdast = JSON.stringify(
|
||||
options.removePosition ? removePosition(structuredClone(tree)) : tree,
|
||||
);
|
||||
|
||||
file.data['mdx-export'].push({
|
||||
name: '_mdast',
|
||||
value: mdast,
|
||||
});
|
||||
}
|
||||
|
||||
for (const { name, value } of file.data['mdx-export'] ?? []) {
|
||||
for (const { name, value } of file.data['mdx-export']) {
|
||||
tree.children.unshift(getMdastExport(name, value));
|
||||
}
|
||||
|
||||
// reset the data to reduce memory usage
|
||||
file.data['mdx-export'] = [];
|
||||
|
||||
for (const name of valueToExport) {
|
||||
if (!(name in file.data)) continue;
|
||||
|
||||
|
||||
@@ -56,7 +56,14 @@ export function createMetaLoader(
|
||||
const data = parse(filePath, source);
|
||||
|
||||
if (!metaCollection) return data;
|
||||
return configLoader.core.metadata(metaCollection, filePath, source, data);
|
||||
return configLoader.core.transformMeta(
|
||||
{
|
||||
collection: metaCollection,
|
||||
filePath,
|
||||
source,
|
||||
},
|
||||
data,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -229,11 +229,13 @@ async function generateDynamicIndexFile(core: Core, codegen: CodeGen) {
|
||||
const fullPath = path.join(collection.dir, file);
|
||||
const content = await readFileWithCache(fullPath).catch(() => '');
|
||||
const parsed = fumaMatter(content);
|
||||
const data = await core.metadata(
|
||||
collection,
|
||||
fullPath,
|
||||
content,
|
||||
parsed.data,
|
||||
const data = await core.transformFrontmatter(
|
||||
{
|
||||
collection,
|
||||
filePath: fullPath,
|
||||
source: content,
|
||||
},
|
||||
parsed.data as Record<string, unknown>,
|
||||
);
|
||||
|
||||
const hash = createHash('md5').update(content).digest('hex');
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface LastModifiedPluginOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects `lastModified` property to page data.
|
||||
* Injects `lastModified` property to page exports.
|
||||
*
|
||||
* By default, it uses Git to obtain the last modified time.
|
||||
*/
|
||||
@@ -32,10 +32,16 @@ export default function lastModified(
|
||||
doc: {
|
||||
async vfile(file) {
|
||||
if (versionControl === 'git') {
|
||||
const timestamp = await getGitTimestamp(this.filePath);
|
||||
const timestamp = await getGitTimestamp(this.filePath).then((v) =>
|
||||
v?.getTime(),
|
||||
);
|
||||
if (timestamp === undefined) return;
|
||||
|
||||
file.data.lastModified = timestamp.getTime();
|
||||
file.data['mdx-export'] ??= [];
|
||||
file.data['mdx-export'].push({
|
||||
name: 'lastModified',
|
||||
value: timestamp,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user