mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
MDX: Support reusing codeblocks in <include>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-mdx': patch
|
||||
---
|
||||
|
||||
Support reusing codeblocks in `<include>`
|
||||
@@ -1,14 +1,38 @@
|
||||
---
|
||||
title: Include
|
||||
description: Reuse content from other Markdown files
|
||||
description: Reuse content from other files.
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Specify the target Markdown file path in `<include>` tag (relative).
|
||||
### Markdown
|
||||
|
||||
Specify the target Markdown file path in `<include>` tag (relative to the Markdown file itself).
|
||||
|
||||
```mdx title="page.mdx"
|
||||
<include>./another.mdx</include>
|
||||
```
|
||||
|
||||
This will display the content from target file (e.g. `another.mdx`).
|
||||
|
||||
### CodeBlock
|
||||
|
||||
For other types of files, it will become a codeblock:
|
||||
|
||||
```mdx title="page.mdx"
|
||||
<include>./script.ts</include>
|
||||
|
||||
<include lang="tsx" meta='title="lib.ts"'>
|
||||
./script.ts
|
||||
</include>
|
||||
```
|
||||
|
||||
### `cwd`
|
||||
|
||||
Resolve relative paths from cwd instead of Markdown file:
|
||||
|
||||
```mdx
|
||||
<include cwd lang="tsx" meta='title="lib.ts"'>
|
||||
./script.ts
|
||||
</include>
|
||||
```
|
||||
|
||||
@@ -16,15 +16,9 @@ description: Support multiple languages in your documentation
|
||||
|
||||
Define the i18n configurations in a file, we will import it with `@/ilb/i18n` in this guide.
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "../../examples/i18n/lib/i18n.ts",
|
||||
"codeblock": {
|
||||
"lang": "ts",
|
||||
"meta": "title=\"lib/i18n.ts\""
|
||||
}
|
||||
}
|
||||
```
|
||||
<include cwd meta='title="lib/i18n.ts"'>
|
||||
../../examples/i18n/lib/i18n.ts
|
||||
</include>
|
||||
|
||||
Pass it to the source loader.
|
||||
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import type { Processor, Transformer } from 'unified';
|
||||
import { visit } from 'unist-util-visit';
|
||||
import type { Literal, Root } from 'mdast';
|
||||
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';
|
||||
|
||||
function flattenNode(node: RootContent): string {
|
||||
if ('children' in node)
|
||||
return node.children.map((child) => flattenNode(child)).join('');
|
||||
|
||||
if ('value' in node) return node.value;
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
export function remarkInclude(this: Processor): Transformer<Root, Root> {
|
||||
const TagName = 'include';
|
||||
|
||||
@@ -17,50 +26,56 @@ export function remarkInclude(this: Processor): Transformer<Root, Root> {
|
||||
) {
|
||||
const queue: Promise<void>[] = [];
|
||||
|
||||
visit(tree, ['mdxJsxFlowElement', 'paragraph'] as const, (node) => {
|
||||
visit(tree, 'mdxJsxFlowElement', (node) => {
|
||||
let specifier: string | undefined;
|
||||
const params: Record<string, string | null> = {};
|
||||
|
||||
// without MDX, parse from paragraph nodes
|
||||
if (node.type === 'paragraph' && node.children.length === 3) {
|
||||
const [open, content, closure] = node.children;
|
||||
if (node.name === TagName) {
|
||||
const value = flattenNode(node);
|
||||
|
||||
if (
|
||||
open.type === 'html' &&
|
||||
open.value === `<${TagName}>` &&
|
||||
content.type === 'text' &&
|
||||
closure.type === 'html' &&
|
||||
closure.value === `</${TagName}>`
|
||||
) {
|
||||
specifier = content.value.trim();
|
||||
}
|
||||
} else if (node.type === 'paragraph' && node.children.length === 1) {
|
||||
const child = node.children[0];
|
||||
|
||||
if (child.type === 'mdxJsxTextElement' && child.name === TagName) {
|
||||
const text = child.children.at(0);
|
||||
|
||||
if (text && text.type === 'text') {
|
||||
specifier = text.value;
|
||||
if (value.length > 0) {
|
||||
for (const attr of node.attributes) {
|
||||
if (
|
||||
attr.type === 'mdxJsxAttribute' &&
|
||||
(typeof attr.value === 'string' || attr.value === null)
|
||||
) {
|
||||
params[attr.name] = attr.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node.type === 'mdxJsxFlowElement' && node.name === TagName) {
|
||||
const child = node.children.at(0) as Literal | undefined;
|
||||
|
||||
if (child && child.type === 'text') {
|
||||
specifier = child.value;
|
||||
specifier = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!specifier) return;
|
||||
const targetPath = path.resolve(path.dirname(file), specifier);
|
||||
|
||||
const targetPath = path.resolve(
|
||||
'cwd' in params ? process.cwd() : path.dirname(file),
|
||||
specifier,
|
||||
);
|
||||
const asCode =
|
||||
params.lang ||
|
||||
(!specifier.endsWith('.md') && !specifier.endsWith('.mdx'));
|
||||
|
||||
queue.push(
|
||||
fs.readFile(targetPath).then(async (content) => {
|
||||
compiler?.addDependency(targetPath);
|
||||
|
||||
if (asCode) {
|
||||
const lang = params.lang ?? path.extname(specifier).slice(1);
|
||||
|
||||
Object.assign(node, {
|
||||
type: 'code',
|
||||
lang,
|
||||
meta: params.meta,
|
||||
value: content.toString(),
|
||||
data: {},
|
||||
} satisfies Code);
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = processor.parse(matter(content).content);
|
||||
|
||||
compiler?.addDependency(targetPath);
|
||||
await update(parsed as Root, targetPath, processor, compiler);
|
||||
Object.assign(node, parsed);
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user