| undefined;
+
+ return {
+ enforce: 'pre',
+ async load(id) {
+ if (!id.endsWith(HIGHLIGHT_QUERY)) return null;
+
+ highlighter ??= createHighlighterCore({
+ engine: createJavaScriptRegexEngine(),
+ langs: [tsx],
+ themes: SHIKI_THEME_REGISTRATIONS,
+ });
+
+ const path = id.slice(0, -HIGHLIGHT_QUERY.length);
+ const source = (await readFile(path, 'utf8')).trim();
+
+ const highlighted: HighlightedSource = {
+ html: (await highlighter).codeToHtml(source, {
+ defaultColor: false,
+ lang: 'tsx',
+ themes: SHIKI_THEMES,
+ transformers: [
+ {
+ name: 'docs:unwrap-pre',
+ root(root) {
+ // Fumadocs provides the outer ``, so the plugin emits the `` element.
+ // The `` element retains Shiki's theme properties.
+ const pre = root.children[0];
+ if (pre?.type !== 'element') throw new Error(`No highlighting ${path}`);
+ const code = pre.children[0];
+ if (code?.type !== 'element') throw new Error(`No highlighting ${path}`);
+
+ code.properties.style = [pre.properties.style, code.properties.style]
+ .filter(Boolean)
+ .join(';');
+ root.children = [code];
+ },
+ },
+ ],
+ }),
+ playgroundHash: encodeCodeHash(source),
+ };
+
+ return `export default ${JSON.stringify(highlighted)};\n`;
+ },
+ name: 'docs-highlight-source',
+ };
+}
diff --git a/apps/docs/src/lib/highlighted-source.ts b/apps/docs/src/lib/highlighted-source.ts
new file mode 100644
index 00000000..fd91dc7a
--- /dev/null
+++ b/apps/docs/src/lib/highlighted-source.ts
@@ -0,0 +1,9 @@
+/**
+ * The build-time highlight plugin exports this value for a `?highlight` TSX import.
+ */
+export type HighlightedSource = {
+ /** Shiki markup for the `` element inside Fumadocs `Pre`. */
+ html: string;
+ /** URL hash that opens this source in the playground, as `encodeCodeHash` would produce. */
+ playgroundHash: string;
+};
diff --git a/apps/docs/src/lib/shiki-theme.ts b/apps/docs/src/lib/shiki-theme.ts
new file mode 100644
index 00000000..53ff3dd7
--- /dev/null
+++ b/apps/docs/src/lib/shiki-theme.ts
@@ -0,0 +1,17 @@
+import githubDark from 'shiki/themes/github-dark.mjs';
+import githubLight from 'shiki/themes/github-light.mjs';
+
+export const SHIKI_THEME_REGISTRATIONS: [typeof githubLight, typeof githubDark] = [
+ githubLight,
+ githubDark,
+];
+
+/**
+ * These Shiki themes apply to MDX fences, examples, and samples.
+ *
+ * Theme order controls Shiki's generated classes and custom properties.
+ */
+export const SHIKI_THEMES = {
+ light: SHIKI_THEME_REGISTRATIONS[0].name,
+ dark: SHIKI_THEME_REGISTRATIONS[1].name,
+};
diff --git a/apps/docs/src/routes/$.tsx b/apps/docs/src/routes/$.tsx
index 02fc2e66..021b698b 100644
--- a/apps/docs/src/routes/$.tsx
+++ b/apps/docs/src/routes/$.tsx
@@ -2,7 +2,6 @@ import { createFileRoute, notFound } from '@tanstack/react-router';
import { createServerFn } from '@tanstack/react-start';
import { staticFunctionMiddleware } from '@tanstack/start-static-server-functions';
import { useFumadocsLoader } from 'fumadocs-core/source/client';
-import { AutoTypeTable } from 'fumadocs-typescript/ui';
import { TypeTable } from 'fumadocs-ui/components/type-table';
import { DocsLayout } from 'fumadocs-ui/layouts/notebook';
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/notebook/page';
@@ -24,9 +23,9 @@ import { getStorybookStoryUrl } from '../lib/storybook';
const GITHUB_DOCS_URL = `${GITHUB_REPO_URL}/blob/main/apps/docs/content/docs`;
const GITHUB_TREE_URL = `${GITHUB_REPO_URL}/tree/main`;
+// `remarkAutoTypeTable` converts `` to a static `` during MDX compilation.
const mdxComponents = {
...defaultMdxComponents,
- AutoTypeTable,
ExampleBlock,
IconGallery,
SourceCodeBlock,
diff --git a/apps/docs/vite.config.ts b/apps/docs/vite.config.ts
index d7ac4e9e..9a8096c3 100644
--- a/apps/docs/vite.config.ts
+++ b/apps/docs/vite.config.ts
@@ -8,6 +8,7 @@ import mdx from 'fumadocs-mdx/vite';
import { readdir } from 'node:fs/promises';
import type { Plugin } from 'vite-plus';
import { defineConfig, lazyPlugins } from 'vite-plus';
+import { highlightSourcePlugin } from './src/lib/highlight-source-plugin.js';
import { getMarkdownPagePath } from './src/lib/markdown-page-path.js';
// staticFunctionMiddleware hardcodes `/__tsr/staticServerFnCache/...` for the
@@ -130,6 +131,7 @@ export default defineConfig(async () => {
},
plugins: lazyPlugins(async () => [
staticFunctionBasePathPlugin(),
+ highlightSourcePlugin(),
mdx(await import('./source.config')),
tailwindcss(),
tanstackStart({
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7e60f7e0..90182f02 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -186,6 +186,9 @@ catalogs:
serve:
specifier: ^14.2.6
version: 14.2.6
+ shiki:
+ specifier: ^4.4.1
+ version: 4.4.1
spin-doctor:
specifier: ^0.0.1
version: 0.0.1
@@ -370,6 +373,9 @@ importers:
serve:
specifier: 'catalog:'
version: 14.2.6(supports-color@7.2.0)
+ shiki:
+ specifier: 'catalog:'
+ version: 4.4.1
tailwindcss:
specifier: 'catalog:'
version: 4.3.3
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 0badc584..c1365247 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -92,6 +92,7 @@ catalog:
react-hook-form: ^7.84.0
react-resizable-panels: ^4.12.2
serve: ^14.2.6
+ shiki: ^4.4.1
spin-doctor: ^0.0.1
storybook: ^10.5.6
sucrase: ^3.35.1