Core: Improve AST plugins

This commit is contained in:
Fuma Nama
2026-04-14 16:49:44 +08:00
parent ccad791959
commit a4189ce685
16 changed files with 400 additions and 282 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-core': patch
---
Improve AST plugins
+101 -105
View File
@@ -1,10 +1,10 @@
import type { Processor, Transformer } from 'unified'; import type { Processor, Transformer } from 'unified';
import type { BlockContent, Code, DefinitionContent, Root } from 'mdast'; import type { BlockContent, Code, Root, RootContent } from 'mdast';
import { visit } from 'unist-util-visit'; import { visit } from 'unist-util-visit';
import type { MdxJsxFlowElement } from 'mdast-util-mdx'; import type { MdxJsxFlowElement } from 'mdast-util-mdx';
import { generateCodeBlockTabs, parseCodeBlockAttributes } from '@/mdx-plugins/codeblock-utils'; import { generateCodeBlockTabs, parseCodeBlockAttributes } from '@/mdx-plugins/codeblock-utils';
type TabType = keyof typeof Types; type TabType = 'CodeBlockTabs' | 'Tabs';
export interface RemarkCodeTabOptions { export interface RemarkCodeTabOptions {
Tabs?: TabType; Tabs?: TabType;
@@ -18,17 +18,54 @@ export interface RemarkCodeTabOptions {
declare module 'mdast' { declare module 'mdast' {
export interface CodeData { export interface CodeData {
/**
* [Fumadocs: remark-code-tab] the associated tab value
*/
tab?: string; tab?: string;
} }
export interface Data {
/**
* [Fumadocs: remark-code-tab] internal
*/
_code_tab_visited?: true;
}
} }
const Tabs = { type TabHandler = (
convert( processor: Processor,
processor: Processor, nodes: Code[],
nodes: Code[], withMdx: boolean,
withMdx = false, withParent: boolean,
withParent = true, ) => RootContent[];
): MdxJsxFlowElement {
const Types: Record<TabType, TabHandler> = {
CodeBlockTabs(processor, nodes, withMdx, withParent) {
const tabs = Array.from(processTabValue(nodes).entries());
const node = generateCodeBlockTabs({
defaultValue: tabs[0][0],
triggers: tabs.map(([name]) => ({
value: name,
children: [
withMdx
? (mdxToAst(processor, name) as unknown as BlockContent)
: {
type: 'text',
value: name,
},
],
})),
tabs: tabs.map(([name, codes]) => ({
value: name,
children: codes,
})),
});
if (!withParent) return node.children;
return [node];
},
Tabs(processor, nodes, withMdx, withParent) {
const tabs = Array.from(processTabValue(nodes).entries()); const tabs = Array.from(processTabValue(nodes).entries());
if (!withMdx) { if (!withMdx) {
@@ -47,42 +84,44 @@ const Tabs = {
}; };
}); });
if (!withParent) return createFragment(children); if (!withParent) return children;
return { return [
type: 'mdxJsxFlowElement', {
name: 'Tabs', type: 'mdxJsxFlowElement',
attributes: [ name: 'Tabs',
{ attributes: [
type: 'mdxJsxAttribute', {
name: 'items', type: 'mdxJsxAttribute',
value: { name: 'items',
type: 'mdxJsxAttributeValueExpression', value: {
value: tabs.map(([name]) => name).join(', '), type: 'mdxJsxAttributeValueExpression',
data: { value: tabs.map(([name]) => name).join(', '),
estree: { data: {
type: 'Program', estree: {
sourceType: 'module', type: 'Program',
comments: [], sourceType: 'module',
body: [ comments: [],
{ body: [
type: 'ExpressionStatement', {
expression: { type: 'ExpressionStatement',
type: 'ArrayExpression', expression: {
elements: tabs.map(([name]) => ({ type: 'ArrayExpression',
type: 'Literal', elements: tabs.map(([name]) => ({
value: name, type: 'Literal',
})), value: name,
})),
},
}, },
}, ],
], },
}, },
}, },
}, },
}, ],
], children,
children, },
}; ];
} }
const children: MdxJsxFlowElement[] = [ const children: MdxJsxFlowElement[] = [
@@ -120,61 +159,25 @@ const Tabs = {
), ),
]; ];
if (!withParent) return createFragment(children); if (!withParent) return children;
return { return [
type: 'mdxJsxFlowElement', {
name: 'Tabs', type: 'mdxJsxFlowElement',
attributes: [ name: 'Tabs',
{ attributes: [
type: 'mdxJsxAttribute', {
name: 'defaultValue', type: 'mdxJsxAttribute',
value: tabs[0][0], name: 'defaultValue',
}, value: tabs[0][0],
], },
children,
};
},
};
const CodeBlockTabs = {
convert(
processor: Processor,
nodes: Code[],
withMdx = false,
withParent = true,
): MdxJsxFlowElement {
const tabs = Array.from(processTabValue(nodes).entries());
const node = generateCodeBlockTabs({
defaultValue: tabs[0][0],
triggers: tabs.map(([name]) => ({
value: name,
children: [
withMdx
? (mdxToAst(processor, name) as unknown as BlockContent)
: {
type: 'text',
value: name,
},
], ],
})), children,
tabs: tabs.map(([name, codes]) => ({ },
value: name, ];
children: codes,
})),
});
if (!withParent) return createFragment(node.children);
return node;
}, },
}; };
const Types = {
CodeBlockTabs,
Tabs,
};
export function remarkCodeTab( export function remarkCodeTab(
this: Processor, this: Processor,
options: RemarkCodeTabOptions = {}, options: RemarkCodeTabOptions = {},
@@ -182,10 +185,8 @@ export function remarkCodeTab(
const { parseMdx = false, Tabs = 'CodeBlockTabs' } = options; const { parseMdx = false, Tabs = 'CodeBlockTabs' } = options;
return (tree) => { return (tree) => {
const ignored = new WeakSet();
visit(tree, (node) => { visit(tree, (node) => {
if (!('children' in node) || ignored.has(node)) return 'skip'; if (!('children' in node) || node.data?._code_tab_visited) return 'skip';
let localTabs: TabType = Tabs; let localTabs: TabType = Tabs;
let localParseMdx = parseMdx; let localParseMdx = parseMdx;
let withParent = true; let withParent = true;
@@ -206,15 +207,19 @@ export function remarkCodeTab(
let end = 0; let end = 0;
const close = () => { const close = () => {
if (start === -1 || start === end) return; if (start === -1 || start === end) return;
const replacement = Types[localTabs].convert( const replacement = Types[localTabs](
this, this,
node.children.slice(start, end) as Code[], node.children.slice(start, end) as Code[],
localParseMdx, localParseMdx,
withParent, withParent,
); );
ignored.add(replacement); for (const element of replacement) {
node.children.splice(start, end - start, replacement); element.data ??= {};
element.data._code_tab_visited = true;
}
node.children.splice(start, end - start, ...replacement);
end = start; end = start;
start = -1; start = -1;
}; };
@@ -272,12 +277,3 @@ function mdxToAst(processor: Processor, name: string) {
return node; return node;
} }
function createFragment(children: (BlockContent | DefinitionContent)[]): MdxJsxFlowElement {
return {
type: 'mdxJsxFlowElement',
name: null,
attributes: [],
children,
};
}
@@ -1,6 +1,6 @@
import type { BlockContent, DefinitionContent, PhrasingContent, Root } from 'mdast'; import type { BlockContent, DefinitionContent, PhrasingContent, Root } from 'mdast';
import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx';
import { Transformer } from 'unified'; import type { Transformer } from 'unified';
import { visit } from 'unist-util-visit'; import { visit } from 'unist-util-visit';
export interface RemarkDirectiveAdmonitionOptions { export interface RemarkDirectiveAdmonitionOptions {
+52 -76
View File
@@ -1,10 +1,10 @@
import * as path from 'node:path'; import * as path from 'node:path';
import type { Image, Root } from 'mdast'; import type { Image, Root, RootContent } from 'mdast';
import type { Transformer } from 'unified'; import type { Transformer } from 'unified';
import { visit } from 'unist-util-visit'; import { visit } from 'unist-util-visit';
import type { MdxjsEsm } from 'mdast-util-mdx'; import type { MdxjsEsm } from 'mdast-util-mdx';
import type { ISizeCalculationResult } from 'image-size/types/interface'; import type { ISizeCalculationResult } from 'image-size/types/interface';
import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx'; import type { MdxJsxFlowElement } from 'mdast-util-mdx';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
const VALID_BLUR_EXT = ['.jpeg', '.png', '.webp', '.avif', '.jpg']; const VALID_BLUR_EXT = ['.jpeg', '.png', '.webp', '.avif', '.jpg'];
@@ -90,23 +90,7 @@ export function remarkImage({
const importsToInject: { variableName: string; importPath: string }[] = []; const importsToInject: { variableName: string; importPath: string }[] = [];
const promises: Promise<void>[] = []; const promises: Promise<void>[] = [];
async function onImage(src: Source, node: Image): Promise<MdxJsxFlowElement | undefined> { async function updateImage(src: Source, node: Image): Promise<RootContent | undefined> {
const attributes: MdxJsxAttribute[] = [
{
type: 'mdxJsxAttribute',
name: 'alt',
value: node.alt ?? 'image',
},
];
if (node.title) {
attributes.push({
type: 'mdxJsxAttribute',
name: 'title',
value: node.title,
});
}
if (src.type === 'file' && useImport) { if (src.type === 'file' && useImport) {
// Unique variable name for the given static image URL // Unique variable name for the given static image URL
const variableName = `__img${importsToInject.length}`; const variableName = `__img${importsToInject.length}`;
@@ -124,34 +108,47 @@ export function remarkImage({
importPath: getImportPath(src.file, file.dirname), importPath: getImportPath(src.file, file.dirname),
}); });
attributes.push({
type: 'mdxJsxAttribute',
name: 'src',
value: {
type: 'mdxJsxAttributeValueExpression',
value: variableName,
data: {
estree: {
body: [
{
type: 'ExpressionStatement',
expression: { type: 'Identifier', name: variableName },
},
],
type: 'Program',
sourceType: 'script',
},
},
},
});
const out: MdxJsxFlowElement = { const out: MdxJsxFlowElement = {
children: [], children: [],
type: 'mdxJsxFlowElement', type: 'mdxJsxFlowElement',
name: 'img', name: 'img',
attributes, attributes: [
{
type: 'mdxJsxAttribute',
name: 'alt',
value: node.alt ?? 'image',
},
{
type: 'mdxJsxAttribute',
name: 'src',
value: {
type: 'mdxJsxAttributeValueExpression',
value: variableName,
data: {
estree: {
body: [
{
type: 'ExpressionStatement',
expression: { type: 'Identifier', name: variableName },
},
],
type: 'Program',
sourceType: 'script',
},
},
},
},
],
}; };
if (node.title) {
out.attributes.push({
type: 'mdxJsxAttribute',
name: 'title',
value: node.title,
});
}
if (hasBlur) { if (hasBlur) {
out.attributes.push({ out.attributes.push({
type: 'mdxJsxAttribute', type: 'mdxJsxAttribute',
@@ -173,39 +170,20 @@ export function remarkImage({
}); });
if (!size) return; if (!size) return;
node.data ??= {};
attributes.push( node.data.hProperties ??= {};
{ // `src` doesn't support file paths, we can use `node.url` for files and let the underlying framework handle it
type: 'mdxJsxAttribute', node.data.hProperties.src = src.type === 'url' ? src.url.href : node.url;
name: 'src', node.data.hProperties.width = size.width.toString();
// `src` doesn't support file paths, we can use `node.url` for files and let the underlying framework handle it node.data.hProperties.height = size.height.toString();
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(),
},
);
return {
type: 'mdxJsxFlowElement',
name: 'img',
attributes,
children: [],
};
} }
visit(tree, 'image', (node) => { visit(tree, 'image', (node, idx, parent) => {
if (typeof idx !== 'number' || !parent) return;
const src = parseSrc(decodeURI(node.url), publicDir, file.dirname); const src = parseSrc(decodeURI(node.url), publicDir, file.dirname);
if (!src) return; if (!src) return;
const task = onImage(src, node) const task = updateImage(src, node)
.catch((e) => { .catch((e) => {
// ignore SVG as it is not always needed // ignore SVG as it is not always needed
if (onError === 'ignore' || node.url.endsWith('.svg')) { if (onError === 'ignore' || node.url.endsWith('.svg')) {
@@ -213,19 +191,17 @@ export function remarkImage({
} }
if (onError === 'hide') { if (onError === 'hide') {
return { parent.children.splice(idx, 1);
type: 'mdxJsxFlowElement', return;
name: null,
attributes: [],
children: [],
} satisfies MdxJsxFlowElement;
} }
if (onError === 'error') throw e; if (onError === 'error') throw e;
onError(e); onError(e);
}) })
.then((res) => { .then((res) => {
if (res) Object.assign(node, res); if (res) {
parent.children[idx] = res;
}
}); });
promises.push(task); promises.push(task);
+3 -3
View File
@@ -3,7 +3,7 @@ import { toMdxExport } from './utils';
import type { Heading, Parents, Root } from 'mdast'; import type { Heading, Parents, Root } from 'mdast';
import { defaultStringifier, type StringifyOptions } from './stringifier'; import { defaultStringifier, type StringifyOptions } from './stringifier';
import type { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx'; import type { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx';
import type { Info, State } from 'mdast-util-to-markdown'; import { defaultHandlers, type Info, type State } from 'mdast-util-to-markdown';
import type { PlaceholderData } from './remark-llms.runtime'; import type { PlaceholderData } from './remark-llms.runtime';
export interface LLMsOptions extends StringifyOptions { export interface LLMsOptions extends StringifyOptions {
@@ -92,8 +92,8 @@ export function remarkLLMs(
handlers: { handlers: {
heading(node: Heading, _p, state, info) { heading(node: Heading, _p, state, info) {
const id = node.data?.hProperties?.id; const id = node.data?.hProperties?.id;
const content = state.containerPhrasing(node, info); const defaultValue = defaultHandlers.heading(node, _p, state, info);
return headingIds && id ? `${content} [#${id}]` : content; return headingIds && id ? `${defaultValue} [#${id}]` : defaultValue;
}, },
...rest.handlers, ...rest.handlers,
}, },
@@ -32,10 +32,10 @@ export function remarkMdxMermaid(options: RemarkMdxMermaidOptions = {}): Transfo
const { lang = 'mermaid' } = options; const { lang = 'mermaid' } = options;
return (tree) => { return (tree) => {
visit(tree, 'code', (node) => { visit(tree, 'code', (node, idx, parent) => {
if (node.lang !== lang || !node.value) return; if (node.lang !== lang || !node.value || typeof idx !== 'number' || !parent) return;
Object.assign(node, toMDX(node.value)); parent.children[idx] = toMDX(node.value);
}); });
}; };
} }
+18 -8
View File
@@ -33,8 +33,6 @@ export interface RemarkNpmOptions {
packageManagers?: PackageManager[]; packageManagers?: PackageManager[];
} }
const aliases = ['npm', 'package-install'];
/** /**
* It generates multiple tabs of codeblocks for different package managers from a npm command codeblock. * It generates multiple tabs of codeblocks for different package managers from a npm command codeblock.
*/ */
@@ -48,13 +46,25 @@ export function remarkNpm({
], ],
}: RemarkNpmOptions = {}): Transformer<Root, Root> { }: RemarkNpmOptions = {}): Transformer<Root, Root> {
return (tree) => { return (tree) => {
visit(tree, 'code', (node) => { visit(tree, 'code', (node, idx, parent) => {
if (!node.lang || !aliases.includes(node.lang)) return; if (typeof idx !== 'number' || !parent) return;
let code = node.value; let code: string;
if (node.lang === 'package-install' && !code.startsWith('npm') && !code.startsWith('npx')) { switch (node.lang) {
code = `npm install ${code}`; case 'package-install':
code = node.value;
if (!code.startsWith('npm') && !code.startsWith('npx')) {
code = `npm install ${code}`;
}
break;
case 'npm':
code = node.value;
break;
default:
return;
} }
const options: CodeBlockTabsOptions = { const options: CodeBlockTabsOptions = {
persist, persist,
tabs: [], tabs: [],
@@ -84,7 +94,7 @@ export function remarkNpm({
}); });
} }
Object.assign(node, generateCodeBlockTabs(options)); parent.children[idx] = generateCodeBlockTabs(options);
return 'skip'; return 'skip';
}); });
}; };
@@ -4,11 +4,11 @@ import remarkGfm from 'remark-gfm';
import type { PluggableList, Processor, Transformer } from 'unified'; import type { PluggableList, Processor, Transformer } from 'unified';
import { visit } from 'unist-util-visit'; import { visit } from 'unist-util-visit';
import { toMdxExport } from './utils'; import { toMdxExport } from './utils';
import { import type {
type MdxJsxAttribute, MdxJsxAttribute,
type MdxJsxExpressionAttribute, MdxJsxExpressionAttribute,
type MdxJsxFlowElement, MdxJsxFlowElement,
type MdxJsxTextElement, MdxJsxTextElement,
} from 'mdast-util-mdx'; } from 'mdast-util-mdx';
import { remarkHeading } from './remark-heading'; import { remarkHeading } from './remark-heading';
import { import {
@@ -0,0 +1,59 @@
{
"children": [
{
"children": [
{
"alt": "External",
"data": {
"hProperties": {
"height": "630",
"src": "https://fumadocs.dev/banner.png",
"width": "1200",
},
},
"position": {
"end": {
"column": 25,
"line": 1,
"offset": 24,
},
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"title": null,
"type": "image",
"url": "/banner.png",
},
],
"position": {
"end": {
"column": 25,
"line": 1,
"offset": 24,
},
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"type": "paragraph",
},
],
"position": {
"end": {
"column": 1,
"line": 2,
"offset": 25,
},
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"type": "root",
}
@@ -1 +0,0 @@
<img alt="External" src="https://fumadocs.dev/banner.png" width="1200" height="630" />
@@ -0,0 +1,129 @@
{
"children": [
{
"children": [
{
"alt": "Test",
"position": {
"end": {
"column": 20,
"line": 1,
"offset": 19,
},
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"title": null,
"type": "image",
"url": "./test.png",
},
],
"position": {
"end": {
"column": 20,
"line": 1,
"offset": 19,
},
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"type": "paragraph",
},
{
"children": [
{
"alt": "External",
"data": {
"hProperties": {
"height": "630",
"src": "https://fumadocs.dev/banner.png",
"width": "1200",
},
},
"position": {
"end": {
"column": 45,
"line": 3,
"offset": 65,
},
"start": {
"column": 1,
"line": 3,
"offset": 21,
},
},
"title": null,
"type": "image",
"url": "https://fumadocs.dev/banner.png",
},
],
"position": {
"end": {
"column": 45,
"line": 3,
"offset": 65,
},
"start": {
"column": 1,
"line": 3,
"offset": 21,
},
},
"type": "paragraph",
},
{
"children": [
{
"alt": "Inline",
"position": {
"end": {
"column": 30,
"line": 5,
"offset": 96,
},
"start": {
"column": 1,
"line": 5,
"offset": 67,
},
},
"title": null,
"type": "image",
"url": "./remark-image.svg",
},
],
"position": {
"end": {
"column": 30,
"line": 5,
"offset": 96,
},
"start": {
"column": 1,
"line": 5,
"offset": 67,
},
},
"type": "paragraph",
},
],
"position": {
"end": {
"column": 1,
"line": 6,
"offset": 97,
},
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"type": "root",
}
@@ -1,5 +0,0 @@
![Test](./test.png)
<img alt="External" src="https://fumadocs.dev/banner.png" width="1200" height="630" />
![Inline](./remark-image.svg)
+7 -54
View File
@@ -53,7 +53,6 @@
{ {
"children": [ "children": [
{ {
"alt": "Test",
"attributes": [ "attributes": [
{ {
"name": "alt", "name": "alt",
@@ -86,21 +85,7 @@
], ],
"children": [], "children": [],
"name": "img", "name": "img",
"position": {
"end": {
"column": 20,
"line": 1,
"offset": 19,
},
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"title": null,
"type": "mdxJsxFlowElement", "type": "mdxJsxFlowElement",
"url": "./test.png",
}, },
], ],
"position": { "position": {
@@ -121,30 +106,13 @@
"children": [ "children": [
{ {
"alt": "External", "alt": "External",
"attributes": [ "data": {
{ "hProperties": {
"name": "alt", "height": "630",
"type": "mdxJsxAttribute", "src": "https://fumadocs.dev/banner.png",
"value": "External", "width": "1200",
}, },
{ },
"name": "src",
"type": "mdxJsxAttribute",
"value": "https://fumadocs.dev/banner.png",
},
{
"name": "width",
"type": "mdxJsxAttribute",
"value": "1200",
},
{
"name": "height",
"type": "mdxJsxAttribute",
"value": "630",
},
],
"children": [],
"name": "img",
"position": { "position": {
"end": { "end": {
"column": 45, "column": 45,
@@ -158,7 +126,7 @@
}, },
}, },
"title": null, "title": null,
"type": "mdxJsxFlowElement", "type": "image",
"url": "https://fumadocs.dev/banner.png", "url": "https://fumadocs.dev/banner.png",
}, },
], ],
@@ -179,7 +147,6 @@
{ {
"children": [ "children": [
{ {
"alt": "Inline",
"attributes": [ "attributes": [
{ {
"name": "alt", "name": "alt",
@@ -212,21 +179,7 @@
], ],
"children": [], "children": [],
"name": "img", "name": "img",
"position": {
"end": {
"column": 30,
"line": 5,
"offset": 96,
},
"start": {
"column": 1,
"line": 5,
"offset": 67,
},
},
"title": null,
"type": "mdxJsxFlowElement", "type": "mdxJsxFlowElement",
"url": "./remark-image.svg",
}, },
], ],
"position": { "position": {
+2
View File
@@ -1,3 +1,5 @@
## Hello World
Below is a **MDX** component: Below is a **MDX** component:
<MyPage title="test"> <MyPage title="test">
Binary file not shown.
+15 -21
View File
@@ -85,9 +85,7 @@ test('Remark Steps', async () => {
test('Remark Image: With Path', async () => { test('Remark Image: With Path', async () => {
const file = path.resolve(cwd, './fixtures/remark-image.md'); const file = path.resolve(cwd, './fixtures/remark-image.md');
const content = await fs.readFile(file); const content = await fs.readFile(file);
const processor = remark() const processor = remark().use(remarkImage, { publicDir: path.resolve(cwd, './fixtures') });
.use(remarkImage, { publicDir: path.resolve(cwd, './fixtures') })
.use(remarkMdx);
const result = await processor.run(processor.parse(content), { const result = await processor.run(processor.parse(content), {
path: file, path: file,
@@ -100,31 +98,27 @@ test('Remark Image: With Path', async () => {
test('Remark Image: Without Import', async () => { test('Remark Image: Without Import', async () => {
const content = await fs.readFile(path.resolve(cwd, './fixtures/remark-image.md')); const content = await fs.readFile(path.resolve(cwd, './fixtures/remark-image.md'));
const result = await remark() const processor = remark().use(remarkImage, {
.use(remarkImage, { publicDir: path.resolve(cwd, './fixtures'),
publicDir: path.resolve(cwd, './fixtures'), useImport: false,
useImport: false, });
})
.use(remarkMdx)
.process(content);
await expect(result.value).toMatchFileSnapshot( const result = await processor.run(processor.parse(content));
path.resolve(cwd, './fixtures/remark-image-without-import.output.mdx'), await expect(result).toMatchFileSnapshot(
path.resolve(cwd, './fixtures/remark-image-without-import.output.json'),
); );
}); });
test('Remark Image: `publicDir` with URL', async () => { test('Remark Image: `publicDir` with URL', async () => {
const content = await fs.readFile(path.resolve(cwd, './fixtures/remark-image-public-dir.md')); const content = await fs.readFile(path.resolve(cwd, './fixtures/remark-image-public-dir.md'));
const result = await remark() const processor = remark().use(remarkImage, {
.use(remarkImage, { publicDir: 'https://fumadocs.dev',
publicDir: 'https://fumadocs.dev', useImport: false,
useImport: false, });
})
.use(remarkMdx)
.process(content);
await expect(result.value).toMatchFileSnapshot( const result = await processor.run(processor.parse(content));
path.resolve(cwd, './fixtures/remark-image-public-dir.output.mdx'), await expect(result).toMatchFileSnapshot(
path.resolve(cwd, './fixtures/remark-image-public-dir.output.json'),
); );
}); });