mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
MDX: Support creating a separate processor when <include /> points to different Markdown format
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-mdx': patch
|
||||
---
|
||||
|
||||
Support creating a separate processor when `<include />` points to different Markdown format
|
||||
@@ -88,58 +88,22 @@ export function remarkImage({
|
||||
src: Source,
|
||||
node: Image,
|
||||
): Promise<MdxJsxFlowElement | undefined> {
|
||||
if ((src.type === 'url' && external) || !useImport) {
|
||||
const size = await getImageSize(src).catch((e) => {
|
||||
throw new Error(
|
||||
`[Remark Image] Failed obtain image size for ${node.url} (public directory configured as ${publicDir})`,
|
||||
{
|
||||
cause: e,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'mdxJsxFlowElement',
|
||||
name: 'img',
|
||||
attributes: [
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'alt',
|
||||
value: node.alt ?? 'image',
|
||||
},
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'src',
|
||||
// `src` doesn't support file paths, we can use `node.url` for files and let the underlying framework handle it
|
||||
value: src.type === 'url' ? src.url.toString() : node.url,
|
||||
},
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'width',
|
||||
value: size.width.toString(),
|
||||
},
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'height',
|
||||
value: size.height.toString(),
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (src.type === 'file') {
|
||||
if (src.type === 'file' && useImport) {
|
||||
// Unique variable name for the given static image URL
|
||||
const variableName = `__img${importsToInject.length}`;
|
||||
const hasBlur =
|
||||
placeholder === 'blur' &&
|
||||
VALID_BLUR_EXT.some((ext) => src.file.endsWith(ext));
|
||||
|
||||
if (!file.dirname) {
|
||||
throw new Error(
|
||||
'When `useImport` is enabled, you must specify `dirname` in the VFile passed to compiler.',
|
||||
);
|
||||
}
|
||||
|
||||
importsToInject.push({
|
||||
variableName,
|
||||
importPath: file.dirname
|
||||
? getImportPath(src.file, file.dirname)
|
||||
: node.url,
|
||||
importPath: getImportPath(src.file, file.dirname),
|
||||
});
|
||||
|
||||
const out: MdxJsxFlowElement = {
|
||||
@@ -185,6 +149,46 @@ export function remarkImage({
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
if (src.type !== 'url' || external) {
|
||||
const size = await getImageSize(src).catch((e) => {
|
||||
throw new Error(
|
||||
`[Remark Image] Failed obtain image size for ${node.url} (public directory configured as ${publicDir})`,
|
||||
{
|
||||
cause: e,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'mdxJsxFlowElement',
|
||||
name: 'img',
|
||||
attributes: [
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'alt',
|
||||
value: node.alt ?? 'image',
|
||||
},
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'src',
|
||||
// `src` doesn't support file paths, we can use `node.url` for files and let the underlying framework handle it
|
||||
value: src.type === 'url' ? src.url.toString() : node.url,
|
||||
},
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'width',
|
||||
value: size.width.toString(),
|
||||
},
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'height',
|
||||
value: size.height.toString(),
|
||||
},
|
||||
],
|
||||
children: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
visit(tree, 'image', (node) => {
|
||||
|
||||
@@ -57,19 +57,6 @@ test('Remark Admonition', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('Remark Image', async () => {
|
||||
const content = readFileSync(path.resolve(cwd, './fixtures/remark-image.md'));
|
||||
const processor = remark()
|
||||
.use(remarkImage, { publicDir: path.resolve(cwd, './fixtures') })
|
||||
.use(remarkMdx);
|
||||
|
||||
const result = await processor.run(processor.parse(content));
|
||||
|
||||
await expect(result).toMatchFileSnapshot(
|
||||
path.resolve(cwd, './fixtures/remark-image.output.json'),
|
||||
);
|
||||
});
|
||||
|
||||
test('Remark Steps', async () => {
|
||||
const content = await fs.readFile(
|
||||
path.resolve(cwd, './fixtures/remark-steps.md'),
|
||||
@@ -98,6 +85,18 @@ test('Remark Image: With Path', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('Remark Image: Missing Path', async () => {
|
||||
const file = path.resolve(cwd, './fixtures/remark-image.md');
|
||||
const content = readFileSync(file);
|
||||
const processor = remark()
|
||||
.use(remarkImage, { publicDir: path.resolve(cwd, './fixtures') })
|
||||
.use(remarkMdx);
|
||||
|
||||
await expect(() =>
|
||||
processor.run(processor.parse(content)),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: When \`useImport\` is enabled, you must specify \`dirname\` in the VFile passed to compiler.]`);
|
||||
});
|
||||
|
||||
test('Remark Image: Without Import', async () => {
|
||||
const content = readFileSync(path.resolve(cwd, './fixtures/remark-image.md'));
|
||||
const result = await remark()
|
||||
|
||||
@@ -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 type { CompilerOptions } from '@/utils/build-mdx';
|
||||
import { fumaMatter } from '@/utils/fuma-matter';
|
||||
import type { DataMap } from '@/utils/build-mdx';
|
||||
|
||||
function flattenNode(node: RootContent): string {
|
||||
if ('children' in node)
|
||||
@@ -53,10 +53,10 @@ export function remarkInclude(this: Processor): Transformer<Root, Root> {
|
||||
const TagName = 'include';
|
||||
|
||||
async function update(
|
||||
this: Processor,
|
||||
tree: Root,
|
||||
directory: string,
|
||||
processor: Processor,
|
||||
compiler?: CompilerOptions,
|
||||
data: DataMap,
|
||||
) {
|
||||
const queue: Promise<void>[] = [];
|
||||
|
||||
@@ -103,7 +103,7 @@ export function remarkInclude(this: Processor): Transformer<Root, Root> {
|
||||
.readFile(targetPath)
|
||||
.then((buffer) => buffer.toString())
|
||||
.then(async (content) => {
|
||||
compiler?.addDependency(targetPath);
|
||||
data._compiler?.addDependency(targetPath);
|
||||
|
||||
if (asCode) {
|
||||
const lang = params.lang ?? path.extname(file).slice(1);
|
||||
@@ -118,6 +118,12 @@ export function remarkInclude(this: Processor): Transformer<Root, Root> {
|
||||
return;
|
||||
}
|
||||
|
||||
const processor = data._processor
|
||||
? data._processor.getProcessor(
|
||||
targetPath.endsWith('.md') ? 'md' : 'mdx',
|
||||
)
|
||||
: this;
|
||||
|
||||
let parsed = processor.parse(fumaMatter(content).content) as Root;
|
||||
if (section) {
|
||||
const extracted = extractSection(parsed, section);
|
||||
@@ -129,11 +135,11 @@ export function remarkInclude(this: Processor): Transformer<Root, Root> {
|
||||
parsed = extracted;
|
||||
}
|
||||
|
||||
await update(
|
||||
await update.call(
|
||||
processor as any,
|
||||
parsed,
|
||||
path.dirname(targetPath),
|
||||
processor,
|
||||
compiler,
|
||||
data,
|
||||
);
|
||||
Object.assign(
|
||||
parent && parent.type === 'paragraph' ? parent : node,
|
||||
@@ -156,6 +162,6 @@ export function remarkInclude(this: Processor): Transformer<Root, Root> {
|
||||
}
|
||||
|
||||
return async (tree, file) => {
|
||||
await update(tree, path.dirname(file.path), this, file.data._compiler);
|
||||
await update.call(this, tree, path.dirname(file.path), file.data);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createProcessor, type ProcessorOptions } from '@mdx-js/mdx';
|
||||
import type { VFile } from 'vfile';
|
||||
import type { DataMap, VFile } from 'vfile';
|
||||
import { remarkInclude } from '@/mdx-plugins/remark-include';
|
||||
|
||||
type Processor = ReturnType<typeof createProcessor>;
|
||||
@@ -10,7 +10,7 @@ interface BuildMDXOptions extends ProcessorOptions {
|
||||
/**
|
||||
* Specify a file path for source
|
||||
*/
|
||||
filePath?: string;
|
||||
filePath: string;
|
||||
|
||||
frontmatter?: Record<string, unknown>;
|
||||
|
||||
@@ -26,12 +26,18 @@ export interface CompilerOptions {
|
||||
addDependency: (file: string) => void;
|
||||
}
|
||||
|
||||
export type { DataMap };
|
||||
|
||||
declare module 'vfile' {
|
||||
interface DataMap {
|
||||
/**
|
||||
* The compiler object from loader
|
||||
*/
|
||||
_compiler?: CompilerOptions;
|
||||
|
||||
_processor?: {
|
||||
getProcessor: (format: 'md' | 'mdx') => Processor;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,33 +52,37 @@ export async function buildMDX(
|
||||
options: BuildMDXOptions,
|
||||
): Promise<VFile> {
|
||||
const { filePath, frontmatter, data, _compiler, ...rest } = options;
|
||||
let format = options.format;
|
||||
if (filePath) {
|
||||
format ??= filePath.endsWith('.mdx') ? 'mdx' : 'md';
|
||||
|
||||
function getProcessor(format: 'md' | 'mdx') {
|
||||
const key = `${cacheKey}:${format}`;
|
||||
let processor = cache.get(key);
|
||||
|
||||
if (!processor) {
|
||||
processor = createProcessor({
|
||||
outputFormat: 'program',
|
||||
...rest,
|
||||
remarkPlugins: [remarkInclude, ...(rest.remarkPlugins ?? [])],
|
||||
format,
|
||||
});
|
||||
|
||||
cache.set(key, processor);
|
||||
}
|
||||
|
||||
return processor;
|
||||
}
|
||||
|
||||
format ??= 'mdx';
|
||||
const key = `${cacheKey}:${format}`;
|
||||
let cached = cache.get(key);
|
||||
|
||||
if (!cached) {
|
||||
cached = createProcessor({
|
||||
outputFormat: 'program',
|
||||
...rest,
|
||||
remarkPlugins: [remarkInclude, ...(rest.remarkPlugins ?? [])],
|
||||
format,
|
||||
});
|
||||
|
||||
cache.set(key, cached);
|
||||
}
|
||||
|
||||
return cached.process({
|
||||
return getProcessor(
|
||||
(options.format ?? filePath.endsWith('.mdx')) ? 'mdx' : 'md',
|
||||
).process({
|
||||
value: source,
|
||||
path: filePath,
|
||||
data: {
|
||||
...data,
|
||||
frontmatter,
|
||||
_compiler,
|
||||
_processor: {
|
||||
getProcessor,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user