diff --git a/.changeset/transform-rspress-code-blocks.md b/.changeset/transform-rspress-code-blocks.md
new file mode 100644
index 00000000000..f09fb49ba29
--- /dev/null
+++ b/.changeset/transform-rspress-code-blocks.md
@@ -0,0 +1,5 @@
+---
+"@module-federation/rspress-plugin": patch
+---
+
+Allow consumers to replace fenced code block content in exposed Rspress documents.
diff --git a/packages/rspress-plugin/README.md b/packages/rspress-plugin/README.md
index 3ad43d0342b..7cca0965e62 100644
--- a/packages/rspress-plugin/README.md
+++ b/packages/rspress-plugin/README.md
@@ -1,33 +1,50 @@
-# @examples/mf-react-component
+# @module-federation/rspress-plugin
-This example demonstrates how to use Rslib to build a simple Module Federation React component.
+Module Federation integration for Rspress.
-### Command
+## Transform remote code blocks
-Build package
+Enable the capability on the Rspress producer:
-```
-nx build rslib-module
+```ts
+pluginModuleFederation(mfConfig, {
+ transformCodeBlocks: true,
+});
```
-Serve package
+The producer keeps authoring regular Markdown. It does not need to add an id or
+create a code-block component:
-```
-nx serve rslib-module
-```
+````md
+## View all commands
-Dev package
+```bash
+npx mf -h
+```
+````
-1.
+The consumer creates a transformer with the browser-safe runtime entry and
+passes it to the remote MDX document:
-```
-nx dev rslib-module
-```
+```tsx
+import Cli from 'mf-doc/cli-en';
+import { transformCodeBlock } from '@module-federation/rspress-plugin/runtime';
-2.
+export const replaceCliName = transformCodeBlock({
+ replace: [[/\bmf\b/g, 'vmok']],
+ filter: ({ lang }) => lang === 'bash' || lang === 'text',
+});
+export default function Page() {
+ return ;
+}
```
-nx storybook rslib-module
-```
-visit http://localhost:6006
+Replacement rules run in order. Without `filter`, they are considered for every
+fenced code block in the remote document; blocks with no matching content keep
+their original highlighting. A transformer can also return a different
+language when the replacement changes the code type.
+
+The transformation runs while rendering the remote MDX document, so the same
+result is used by browser rendering and SSG. Existing HTML-based llms/Markdown
+rebuilds also read the transformed output.
diff --git a/packages/rspress-plugin/package.json b/packages/rspress-plugin/package.json
index 77f050a5924..8be4c164978 100644
--- a/packages/rspress-plugin/package.json
+++ b/packages/rspress-plugin/package.json
@@ -25,6 +25,10 @@
".": {
"types": "./dist/plugin.d.ts",
"import": "./dist/index.js"
+ },
+ "./runtime": {
+ "types": "./dist/runtime/index.d.ts",
+ "import": "./dist/runtime.js"
}
},
"module": "./dist/index.js",
@@ -33,17 +37,24 @@
"build": "rslib build",
"build:watch": "rslib build --watch",
"dev": "pnpm run build:watch",
+ "test": "rstest",
"pre-release": "pnpm exec turbo run build --filter=@module-federation/webpack-bundler-runtime"
},
"devDependencies": {
+ "@mdx-js/mdx": "^3.1.1",
"@rslib/core": "^0.23.2",
"@rspress/core": "2.0.14",
+ "@rstest/core": "^0.10.6",
"@types/html-to-text": "^9.0.4",
"@types/lodash-es": "^4.17.12",
"@types/react": "^18.3.11",
+ "@types/react-dom": "^18.3.1",
+ "react": "^19.2.6",
+ "react-dom": "^19.2.6",
"typescript": "7.0.2"
},
"dependencies": {
+ "@mdx-js/react": "^3.1.1",
"cheerio": "1.0.0-rc.12",
"@module-federation/sdk": "workspace:*",
"html-to-text": "9.0.5",
@@ -54,6 +65,7 @@
"@rspress/shared": "2.0.14"
},
"peerDependencies": {
- "@rspress/core": "2.0.14"
+ "@rspress/core": "2.0.14",
+ "react": ">=18.0.0"
}
}
diff --git a/packages/rspress-plugin/rslib.config.ts b/packages/rspress-plugin/rslib.config.ts
index 3f851993245..be7f1a23b79 100644
--- a/packages/rspress-plugin/rslib.config.ts
+++ b/packages/rspress-plugin/rslib.config.ts
@@ -12,7 +12,9 @@ export default defineConfig({
source: {
entry: {
index: 'src/plugin.ts',
+ runtime: 'src/runtime/index.tsx',
},
+ tsconfigPath: './tsconfig.lib.json',
},
lib: [
{
diff --git a/packages/rspress-plugin/rstest.config.ts b/packages/rspress-plugin/rstest.config.ts
new file mode 100644
index 00000000000..4df30d6dcc4
--- /dev/null
+++ b/packages/rspress-plugin/rstest.config.ts
@@ -0,0 +1,5 @@
+import { defineConfig } from '@rstest/core';
+
+export default defineConfig({
+ testEnvironment: 'node',
+});
diff --git a/packages/rspress-plugin/src/plugin.ts b/packages/rspress-plugin/src/plugin.ts
index 6fdc022c9f0..9b51daf3546 100644
--- a/packages/rspress-plugin/src/plugin.ts
+++ b/packages/rspress-plugin/src/plugin.ts
@@ -8,11 +8,19 @@ import type { moduleFederationPlugin } from '@module-federation/sdk';
import type { RspressPlugin, RouteMeta } from '@rspress/core';
import { rebuildLlmsByHtml } from './rebuildLlmsByHtml';
import { rebuildSearchIndexByHtml } from './rebuildSearchIndexByHtml';
+import { remarkCodeBlockTransform } from './remarkCodeBlockTransform';
-type RspressPluginOptions = {
+export type RspressPluginOptions = {
autoShared?: boolean;
rebuildSearchIndex?: boolean;
rebuildLlms?: boolean;
+ /**
+ * Allow exposed MDX documents to transform fenced code blocks through a
+ * `transformCodeBlock` prop.
+ *
+ * @default false
+ */
+ transformCodeBlocks?: boolean;
};
const isDev = () => process.env.NODE_ENV === 'development';
@@ -25,6 +33,7 @@ export function pluginModuleFederation(
autoShared = true,
rebuildSearchIndex = true,
rebuildLlms = true,
+ transformCodeBlocks = false,
} = rspressOptions || {};
if (autoShared) {
@@ -66,6 +75,11 @@ export function pluginModuleFederation(
return {
name: 'plugin-module-federation',
+ markdown: transformCodeBlocks
+ ? {
+ remarkPlugins: [remarkCodeBlockTransform],
+ }
+ : undefined,
async config(config) {
if (!isDev() && config.ssg !== false) {
enableSSG = true;
diff --git a/packages/rspress-plugin/src/remarkCodeBlockTransform.test.ts b/packages/rspress-plugin/src/remarkCodeBlockTransform.test.ts
new file mode 100644
index 00000000000..18a1be34604
--- /dev/null
+++ b/packages/rspress-plugin/src/remarkCodeBlockTransform.test.ts
@@ -0,0 +1,85 @@
+import { compile } from '@mdx-js/mdx';
+import { compile as compileRspressMdx } from '@rspress/core/dist/node/mdx/processor.js';
+import type { PluginDriver } from '@rspress/core/dist/node/PluginDriver.js';
+import { describe, expect, it } from '@rstest/core';
+import path from 'node:path';
+import { pluginModuleFederation } from './plugin';
+import {
+ codeBlockTransformRuntimeModule,
+ remarkCodeBlockTransform,
+} from './remarkCodeBlockTransform';
+
+describe('remarkCodeBlockTransform', () => {
+ it('injects the runtime as the implicit MDX layout', async () => {
+ const output = String(
+ await compile('# Title\n\n```bash\nnpx mf -h\n```', {
+ providerImportSource: '@mdx-js/react',
+ remarkPlugins: [remarkCodeBlockTransform],
+ }),
+ );
+
+ expect(output).toContain(
+ `import MDXLayout from "${codeBlockTransformRuntimeModule}";`,
+ );
+ expect(output).toContain('_jsx(MDXLayout');
+ expect(output).toContain('...props');
+ });
+
+ it('preserves an explicit user layout', async () => {
+ const source = [
+ 'export default function CustomLayout({ children }) {',
+ ' return ;',
+ '}',
+ '',
+ '# Title',
+ ].join('\n');
+ const output = String(
+ await compile(source, {
+ providerImportSource: '@mdx-js/react',
+ remarkPlugins: [remarkCodeBlockTransform],
+ }),
+ );
+
+ expect(output).toContain('function CustomLayout');
+ expect(output).not.toContain(codeBlockTransformRuntimeModule);
+ });
+
+ it('works in the real Rspress Markdown pipeline', async () => {
+ const plugin = pluginModuleFederation(
+ { name: 'rspress-code-block-test' },
+ { transformCodeBlocks: true },
+ );
+ const docDirectory = process.cwd();
+ const filepath = path.join(docDirectory, 'code-block-test.mdx');
+ const pluginDriver = {
+ getPlugins: () => [plugin],
+ } as PluginDriver;
+
+ const output = await compileRspressMdx({
+ source: [
+ '# Commands',
+ '',
+ 'Other content.',
+ '',
+ '```bash title=cli wrapCode lineNumbers',
+ 'npx mf -h',
+ '```',
+ ].join('\n'),
+ filepath,
+ docDirectory,
+ config: { markdown: {} },
+ routeService: null,
+ pluginDriver,
+ });
+
+ expect(output).toContain(
+ `import MDXLayout from "${codeBlockTransformRuntimeModule}";`,
+ );
+ expect(output).toContain('className: "shiki css-variables"');
+ expect(output).toContain('lang: "bash"');
+ expect(output).toContain('title: "cli"');
+ expect(output).toContain('lineNumbers: true');
+ expect(output).toContain('wrapCode: true');
+ expect(output).toContain('"Other content."');
+ });
+});
diff --git a/packages/rspress-plugin/src/remarkCodeBlockTransform.ts b/packages/rspress-plugin/src/remarkCodeBlockTransform.ts
new file mode 100644
index 00000000000..165a7550568
--- /dev/null
+++ b/packages/rspress-plugin/src/remarkCodeBlockTransform.ts
@@ -0,0 +1,111 @@
+const RUNTIME_MODULE = '@module-federation/rspress-plugin/runtime';
+
+type EstreeNode = {
+ type: string;
+ [key: string]: unknown;
+};
+
+type EstreeProgram = {
+ type: 'Program';
+ sourceType: 'module';
+ body: EstreeNode[];
+};
+
+type MdxNode = {
+ type: string;
+ data?: {
+ estree?: EstreeProgram;
+ };
+};
+
+type MdxRoot = {
+ children: MdxNode[];
+};
+
+function hasDefaultExport(tree: MdxRoot): boolean {
+ return tree.children.some((node) =>
+ node.data?.estree?.body?.some((statement) => {
+ if (statement.type === 'ExportDefaultDeclaration') {
+ return true;
+ }
+ if (statement.type !== 'ExportNamedDeclaration') {
+ return false;
+ }
+ const specifiers = statement.specifiers;
+ return (
+ Array.isArray(specifiers) &&
+ specifiers.some((specifier) => {
+ if (
+ typeof specifier !== 'object' ||
+ specifier === null ||
+ !('exported' in specifier)
+ ) {
+ return false;
+ }
+ const exported = specifier.exported;
+ return (
+ typeof exported === 'object' &&
+ exported !== null &&
+ 'name' in exported &&
+ exported.name === 'default'
+ );
+ })
+ );
+ }),
+ );
+}
+
+function createLayoutExportNode(): MdxNode {
+ const rawSource = JSON.stringify(RUNTIME_MODULE);
+ return {
+ type: 'mdxjsEsm',
+ data: {
+ estree: {
+ type: 'Program',
+ sourceType: 'module',
+ body: [
+ {
+ type: 'ExportNamedDeclaration',
+ declaration: null,
+ specifiers: [
+ {
+ type: 'ExportSpecifier',
+ local: {
+ type: 'Identifier',
+ name: 'default',
+ },
+ exported: {
+ type: 'Identifier',
+ name: 'default',
+ },
+ },
+ ],
+ source: {
+ type: 'Literal',
+ value: RUNTIME_MODULE,
+ raw: rawSource,
+ },
+ },
+ ],
+ },
+ },
+ };
+}
+
+/**
+ * Install the plugin runtime as the implicit MDX layout. MDX renders the
+ * document body as the layout's child, so the layout can provide code-block
+ * components before the body is evaluated.
+ */
+export function remarkCodeBlockTransform() {
+ return (tree: MdxRoot) => {
+ // Preserve an explicit user-authored MDX layout. Consumers can still use
+ // the runtime API directly around such documents.
+ if (hasDefaultExport(tree)) {
+ return;
+ }
+ tree.children.unshift(createLayoutExportNode());
+ };
+}
+
+export const codeBlockTransformRuntimeModule = RUNTIME_MODULE;
diff --git a/packages/rspress-plugin/src/runtime/index.test.tsx b/packages/rspress-plugin/src/runtime/index.test.tsx
new file mode 100644
index 00000000000..208f46aa536
--- /dev/null
+++ b/packages/rspress-plugin/src/runtime/index.test.tsx
@@ -0,0 +1,102 @@
+import { MDXProvider, useMDXComponents } from '@mdx-js/react';
+import { describe, expect, it } from '@rstest/core';
+import React from 'react';
+import { renderToStaticMarkup } from 'react-dom/server';
+import CodeBlockTransformLayout, { transformCodeBlock } from './index';
+
+function HighlightedDocument() {
+ const components = useMDXComponents();
+ const Pre = components.pre ?? 'pre';
+ const Code = components.code ?? 'code';
+
+ return (
+ <>
+
Commands
+ Run this command:
+
+
+
+ npx
+ mf
+ -h
+
+
+
+ hand-written pre
+ >
+ );
+}
+
+describe('code block transform runtime', () => {
+ it('replaces fenced code without changing surrounding content', () => {
+ const transformer = transformCodeBlock({
+ replace: [[/\bmf\b/g, 'vmok']],
+ });
+ const html = renderToStaticMarkup(
+
+
+
+
+ ,
+ );
+
+ expect(html).toContain('Commands
');
+ expect(html).toContain('Run this command:
');
+ expect(html).toContain(
+ 'npx vmok -h',
+ );
+ expect(html).toContain('lang="bash"');
+ expect(html).toContain('hand-written pre
');
+ });
+
+ it('keeps the original highlighted block when no replacement matches', () => {
+ const transformer = transformCodeBlock({
+ replace: [['webpack', 'rspack']],
+ });
+ const html = renderToStaticMarkup(
+
+
+
+
+ ,
+ );
+
+ expect(html).toContain('npx mf -h');
+ });
+
+ it('allows changing the language from the transformed content', () => {
+ const transformer = transformCodeBlock({
+ replace: [[/\bmf\b/g, 'vmok']],
+ lang: ({ code, lang }) => (code.startsWith('npx vmok') ? 'text' : lang),
+ });
+ const html = renderToStaticMarkup(
+
+
+
+
+ ,
+ );
+
+ expect(html).toContain(
+ 'npx vmok -h',
+ );
+ expect(html).toContain('lang="text"');
+ });
+
+ it('inherits the transformer through nested MDX layouts', () => {
+ const transformer = transformCodeBlock({
+ replace: [[/\bmf\b/g, 'vmok']],
+ });
+ const html = renderToStaticMarkup(
+
+
+
+
+
+
+ ,
+ );
+
+ expect(html.match(/ vmok/g)).toHaveLength(1);
+ });
+});
diff --git a/packages/rspress-plugin/src/runtime/index.tsx b/packages/rspress-plugin/src/runtime/index.tsx
new file mode 100644
index 00000000000..a5e92fc2640
--- /dev/null
+++ b/packages/rspress-plugin/src/runtime/index.tsx
@@ -0,0 +1,321 @@
+import { MDXProvider, useMDXComponents } from '@mdx-js/react';
+import React, {
+ Children,
+ cloneElement,
+ createContext,
+ isValidElement,
+ type ReactNode,
+ useContext,
+ useMemo,
+} from 'react';
+
+export type CodeBlockInfo = {
+ code: string;
+ lang: string;
+ title?: string;
+};
+
+export type CodeBlockTransformResult =
+ | string
+ | {
+ code: string;
+ lang?: string;
+ }
+ | null
+ | undefined;
+
+export type CodeBlockTransformer = (
+ block: CodeBlockInfo,
+) => CodeBlockTransformResult;
+
+export type CodeBlockReplacement = readonly [from: string | RegExp, to: string];
+
+export type TransformCodeBlockOptions = {
+ /**
+ * Ordered replacements applied to every fenced code block.
+ */
+ replace: readonly CodeBlockReplacement[];
+ /**
+ * Limit replacements to selected code blocks.
+ */
+ filter?: (block: CodeBlockInfo) => boolean;
+ /**
+ * Optionally update the language after replacements have been applied.
+ */
+ lang?:
+ | string
+ | ((block: CodeBlockInfo, original: CodeBlockInfo) => string | undefined);
+};
+
+/**
+ * Create a code-block transformer from replacement rules.
+ *
+ * @example
+ * ```tsx
+ * const replaceCliName = transformCodeBlock({
+ * replace: [[/\bmf\b/g, 'vmok']],
+ * });
+ *
+ *
+ * ```
+ */
+export function transformCodeBlock({
+ replace,
+ filter,
+ lang,
+}: TransformCodeBlockOptions): CodeBlockTransformer {
+ return (original) => {
+ if (filter && !filter(original)) {
+ return undefined;
+ }
+
+ let code = original.code;
+ for (const [from, to] of replace) {
+ code =
+ typeof from === 'string'
+ ? code.split(from).join(to)
+ : code.replace(from, to);
+ }
+
+ const transformed = {
+ ...original,
+ code,
+ };
+ const nextLang =
+ typeof lang === 'function' ? lang(transformed, original) : lang;
+ const resolvedLang = nextLang ?? original.lang;
+
+ if (code === original.code && resolvedLang === original.lang) {
+ return undefined;
+ }
+
+ return {
+ code,
+ lang: resolvedLang,
+ };
+ };
+}
+
+type CodeBlockTransformLayoutProps = {
+ children?: ReactNode;
+ transformCodeBlock?: CodeBlockTransformer | null;
+};
+
+type TransformContextValue = {
+ transformer?: CodeBlockTransformer;
+ pre: React.ElementType;
+};
+
+type TransformPreProps = React.ComponentPropsWithoutRef<'pre'> & {
+ lang?: string;
+ title?: string;
+ wrapCode?: boolean;
+ lineNumbers?: boolean;
+ fold?: boolean;
+ height?: number;
+};
+
+const CodeBlockTransformContext = createContext(
+ null,
+);
+
+function getTextContent(node: ReactNode): string {
+ if (typeof node === 'string' || typeof node === 'number') {
+ return String(node);
+ }
+ if (!node) {
+ return '';
+ }
+ if (Array.isArray(node)) {
+ return node.map(getTextContent).join('');
+ }
+ if (isValidElement<{ children?: ReactNode }>(node)) {
+ return getTextContent(node.props.children);
+ }
+ let text = '';
+ Children.forEach(node, (child) => {
+ text += getTextContent(child);
+ });
+ return text;
+}
+
+function isRspressCodeBlock(className: unknown, lang: unknown): boolean {
+ return (
+ typeof className === 'string' &&
+ className.split(/\s+/).includes('shiki') &&
+ typeof lang === 'string'
+ );
+}
+
+function replaceHighlightedText(
+ node: ReactNode,
+ originalCode: string,
+ transformedCode: string,
+): ReactNode {
+ let prefixLength = 0;
+ while (
+ prefixLength < originalCode.length &&
+ prefixLength < transformedCode.length &&
+ originalCode[prefixLength] === transformedCode[prefixLength]
+ ) {
+ prefixLength++;
+ }
+
+ let suffixLength = 0;
+ while (
+ suffixLength < originalCode.length - prefixLength &&
+ suffixLength < transformedCode.length - prefixLength &&
+ originalCode[originalCode.length - suffixLength - 1] ===
+ transformedCode[transformedCode.length - suffixLength - 1]
+ ) {
+ suffixLength++;
+ }
+
+ const oldSuffixStart = originalCode.length - suffixLength;
+ const replacement = transformedCode.slice(
+ prefixLength,
+ transformedCode.length - suffixLength,
+ );
+ let offset = 0;
+ let replacementInserted = false;
+
+ const visit = (current: ReactNode): ReactNode => {
+ if (typeof current === 'string' || typeof current === 'number') {
+ const text = String(current);
+ const start = offset;
+ const end = start + text.length;
+ offset = end;
+
+ if (end <= prefixLength || start >= oldSuffixStart) {
+ return text;
+ }
+
+ let next = '';
+ if (start < prefixLength) {
+ next += text.slice(0, prefixLength - start);
+ }
+ if (!replacementInserted) {
+ next += replacement;
+ replacementInserted = true;
+ }
+ if (end > oldSuffixStart) {
+ next += text.slice(oldSuffixStart - start);
+ }
+ return next;
+ }
+ if (Array.isArray(current)) {
+ return Children.map(current, visit);
+ }
+ if (isValidElement<{ children?: ReactNode }>(current)) {
+ return cloneElement(current, undefined, visit(current.props.children));
+ }
+ return current;
+ };
+
+ const result = visit(node);
+ return getTextContent(result) === transformedCode ? result : transformedCode;
+}
+
+function TransformedCodeBlock({
+ OriginalPre,
+ originalProps,
+ originalCode,
+ code,
+ lang,
+}: {
+ OriginalPre: React.ElementType;
+ originalProps: TransformPreProps;
+ originalCode: string;
+ code: string;
+ lang: string;
+}) {
+ const { children, ...preProps } = originalProps;
+ const codeElement = isValidElement<{ children?: ReactNode }>(children)
+ ? cloneElement(
+ children,
+ undefined,
+ replaceHighlightedText(children.props.children, originalCode, code),
+ )
+ : React.createElement('code', undefined, code);
+
+ return (
+
+ {codeElement}
+
+ );
+}
+
+/**
+ * Internal MDX layout injected by the Rspress plugin.
+ */
+export default function CodeBlockTransformLayout({
+ children,
+ transformCodeBlock: directTransformer,
+}: CodeBlockTransformLayoutProps) {
+ const inherited = useContext(CodeBlockTransformContext);
+ const mdxComponents = useMDXComponents();
+
+ // Nested MDX fragments inherit the parent document's provider. Avoid wrapping
+ // the same code block more than once unless a nested document explicitly
+ // supplies its own transformer.
+ if (inherited && directTransformer === undefined) {
+ return children;
+ }
+
+ const transformer =
+ directTransformer === null
+ ? undefined
+ : (directTransformer ?? inherited?.transformer);
+ const OriginalPre = inherited?.pre ?? mdxComponents.pre ?? 'pre';
+
+ const components = useMemo(
+ () => ({
+ pre: (props: TransformPreProps) => {
+ const { children: codeChildren, lang, title, className } = props;
+
+ if (!transformer || !isRspressCodeBlock(className, lang)) {
+ return ;
+ }
+
+ const original: CodeBlockInfo = {
+ code: getTextContent(codeChildren as ReactNode),
+ lang: lang as string,
+ title: typeof title === 'string' ? title : undefined,
+ };
+ const result = transformer(original);
+
+ if (result == null) {
+ return ;
+ }
+
+ const code = typeof result === 'string' ? result : result.code;
+ const transformedLang =
+ typeof result === 'string'
+ ? original.lang
+ : (result.lang ?? original.lang);
+
+ return (
+
+ );
+ },
+ }),
+ [OriginalPre, transformer],
+ );
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/rspress-plugin/tsconfig.lib.json b/packages/rspress-plugin/tsconfig.lib.json
new file mode 100644
index 00000000000..4c75c663482
--- /dev/null
+++ b/packages/rspress-plugin/tsconfig.lib.json
@@ -0,0 +1,12 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "declaration": true
+ },
+ "exclude": [
+ "rstest.config.ts",
+ "src/**/*.spec.ts",
+ "src/**/*.test.ts",
+ "src/**/*.test-*.tsx"
+ ]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4b15ef49a5c..ca7d0985616 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4006,6 +4006,9 @@ importers:
packages/rspress-plugin:
dependencies:
+ '@mdx-js/react':
+ specifier: ^3.1.1
+ version: 3.1.1(@types/react@18.3.28)(react@19.2.7)
'@module-federation/enhanced':
specifier: workspace:*
version: link:../enhanced
@@ -4031,12 +4034,18 @@ importers:
specifier: 4.18.1
version: 4.18.1
devDependencies:
+ '@mdx-js/mdx':
+ specifier: ^3.1.1
+ version: 3.1.1
'@rslib/core':
specifier: ^0.23.2
version: 0.23.2(@microsoft/api-extractor@7.57.7(@types/node@26.1.1))(@module-federation/runtime-tools@2.8.0)(core-js@3.49.0)(typescript@7.0.2)
'@rspress/core':
specifier: 2.0.14
version: 2.0.14(@module-federation/runtime-tools@2.8.0)(@rspack/core@2.1.2(@module-federation/runtime-tools@2.8.0)(@swc/helpers@0.5.23))(@types/mdast@4.0.4)(@types/react@18.3.28)(core-js@3.49.0)(micromark-util-types@2.0.2)(micromark@4.0.2)
+ '@rstest/core':
+ specifier: ^0.10.6
+ version: 0.10.6(@module-federation/runtime-tools@2.8.0)(core-js@3.49.0)(jsdom@20.0.3)
'@types/html-to-text':
specifier: ^9.0.4
version: 9.0.4
@@ -4046,6 +4055,15 @@ importers:
'@types/react':
specifier: ^18.3.11
version: 18.3.28
+ '@types/react-dom':
+ specifier: ^18.3.1
+ version: 18.3.7(@types/react@18.3.28)
+ react:
+ specifier: ^19.2.6
+ version: 19.2.7
+ react-dom:
+ specifier: ^19.2.6
+ version: 19.2.7(react@19.2.7)
typescript:
specifier: 7.0.2
version: 7.0.2