Repository: mintlify/mdx Branch: main Commit: 439e0d177984 Files: 51 Total size: 74.1 KB Directory structure: gitextract_yzwoer2e/ ├── .eslintrc.cjs ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .yarnrc.yml ├── examples/ │ ├── app-router/ │ │ ├── .eslintrc.json │ │ ├── .gitignore │ │ ├── app/ │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── examples/ │ │ │ └── highlight-example.mdx │ │ ├── next.config.js │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── readme.md │ │ ├── tailwind.config.ts │ │ └── tsconfig.json │ └── pages-router/ │ ├── .eslintrc.json │ ├── .gitignore │ ├── eslint.config.mjs │ ├── examples/ │ │ └── highlight-example.mdx │ ├── next.config.js │ ├── package.json │ ├── pages/ │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ └── index.tsx │ ├── postcss.config.js │ ├── readme.md │ ├── styles/ │ │ └── globals.css │ ├── tailwind.config.ts │ └── tsconfig.json ├── package.json ├── packages/ │ └── mdx/ │ ├── package.json │ ├── src/ │ │ ├── client/ │ │ │ ├── default.tsx │ │ │ └── rsc.tsx │ │ ├── index.ts │ │ ├── plugins/ │ │ │ ├── index.ts │ │ │ └── rehype/ │ │ │ ├── index.ts │ │ │ ├── rehypeSyntaxHighlighting.ts │ │ │ ├── shiki/ │ │ │ │ └── custom-language.ts │ │ │ ├── shiki-constants.ts │ │ │ ├── twoslash/ │ │ │ │ └── config.ts │ │ │ └── utils.ts │ │ ├── server/ │ │ │ └── index.ts │ │ ├── types/ │ │ │ └── index.ts │ │ └── ui/ │ │ ├── index.ts │ │ └── popup.tsx │ ├── tsconfig.build.json │ └── tsconfig.json └── readme.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.cjs ================================================ module.exports = { extends: ['@mintlify/eslint-config-typescript'], parserOptions: { tsconfigRootDir: __dirname, project: './tsconfig.json', }, ignorePatterns: ['.eslintrc.cjs', 'dist'], overrides: [ { files: ['*.js'], extends: ['plugin:@typescript-eslint/disable-type-checked'], }, ], }; ================================================ FILE: .gitignore ================================================ # NPM node_modules/ .eslintcache yarn-error.log # Output dist/ # Misc .DS_Store # TypeScript *.tsbuildinfo # yarn .pnp.* .yarn/* !.yarn/patches !.yarn/plugins !.yarn/releases !.yarn/sdks !.yarn/versions ================================================ FILE: .prettierignore ================================================ /dist /node_modules .next ================================================ FILE: .prettierrc ================================================ "@mintlify/prettier-config/config.js" ================================================ FILE: .yarnrc.yml ================================================ nodeLinker: node-modules ================================================ FILE: examples/app-router/.eslintrc.json ================================================ { "extends": "next/core-web-vitals" } ================================================ FILE: examples/app-router/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js .yarn/install-state.gz # testing /coverage # next.js /.next/ /out/ # production /build # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* # local env files .env*.local # vercel .vercel # typescript *.tsbuildinfo next-env.d.ts ================================================ FILE: examples/app-router/app/globals.css ================================================ @tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { --primary-light: 85 215 153; } } /* modified from https://github.com/shikijs/shiki/blob/main/packages/twoslash/style-rich.css */ /* ===== Basic ===== */ :root { --twoslash-border-color: #8888; --twoslash-underline-color: currentColor; --twoslash-highlighted-border: #c37d0d50; --twoslash-highlighted-bg: #c37d0d20; --twoslash-popup-bg: #f8f8f8; --twoslash-popup-color: inherit; --twoslash-popup-shadow: rgba(0, 0, 0, 0.08) 0px 1px 4px; --twoslash-docs-color: #888; --twoslash-docs-font: sans-serif; --twoslash-code-font: inherit; --twoslash-code-font-size: 1em; --twoslash-matched-color: inherit; --twoslash-unmatched-color: #888; --twoslash-cursor-color: #8888; --twoslash-error-color: #d45656; --twoslash-error-bg: #d4565620; --twoslash-warn-color: #c37d0d; --twoslash-warn-bg: #c37d0d20; --twoslash-tag-color: #3772cf; --twoslash-tag-bg: #3772cf20; --twoslash-tag-warn-color: var(--twoslash-warn-color); --twoslash-tag-warn-bg: var(--twoslash-warn-bg); --twoslash-tag-annotate-color: #1ba673; --twoslash-tag-annotate-bg: #1ba67320; --twoslash-text-size: 0.8rem; --twoslash-docs-tag-style: italic; } /* Respect people's wishes to not have animations */ @media (prefers-reduced-motion: reduce) { .twoslash * { transition: none !important; } } /* ===== Hover Info ===== */ .twoslash:hover .twoslash-hover { border-color: var(--twoslash-underline-color); } .twoslash .twoslash-hover { border-bottom: 1px dotted transparent; transition-timing-function: ease; transition: border-color 0.3s; position: relative; } /* ===== Popup Override ===== */ .mint-twoslash-popover { background: var(--twoslash-popup-bg); color: var(--twoslash-popup-color); border: 1px solid var(--twoslash-border-color); border-radius: 4px; pointer-events: auto; text-align: left; box-shadow: var(--twoslash-popup-shadow); display: inline-flex; flex-direction: column; } .mint-twoslash-popover-pre { display: flex; font-size: var(--twoslash-text-size); font-family: var(--twoslash-code-font); } .mint-twoslash-popover:hover { user-select: auto; } .twoslash .twoslash-popup-arrow { display: none; } .twoslash-popup-code, .twoslash-popup-error, .twoslash-popup-docs { padding: 6px 8px !important; } .mint-twoslash-popover .twoslash-popup-docs { color: var(--twoslash-docs-color); font-family: var(--twoslash-docs-font); font-size: var(--twoslash-text-size); max-width: unset; } .mint-twoslash-popover .twoslash-popup-error { color: var(--twoslash-error-color); background-color: var(--twoslash-error-bg); font-family: var(--twoslash-docs-font); font-size: var(--twoslash-text-size); } .mint-twoslash-popover .twoslash-popup-docs-tags { display: flex; flex-direction: column; font-family: var(--twoslash-docs-font); } .mint-twoslash-popover .twoslash-popup-docs-tag-name { margin-right: 0.5em; font-style: var(--twoslash-docs-tag-style); } .mint-twoslash-popover .twoslash-popup-docs-tag-name { font-family: var(--twoslash-code-font); } /* ===== Query Line ===== */ .mint-twoslash-popover .twoslash-query-line .twoslash-popup-container { position: relative; margin-bottom: 1.4em; transform: translateY(0.6em); } /* ===== Error Line ===== */ .mint-twoslash-popover .twoslash-error-line { position: relative; background-color: var(--twoslash-error-bg); border-left: 3px solid var(--twoslash-error-color); color: var(--twoslash-error-color); padding: 6px 12px; margin: 0.2em 0; min-width: 100%; width: max-content; } .mint-twoslash-popover .twoslash-error-line.twoslash-error-level-warning { background-color: var(--twoslash-warn-bg); border-left: 3px solid var(--twoslash-warn-color); color: var(--twoslash-warn-color); } .mint-twoslash-popover .twoslash-error { background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") repeat-x bottom left; padding-bottom: 2px; } .mint-twoslash-popover .twoslash-error.twoslash-error-level-warning { background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c37d0d'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") repeat-x bottom left; padding-bottom: 2px; } /* ===== Completeions ===== */ .mint-twoslash-popover .twoslash-completion-cursor { position: relative; } .mint-twoslash-popover .twoslash-completion-cursor .twoslash-completion-list { user-select: none; position: absolute; top: 0; left: 0; transform: translate(0, 1.2em); margin: 3px 0 0 -1px; display: inline-block; z-index: 8; box-shadow: var(--twoslash-popup-shadow); background: var(--twoslash-popup-bg); border: 1px solid var(--twoslash-border-color); } .twoslash-completion-list { width: 240px; font-size: var(--twoslash-text-size); padding: 4px; display: flex; flex-direction: column; gap: 4px; } .twoslash-completion-list:hover { user-select: auto; } .twoslash-completion-list::before { background-color: var(--twoslash-cursor-color); width: 2px; position: absolute; top: -1.6em; height: 1.4em; left: -1px; content: ' '; } .twoslash-completion-list li { overflow: hidden; display: flex; align-items: center; gap: 0.25em; line-height: 1em; } .twoslash-completion-list li span.twoslash-completions-unmatched { color: var(--twoslash-unmatched-color); } .twoslash-completion-list .deprecated { text-decoration: line-through; opacity: 0.5; } .twoslash-completion-list li span.twoslash-completions-matched { color: var(--twoslash-matched-color); } /* Highlights */ .twoslash-highlighted { background-color: var(--twoslash-highlighted-bg); border: 1px solid var(--twoslash-highlighted-border); padding: 1px 2px; margin: -1px -3px; border-radius: 4px; } /* Icons */ .twoslash-completion-list .twoslash-completions-icon { color: var(--twoslash-unmatched-color); width: 1em; flex: none; } /* Custom Tags */ .mint-twoslash-popover .twoslash-tag-line { position: relative; background-color: var(--twoslash-tag-bg); border-left: 3px solid var(--twoslash-tag-color); color: var(--twoslash-tag-color); padding: 6px 10px; margin: 0.2em 0; display: flex; align-items: center; gap: 0.3em; min-width: 100%; width: max-content; } .mint-twoslash-popover .twoslash-tag-line .twoslash-tag-icon { width: 1.1em; color: inherit; } .mint-twoslash-popover .twoslash-tag-line.twoslash-tag-error-line { background-color: var(--twoslash-error-bg); border-left: 3px solid var(--twoslash-error-color); color: var(--twoslash-error-color); } .mint-twoslash-popover .twoslash-tag-line.twoslash-tag-warn-line { background-color: var(--twoslash-tag-warn-bg); border-left: 3px solid var(--twoslash-tag-warn-color); color: var(--twoslash-tag-warn-color); } .mint-twoslash-popover .twoslash-tag-line.twoslash-tag-annotate-line { background-color: var(--twoslash-tag-annotate-bg); border-left: 3px solid var(--twoslash-tag-annotate-color); color: var(--twoslash-tag-annotate-color); } ================================================ FILE: examples/app-router/app/layout.tsx ================================================ import type { Metadata } from 'next'; import '@/app/globals.css'; export const metadata: Metadata = { title: 'Create Next App', description: 'Generated by create next app', }; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ================================================ FILE: examples/app-router/app/loading.tsx ================================================ export default function Loading() { return <>Loading...; } ================================================ FILE: examples/app-router/app/page.tsx ================================================ import { MDXRemote } from '@mintlify/mdx/rsc'; import { promises as fs } from 'fs'; export default async function Home() { const data = await fs.readFile(process.cwd() + '/examples/highlight-example.mdx', 'utf8'); return (
); } ================================================ FILE: examples/app-router/examples/highlight-example.mdx ================================================ --- title: 'Line Highlighting' description: 'Highlights specific lines and/or line ranges' --- This MDX file demonstrates syntax highlighting for various programming languages. ## JavaScript ```js index.js {2} console.log('Hello, world!'); function add(a, b) { return a + b; } function subtract(a, b) { return a - b; } ``` ## Python ```python index.py {1-2,4-5} def add(a, b): return a + b def subtract(a, b): return a - b ``` ## Java ```java {3} public class Main { public static void main(String[] args) { System.out.println("Hello, World!"); } } ``` ## C# ```csharp index.cs {1,3-4} public class Program { public static void Main(string[] args) { Console.WriteLine("Hello, World!"); } } ``` ## Testing Twoslash ### Twoslash disabled without any additional configs or filenames ```ts // This is a tooltip that will appear on the next line const myVariable = 'hello world'; // ^? // This is the second line // You can include [links](#anchor) in your hover content function myFunction() { // ^? return myVariable; } ``` ### Twoslash enabled without any additional configs or filenames ```ts twoslash // This is a tooltip that will appear on the next line const myVariable = 'hello world'; // ^? // This is the second line // You can include [links](#anchor) in your hover content function myFunction() { // ^? return myVariable; } ``` ### Twoslash disabled with additional configs and filename ```js something_with_external_packages.tsx icon="js" lines import { useEffect, useState } from 'react'; export function Component() { // ^? const [count, setCount] = useState(0); // ^? ^? useEffect(() => { setTimeout(() => setCount(count + 1), 1000); // ^? }, [count]); return
{count}
; } ``` ### Twoslash enabled with additional configs ```js something_with_external_packages.tsx icon="js" lines twoslash import { useEffect, useState } from 'react'; export function Component() { // ^? const [count, setCount] = useState(0); // ^? ^? useEffect(() => { setTimeout(() => setCount(count + 1), 1000); // ^? }, [count]); return
{count}
; } ``` ## Link support ```js Link Testing icon="js" lines twoslash import { useEffect, useState } from 'react'; // @link Component export function Component() { // ^? return
{count}
; } // @link OtherFunction: #hola-there export function OtherFunction() { // ^? return
{count}
; } // @link ExternalLink: https://google.com export function ExternalLink() { // ^? const str = "Don't worry, only hover targets with ExternalLink will be affected, not random strings"; return
{count}
; } ``` ```ts twoslash type PermissionResult = | { behavior: 'allow'; updatedInput: ToolInput; updatedPermissions?: PermissionUpdate[]; } | { behavior: 'deny'; message: string; interrupt?: boolean; }; type ToolInput = string[]; type PermissionUpdate = { name: string; permission: Array; }; // ---cut-before--- type CanUseTool = ( toolName: string, input: ToolInput, options: { signal: AbortSignal; suggestions?: PermissionUpdate[]; // ^? } ) => Promise; ``` ### Component Hello world from the `Component` section ================================================ FILE: examples/app-router/next.config.js ================================================ const path = require('path'); /** @type {import('next').NextConfig} */ const nextConfig = { serverExternalPackages: ['@shikijs/twoslash'], outputFileTracingIncludes: { '/render': [ path.relative( process.cwd(), path.resolve(require.resolve('typescript/package.json'), '..', 'lib', 'lib.*.d.ts') ), './node_modules/@types/node/**', ], }, }; module.exports = nextConfig; ================================================ FILE: examples/app-router/package.json ================================================ { "name": "app-router", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "@mintlify/mdx": "workspace:^", "@radix-ui/react-popover": "^1.1.15", "next": "16.0.9", "react": "^19.2.1", "react-dom": "^19.2.1" }, "devDependencies": { "@tailwindcss/typography": "^0.5.10", "@types/node": "^20", "@types/react": "^19.2.1", "@types/react-dom": "^19.2.1", "autoprefixer": "^10.0.1", "eslint": "^8", "eslint-config-next": "16.0.7", "postcss": "^8", "tailwindcss": "^3.3.0", "typescript": "^5" } } ================================================ FILE: examples/app-router/postcss.config.js ================================================ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, }; ================================================ FILE: examples/app-router/readme.md ================================================ ## Getting Started This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) and it uses the [App Router](https://nextjs.org/docs/app). It also uses [Tailwind CSS](https://tailwindcss.com/) for styling. You can check out the code at [https://github.com/mintlify/mdx/blob/main/examples/app-router/app/page.tsx](https://github.com/mintlify/mdx/blob/main/examples/app-router/app/page.tsx) to understand how to parse your markdown using [@mintlify/mdx](https://www.npmjs.com/package/@mintlify/mdx). ## Demo You can check out the demo of [this page](https://github.com/mintlify/mdx/blob/main/examples/app-router/app/page.tsx) at [https://mdx-app-router.vercel.app](https://mdx-app-router.vercel.app). ## How to use 1. Use the `MDXRemote` component directly inside your async React Server Component. ```tsx import { MDXRemote } from '@mintlify/mdx/rsc'; export default async function Home() { const source: `--- title: Title --- ## Markdown H2 `; return (
); } ``` ================================================ FILE: examples/app-router/tailwind.config.ts ================================================ import type { Config } from 'tailwindcss'; const config: Config = { content: [ './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './app/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { extend: { backgroundImage: { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, }, }, plugins: [require('@tailwindcss/typography')], }; export default config; ================================================ FILE: examples/app-router/tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "plugins": [ { "name": "next" } ], "paths": { "@/*": ["./*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] } ================================================ FILE: examples/pages-router/.eslintrc.json ================================================ { "extends": "next/core-web-vitals" } ================================================ FILE: examples/pages-router/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js .yarn/install-state.gz # testing /coverage # next.js /.next/ /out/ # production /build # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* # local env files .env*.local # vercel .vercel # typescript *.tsbuildinfo next-env.d.ts ================================================ FILE: examples/pages-router/eslint.config.mjs ================================================ import { defineConfig } from "eslint/config"; import nextCoreWebVitals from "eslint-config-next/core-web-vitals"; import path from "node:path"; import { fileURLToPath } from "node:url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export default defineConfig([{ extends: [...nextCoreWebVitals], }]); ================================================ FILE: examples/pages-router/examples/highlight-example.mdx ================================================ --- title: 'Line Highlighting' description: 'Highlights specific lines and/or line ranges' --- This MDX file demonstrates syntax highlighting for various programming languages. ## JavaScript ```js index.js {2} console.log('Hello, world!'); function add(a, b) { return a + b; } function subtract(a, b) { return a - b; } ``` ## Python ```python index.py {1-2,4-5} def add(a, b): return a + b def subtract(a, b): return a - b ``` ## Java ```java {3} public class Main { public static void main(String[] args) { System.out.println("Hello, World!"); } } ``` ## C# ```csharp index.cs {1,3-4} public class Program { public static void Main(string[] args) { Console.WriteLine("Hello, World!"); } } ``` ## Testing Twoslash ### Twoslash disabled without any additional configs or filenames ```ts // This is a tooltip that will appear on the next line const myVariable = 'hello world'; // ^? // This is the second line // You can include [links](#anchor) in your hover content function myFunction() { // ^? return myVariable; } ``` ### Twoslash enabled without any additional configs or filenames ```ts twoslash // This is a tooltip that will appear on the next line const myVariable = 'hello world'; // ^? // This is the second line // You can include [links](#anchor) in your hover content function myFunction() { // ^? return myVariable; } ``` ### Twoslash disabled with additional configs and filename ```js something_with_external_packages.tsx icon="js" lines import { useEffect, useState } from 'react'; export function Component() { // ^? const [count, setCount] = useState(0); // ^? ^? useEffect(() => { setTimeout(() => setCount(count + 1), 1000); // ^? }, [count]); return
{count}
; } ``` ### Twoslash enabled with additional configs ```js something_with_external_packages.tsx icon="js" lines twoslash import { useEffect, useState } from 'react'; export function Component() { // ^? const [count, setCount] = useState(0); // ^? ^? useEffect(() => { setTimeout(() => setCount(count + 1), 1000); // ^? }, [count]); return
{count}
; } ``` ## Link support ```js Link Testing icon="js" lines twoslash import { useEffect, useState } from 'react'; // @link Component export function Component() { // ^? return
{count}
; } // @link OtherFunction: #hola-there export function OtherFunction() { // ^? return
{count}
; } // @link ExternalLink: https://google.com export function ExternalLink() { // ^? const str = "Don't worry, only hover targets with ExternalLink will be affected, not random strings"; return
{count}
; } ``` ```ts twoslash type PermissionResult = | { behavior: 'allow'; updatedInput: ToolInput; updatedPermissions?: PermissionUpdate[]; } | { behavior: 'deny'; message: string; interrupt?: boolean; }; type ToolInput = string[]; type PermissionUpdate = { name: string; permission: Array; }; // ---cut-before--- type CanUseTool = ( toolName: string, input: ToolInput, options: { signal: AbortSignal; suggestions?: PermissionUpdate[]; // ^? } ) => Promise; ``` ### Component Hello world from the `Component` section ================================================ FILE: examples/pages-router/next.config.js ================================================ const path = require('path'); /** @type {import('next').NextConfig} */ const nextConfig = { serverExternalPackages: ['@shikijs/twoslash'], outputFileTracingIncludes: { '/render': [ path.relative( process.cwd(), path.resolve(require.resolve('typescript/package.json'), '..', 'lib', 'lib.*.d.ts') ), './node_modules/@types/node/**', ], }, }; module.exports = nextConfig; ================================================ FILE: examples/pages-router/package.json ================================================ { "name": "pages-router", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "eslint ." }, "dependencies": { "@mintlify/mdx": "workspace:^", "@radix-ui/react-popover": "^1.1.15", "next": "16.0.9", "react": "^19.2.1", "react-dom": "^19.2.1" }, "devDependencies": { "@tailwindcss/typography": "^0.5.10", "@types/node": "^20", "@types/react": "^19.2.1", "@types/react-dom": "^19.2.1", "autoprefixer": "^10.0.1", "eslint": "^9", "eslint-config-next": "16.0.7", "postcss": "^8", "tailwindcss": "^3.3.0", "typescript": "^5" }, "resolutions": { "@types/react": "19.2.7", "@types/react-dom": "19.2.3" } } ================================================ FILE: examples/pages-router/pages/_app.tsx ================================================ import { AppProps } from 'next/app'; import '@/styles/globals.css'; export default function App({ Component, pageProps }: AppProps) { return ; } ================================================ FILE: examples/pages-router/pages/_document.tsx ================================================ import { Html, Head, Main, NextScript } from 'next/document'; export default function Document() { return (
); } ================================================ FILE: examples/pages-router/pages/index.tsx ================================================ import { MDXClient } from '@mintlify/mdx/client'; import { serialize } from '@mintlify/mdx/server'; import type { SerializeResult } from '@mintlify/mdx/types'; import { promises as fs } from 'fs'; import type { GetStaticProps, InferGetStaticPropsType } from 'next'; export const getStaticProps = (async () => { const data = await fs.readFile(process.cwd() + '/examples/highlight-example.mdx', 'utf8'); const mdxSource = await serialize({ source: data }); if ('error' in mdxSource) { throw mdxSource.error; } return { props: { mdxSource } }; }) satisfies GetStaticProps<{ mdxSource: Omit; }>; export default function Home({ mdxSource }: InferGetStaticPropsType) { return (

{String(mdxSource.frontmatter.title)}

); } ================================================ FILE: examples/pages-router/postcss.config.js ================================================ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, }; ================================================ FILE: examples/pages-router/readme.md ================================================ ## Getting Started This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) and it uses the [Pages Router](https://nextjs.org/docs/pages). It also uses [Tailwind CSS](https://tailwindcss.com/) for styling. You can check out the code at [https://github.com/mintlify/mdx/blob/main/examples/pages-router/pages/index.tsx](https://github.com/mintlify/mdx/blob/main/examples/pages-router/pages/index.tsx) to understand how to parse your markdown using [@mintlify/mdx](https://www.npmjs.com/package/@mintlify/mdx). ## Demo You can check out the demo of [this page](https://github.com/mintlify/mdx/blob/main/examples/pages-router/pages/index.tsx) at [https://mdx-pages-router.vercel.app](https://mdx-pages-router.vercel.app). ## How to use 1. Call the `serialize` function inside `getStaticProps` and return the `mdxSource` object. ```tsx export const getStaticProps = (async () => { const mdxSource = await serialize({ source: '## Markdown H2', }); if ('error' in mdxSource) { // handle error case } return { props: { mdxSource } }; }) satisfies GetStaticProps<{ mdxSource: SerializeSuccess; }>; ``` 2. Pass the `mdxSource` object as props inside the `MDXComponent`. ```tsx export default function Page({ mdxSource }: InferGetStaticPropsType) { return ; } ``` ================================================ FILE: examples/pages-router/styles/globals.css ================================================ @tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { --primary-light: 85 215 153; } } /* modified from https://github.com/shikijs/shiki/blob/main/packages/twoslash/style-rich.css */ /* ===== Basic ===== */ :root { --twoslash-border-color: #8888; --twoslash-underline-color: currentColor; --twoslash-highlighted-border: #c37d0d50; --twoslash-highlighted-bg: #c37d0d20; --twoslash-popup-bg: #f8f8f8; --twoslash-popup-color: inherit; --twoslash-popup-shadow: rgba(0, 0, 0, 0.08) 0px 1px 4px; --twoslash-docs-color: #888; --twoslash-docs-font: sans-serif; --twoslash-code-font: inherit; --twoslash-code-font-size: 1em; --twoslash-matched-color: inherit; --twoslash-unmatched-color: #888; --twoslash-cursor-color: #8888; --twoslash-error-color: #d45656; --twoslash-error-bg: #d4565620; --twoslash-warn-color: #c37d0d; --twoslash-warn-bg: #c37d0d20; --twoslash-tag-color: #3772cf; --twoslash-tag-bg: #3772cf20; --twoslash-tag-warn-color: var(--twoslash-warn-color); --twoslash-tag-warn-bg: var(--twoslash-warn-bg); --twoslash-tag-annotate-color: #1ba673; --twoslash-tag-annotate-bg: #1ba67320; --twoslash-text-size: 0.8rem; --twoslash-docs-tag-style: italic; } /* Respect people's wishes to not have animations */ @media (prefers-reduced-motion: reduce) { .twoslash * { transition: none !important; } } /* ===== Hover Info ===== */ .twoslash:hover .twoslash-hover { border-color: var(--twoslash-underline-color); } .twoslash .twoslash-hover { border-bottom: 1px dotted transparent; transition-timing-function: ease; transition: border-color 0.3s; position: relative; } /* ===== Popup Override ===== */ .mint-twoslash-popover { background: var(--twoslash-popup-bg); color: var(--twoslash-popup-color); border: 1px solid var(--twoslash-border-color); border-radius: 4px; pointer-events: auto; text-align: left; box-shadow: var(--twoslash-popup-shadow); display: inline-flex; flex-direction: column; } .mint-twoslash-popover-pre { display: flex; font-size: var(--twoslash-text-size); font-family: var(--twoslash-code-font); } .mint-twoslash-popover:hover { user-select: auto; } .twoslash .twoslash-popup-arrow { display: none; } .twoslash-popup-code, .twoslash-popup-error, .twoslash-popup-docs { padding: 6px 8px !important; } .mint-twoslash-popover .twoslash-popup-docs { color: var(--twoslash-docs-color); font-family: var(--twoslash-docs-font); font-size: var(--twoslash-text-size); max-width: unset; } .mint-twoslash-popover .twoslash-popup-error { color: var(--twoslash-error-color); background-color: var(--twoslash-error-bg); font-family: var(--twoslash-docs-font); font-size: var(--twoslash-text-size); } .mint-twoslash-popover .twoslash-popup-docs-tags { display: flex; flex-direction: column; font-family: var(--twoslash-docs-font); } .mint-twoslash-popover .twoslash-popup-docs-tag-name { margin-right: 0.5em; font-style: var(--twoslash-docs-tag-style); } .mint-twoslash-popover .twoslash-popup-docs-tag-name { font-family: var(--twoslash-code-font); } /* ===== Query Line ===== */ .mint-twoslash-popover .twoslash-query-line .twoslash-popup-container { position: relative; margin-bottom: 1.4em; transform: translateY(0.6em); } /* ===== Error Line ===== */ .mint-twoslash-popover .twoslash-error-line { position: relative; background-color: var(--twoslash-error-bg); border-left: 3px solid var(--twoslash-error-color); color: var(--twoslash-error-color); padding: 6px 12px; margin: 0.2em 0; min-width: 100%; width: max-content; } .mint-twoslash-popover .twoslash-error-line.twoslash-error-level-warning { background-color: var(--twoslash-warn-bg); border-left: 3px solid var(--twoslash-warn-color); color: var(--twoslash-warn-color); } .mint-twoslash-popover .twoslash-error { background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") repeat-x bottom left; padding-bottom: 2px; } .mint-twoslash-popover .twoslash-error.twoslash-error-level-warning { background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c37d0d'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") repeat-x bottom left; padding-bottom: 2px; } /* ===== Completeions ===== */ .mint-twoslash-popover .twoslash-completion-cursor { position: relative; } .mint-twoslash-popover .twoslash-completion-cursor .twoslash-completion-list { user-select: none; position: absolute; top: 0; left: 0; transform: translate(0, 1.2em); margin: 3px 0 0 -1px; display: inline-block; z-index: 8; box-shadow: var(--twoslash-popup-shadow); background: var(--twoslash-popup-bg); border: 1px solid var(--twoslash-border-color); } .twoslash-completion-list { width: 240px; font-size: var(--twoslash-text-size); padding: 4px; display: flex; flex-direction: column; gap: 4px; } .twoslash-completion-list:hover { user-select: auto; } .twoslash-completion-list::before { background-color: var(--twoslash-cursor-color); width: 2px; position: absolute; top: -1.6em; height: 1.4em; left: -1px; content: ' '; } .twoslash-completion-list li { overflow: hidden; display: flex; align-items: center; gap: 0.25em; line-height: 1em; } .twoslash-completion-list li span.twoslash-completions-unmatched { color: var(--twoslash-unmatched-color); } .twoslash-completion-list .deprecated { text-decoration: line-through; opacity: 0.5; } .twoslash-completion-list li span.twoslash-completions-matched { color: var(--twoslash-matched-color); } /* Highlights */ .twoslash-highlighted { background-color: var(--twoslash-highlighted-bg); border: 1px solid var(--twoslash-highlighted-border); padding: 1px 2px; margin: -1px -3px; border-radius: 4px; } /* Icons */ .twoslash-completion-list .twoslash-completions-icon { color: var(--twoslash-unmatched-color); width: 1em; flex: none; } /* Custom Tags */ .mint-twoslash-popover .twoslash-tag-line { position: relative; background-color: var(--twoslash-tag-bg); border-left: 3px solid var(--twoslash-tag-color); color: var(--twoslash-tag-color); padding: 6px 10px; margin: 0.2em 0; display: flex; align-items: center; gap: 0.3em; min-width: 100%; width: max-content; } .mint-twoslash-popover .twoslash-tag-line .twoslash-tag-icon { width: 1.1em; color: inherit; } .mint-twoslash-popover .twoslash-tag-line.twoslash-tag-error-line { background-color: var(--twoslash-error-bg); border-left: 3px solid var(--twoslash-error-color); color: var(--twoslash-error-color); } .mint-twoslash-popover .twoslash-tag-line.twoslash-tag-warn-line { background-color: var(--twoslash-tag-warn-bg); border-left: 3px solid var(--twoslash-tag-warn-color); color: var(--twoslash-tag-warn-color); } .mint-twoslash-popover .twoslash-tag-line.twoslash-tag-annotate-line { background-color: var(--twoslash-tag-annotate-bg); border-left: 3px solid var(--twoslash-tag-annotate-color); color: var(--twoslash-tag-annotate-color); } ================================================ FILE: examples/pages-router/tailwind.config.ts ================================================ import type { Config } from 'tailwindcss'; const config: Config = { content: [ './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './app/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { extend: { backgroundImage: { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, }, }, plugins: [require('@tailwindcss/typography')], }; export default config; ================================================ FILE: examples/pages-router/tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "paths": { "@/*": ["./*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] } ================================================ FILE: package.json ================================================ { "name": "mdx", "private": true, "scripts": { "build": "yarn workspaces foreach --topological-dev -Av run build" }, "workspaces": [ "packages/*", "examples/*" ], "packageManager": "yarn@4.3.1" } ================================================ FILE: packages/mdx/package.json ================================================ { "name": "@mintlify/mdx", "version": "4.0.0", "description": "Markdown parser from Mintlify", "main": "./dist/index.js", "types": "./dist/index.d.ts", "sideEffects": false, "files": [ "dist" ], "exports": { ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" }, "./rsc": { "import": "./dist/client/rsc.js", "types": "./dist/client/rsc.d.ts" }, "./client": { "import": "./dist/client/default.js", "types": "./dist/client/default.d.ts" }, "./server": { "import": "./dist/server/index.js", "types": "./dist/server/index.d.ts" }, "./types": { "import": "./dist/types/index.js", "types": "./dist/types/index.d.ts" }, "./plugins": { "import": "./dist/plugins/index.js", "types": "./dist/plugins/index.d.ts" }, "./constants": { "import": "./dist/plugins/rehype/shiki-constants.js", "types": "./dist/plugins/rehype/shiki-constants.d.ts" }, "./ui": { "import": "./dist/ui/index.js", "types": "./dist/ui/index.d.ts" } }, "type": "module", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" }, "repository": { "type": "git", "url": "https://github.com/mintlify/mdx.git" }, "scripts": { "prepare": "npm run build", "build": "tsc --project tsconfig.build.json", "clean:build": "rimraf dist", "clean:all": "rimraf node_modules .eslintcache && yarn clean:build", "watch": "tsc --watch", "type": "tsc --noEmit", "lint": "eslint . --cache", "format": "prettier . --write", "format:check": "prettier . --check" }, "author": "Mintlify, Inc.", "license": "MIT", "devDependencies": { "@mintlify/eslint-config": "^1.0.4", "@mintlify/eslint-config-typescript": "^1.0.9", "@mintlify/prettier-config": "^1.0.1", "@mintlify/ts-config": "^2.0.2", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@tsconfig/recommended": "1.x", "@types/hast": "^3.0.4", "@types/react": "^19.2.1", "@types/react-dom": "^19.2.1", "@types/unist": "^3.0.3", "@typescript-eslint/eslint-plugin": "6.x", "@typescript-eslint/parser": "6.x", "eslint": "8.x", "eslint-config-prettier": "8.x", "eslint-plugin-unused-imports": "^3.x", "prettier": "^3.1.1", "prettier-plugin-tailwindcss": "^0.6.8", "react": "^19.2.1", "react-dom": "^19.2.1", "rimraf": "^5.0.1", "typescript": "^5.7.2" }, "peerDependencies": { "@radix-ui/react-popover": "^19.2.1", "react": "^19.2.1", "react-dom": "^19.2.1" }, "dependencies": { "@shikijs/transformers": "^3.11.0", "@shikijs/twoslash": "^3.12.2", "arktype": "^2.1.26", "hast-util-to-string": "^3.0.1", "mdast-util-from-markdown": "^2.0.2", "mdast-util-gfm": "^3.1.0", "mdast-util-mdx-jsx": "^3.2.0", "mdast-util-to-hast": "^13.2.0", "next-mdx-remote-client": "^1.0.3", "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.0", "remark-math": "^6.0.0", "remark-smartypants": "^3.0.2", "shiki": "^3.11.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0" } } ================================================ FILE: packages/mdx/src/client/default.tsx ================================================ import { MDXClient as BaseMDXClient, MDXClientProps } from 'next-mdx-remote-client/csr'; import { Popup, PopupContent, PopupTrigger } from '../ui/index.js'; export function MDXClient(props: MDXClientProps) { const mergedComponents = { Popup, PopupContent, PopupTrigger, ...props.components, }; return ; } ================================================ FILE: packages/mdx/src/client/rsc.tsx ================================================ import { MDXRemote as BaseMDXRemote, MDXComponents } from 'next-mdx-remote-client/rsc'; import { SerializeOptions } from 'next-mdx-remote-client/serialize'; import rehypeKatex from 'rehype-katex'; import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; import remarkSmartypants from 'remark-smartypants'; import { rehypeSyntaxHighlighting, RehypeSyntaxHighlightingOptions } from '../plugins/index.js'; import { Popup, PopupContent, PopupTrigger } from '../ui/index.js'; export async function MDXRemote({ source, mdxOptions, scope, components, parseFrontmatter, syntaxHighlightingOptions, }: { source: string; mdxOptions?: SerializeOptions['mdxOptions']; scope?: SerializeOptions['scope']; components?: MDXComponents; parseFrontmatter?: SerializeOptions['parseFrontmatter']; syntaxHighlightingOptions?: RehypeSyntaxHighlightingOptions; }) { const mergedComponents = { Popup, PopupContent, PopupTrigger, ...components, }; return ( ); } ================================================ FILE: packages/mdx/src/index.ts ================================================ ================================================ FILE: packages/mdx/src/plugins/index.ts ================================================ export * from './rehype/index.js'; ================================================ FILE: packages/mdx/src/plugins/rehype/index.ts ================================================ export * from './rehypeSyntaxHighlighting.js'; ================================================ FILE: packages/mdx/src/plugins/rehype/rehypeSyntaxHighlighting.ts ================================================ import { transformerTwoslash } from '@shikijs/twoslash'; import { type } from 'arktype'; import type { Element, Root } from 'hast'; import { toString } from 'hast-util-to-string'; import type { MdxJsxFlowElementHast, MdxJsxTextElementHast } from 'mdast-util-mdx-jsx'; import { createHighlighter, type Highlighter } from 'shiki'; import type { Plugin } from 'unified'; import { visit } from 'unist-util-visit'; import { type ShikiLang, type ShikiTheme, shikiColorReplacements, DEFAULT_LANG_ALIASES, DEFAULT_LANG, DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME, DEFAULT_THEMES, DEFAULT_LANGS, SHIKI_TRANSFORMERS, UNIQUE_LANGS, } from './shiki-constants.js'; import { TextMateGrammar, TextMateGrammarType } from './shiki/custom-language.js'; import { getTwoslashOptions, parseLineComment } from './twoslash/config.js'; import { getLanguage } from './utils.js'; export type RehypeSyntaxHighlightingOptions = { theme?: ShikiTheme; themes?: Record<'light' | 'dark', ShikiTheme>; codeStyling?: 'dark' | 'system' | 'light' | Record | null; linkMap?: Map; customLanguages?: string[]; }; let highlighterPromise: Promise | null = null; async function getHighlighter(): Promise { if (!highlighterPromise) { highlighterPromise = createHighlighter({ themes: DEFAULT_THEMES, langs: DEFAULT_LANGS, }); } return highlighterPromise; } export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?], Root, Root> = ( options = {} ) => { return async (tree) => { const nodesToProcess: Promise[] = []; const customLanguageNames: string[] = []; const themesToLoad: ShikiTheme[] = []; if (options.themes) { themesToLoad.push(options.themes.dark); themesToLoad.push(options.themes.light); } else if (options.theme) { themesToLoad.push(options.theme); } const highlighter = await getHighlighter(); await Promise.all([ ...themesToLoad .filter( (theme): theme is Exclude => !DEFAULT_THEMES.includes(theme) && theme !== 'css-variables' ) .map((theme) => highlighter.loadTheme(theme)), ...(options.customLanguages?.map(async (unparsedLang) => { const parsedLang = JSON.parse(unparsedLang); const lang = TextMateGrammar(parsedLang); if (lang instanceof type.errors) { console.error(lang.summary); return; } await highlighter.loadLanguage(lang); const possibleNames = [lang.name, lang.displayName, ...(lang.aliases ?? [])]; customLanguageNames.push(...possibleNames.filter((l) => l != undefined)); }) ?? []), ]); visit(tree, 'element', (node, index, parent) => { const child = node.children[0]; if ( !parent || index === undefined || node.type !== 'element' || node.tagName !== 'pre' || !child || child.type !== 'element' || child.tagName !== 'code' ) { return; } // set the metadata of `node` (which is a pre element) to that of // `child` (which is the code element that likely contains all the metadata) if (!Object.keys(node.properties).length) { node.properties = child.properties; } if (!node.data) { node.data = child.data; } let lang = getLanguage(node, DEFAULT_LANG_ALIASES) ?? getLanguage(child, DEFAULT_LANG_ALIASES) ?? DEFAULT_LANG; if ( !DEFAULT_LANGS.includes(lang) && !customLanguageNames.includes(lang) && UNIQUE_LANGS.includes(lang) ) { nodesToProcess.push( highlighter.loadLanguage(lang).then(() => { traverseNode({ node, index, parent, highlighter, lang, options }); }) ); } else { if (!UNIQUE_LANGS.includes(lang) && !customLanguageNames.includes(lang)) { lang = DEFAULT_LANG; } traverseNode({ node, index, parent, highlighter, lang, options }); } }); await Promise.all(nodesToProcess); }; }; function traverseNode({ node, index, parent, highlighter, lang, options, }: { node: Element; index: number; parent: Element | Root | MdxJsxTextElementHast | MdxJsxFlowElementHast; highlighter: Highlighter; lang: ShikiLang; options: RehypeSyntaxHighlightingOptions; }) { try { let code = toString(node); const meta = node.data?.meta?.split(' ') ?? []; const twoslashIndex = meta.findIndex((str) => str.toLowerCase() === 'twoslash'); const shouldUseTwoslash = twoslashIndex > -1; if (node.data && node.data.meta && shouldUseTwoslash) { meta.splice(twoslashIndex, 1); node.data.meta = meta.join(' ').trim() || undefined; } const linkMap = options.linkMap ?? new Map(); if (shouldUseTwoslash) { const splitCode = code.split('\n'); for (const [i, line] of splitCode.entries()) { const parsedLineComment = parseLineComment(line); if (!parsedLineComment) continue; const { word, href } = parsedLineComment; linkMap.set(word, href); splitCode.splice(i, 1); } code = splitCode.join('\n'); } const twoslashOptions = getTwoslashOptions({ linkMap }); const hast = highlighter.codeToHast(code, { lang: lang ?? DEFAULT_LANG, meta: shouldUseTwoslash ? { __raw: 'twoslash' } : undefined, themes: { light: options.themes?.light ?? options.theme ?? (options.codeStyling === 'dark' ? DEFAULT_DARK_THEME : DEFAULT_LIGHT_THEME), dark: options.themes?.dark ?? options.theme ?? DEFAULT_DARK_THEME, }, colorReplacements: shikiColorReplacements, tabindex: false, tokenizeMaxLineLength: 1000, transformers: [...SHIKI_TRANSFORMERS, transformerTwoslash(twoslashOptions)], }); const codeElement = hast.children[0] as Element; if (!codeElement) return; const preChild = codeElement.children[0] as Element; node.data = node.data ?? {}; codeElement.data = node.data; codeElement.properties.language = lang; if (preChild) { preChild.data = node.data; preChild.properties.language = lang; } parent.children.splice(index, 1, codeElement); } catch (err) { if (err instanceof Error && /Unknown language/.test(err.message)) { return; } throw err; } } export { TextMateGrammar, type TextMateGrammarType }; ================================================ FILE: packages/mdx/src/plugins/rehype/shiki/custom-language.ts ================================================ import { scope } from 'arktype'; // Types come from the LanguageRegistration type in Shiki: node_modules/@shikijs/types/dist/index.d.ts const types = scope({ ScopeName: 'string', ScopePath: 'string', ScopePattern: 'string', IncludeString: 'string', RegExpString: 'string | RegExp', ILocation: { filename: 'string', line: 'number', char: 'number', }, ILocatable: { '$vscodeTextmateLocation?': 'ILocation', }, IRawCapturesMap: { '[string]': 'IRawRule', }, IRawRepositoryMap: { '[string]': 'IRawRule', }, IRawCaptures: 'IRawCapturesMap & ILocatable', _IRawRule: { 'include?': 'IncludeString', 'name?': 'ScopeName', 'contentName?': 'ScopeName', 'match?': 'RegExpString', 'captures?': 'IRawCaptures', 'begin?': 'RegExpString', 'beginCaptures?': 'IRawCaptures', 'end?': 'RegExpString', 'endCaptures?': 'IRawCaptures', 'while?': 'RegExpString', 'whileCaptures?': 'IRawCaptures', 'patterns?': 'IRawRule[]', 'repository?': 'IRawRepository', 'applyEndPatternLast?': 'boolean', '[string]': 'unknown', }, IRawRule: '_IRawRule & ILocatable', IRawRepository: 'IRawRepositoryMap & ILocatable', _IRawGrammar: { repository: 'IRawRepository', scopeName: 'ScopeName', patterns: 'IRawRule[]', 'injections?': { '[string]': 'IRawRule', }, 'injectionSelector?': 'string', 'fileTypes?': 'string[]', 'name?': 'string', 'firstLineMatch?': 'string', '[string]': 'unknown', }, IRawGrammar: 'ILocatable & _IRawGrammar', LanguageRegistration: { name: 'string', scopeName: 'string', 'displayName?': 'string', 'aliases?': 'string[]', 'embeddedLangs?': 'string[]', 'embeddedLangsLazy?': 'string[]', 'balancedBracketSelectors?': 'string[]', 'unbalancedBracketSelectors?': 'string[]', 'foldingStopMarker?': 'string', 'foldingStartMarker?': 'string', 'injectTo?': 'string[]', '[string]': 'unknown', }, TextMateGrammar: 'LanguageRegistration & IRawGrammar', }).export(); export const TextMateGrammar = types.TextMateGrammar; export type TextMateGrammarType = typeof TextMateGrammar.infer; ================================================ FILE: packages/mdx/src/plugins/rehype/shiki-constants.ts ================================================ import { transformerNotationHighlight, transformerNotationFocus, transformerMetaHighlight, transformerNotationDiff, } from '@shikijs/transformers'; import type { ShikiTransformer } from '@shikijs/types'; import { createCssVariablesTheme } from 'shiki/core'; import type { BundledLanguage, ThemeRegistration } from 'shiki/types'; export const LINE_HIGHLIGHT_CLASS_NAME = 'line-highlight'; export const LINE_FOCUS_CLASS_NAME = 'line-focus'; export const LINE_DIFF_ADD_CLASS_NAME = 'line-diff line-add'; export const LINE_DIFF_REMOVE_CLASS_NAME = 'line-diff line-remove'; export type ShikiLang = BundledLanguage | 'ansi' | 'text'; export type ShikiTheme = (typeof SHIKI_THEMES)[number]; export const SHIKI_CSS_THEME = createCssVariablesTheme({ name: 'css-variables', variablePrefix: '--mint-', variableDefaults: { 'color-text': '#171717', 'color-background': 'transparent', 'token-constant': '#171717', 'token-string': '#297a3a', 'token-comment': '#666666', 'token-keyword': '#bd2864', 'token-parameter': '#a35200', 'token-function': '#0068d6', 'token-string-expression': '#297a3a', 'token-punctuation': '#171717', 'token-link': '#297a3a', 'ansi-black': '#000000', 'ansi-black-dim': '#00000080', 'ansi-red': '#bb0000', 'ansi-red-dim': '#bb000080', 'ansi-green': '#00bb00', 'ansi-green-dim': '#00bb0080', 'ansi-yellow': '#bbbb00', 'ansi-yellow-dim': '#bbbb0080', 'ansi-blue': '#0000bb', 'ansi-blue-dim': '#0000bb80', 'ansi-magenta': '#ff00ff', 'ansi-magenta-dim': '#ff00ff80', 'ansi-cyan': '#00bbbb', 'ansi-cyan-dim': '#00bbbb80', 'ansi-white': '#eeeeee', 'ansi-white-dim': '#eeeeee80', 'ansi-bright-black': '#555555', 'ansi-bright-black-dim': '#55555580', 'ansi-bright-red': '#ff5555', 'ansi-bright-red-dim': '#ff555580', 'ansi-bright-green': '#00ff00', 'ansi-bright-green-dim': '#00ff0080', 'ansi-bright-yellow': '#ffff55', 'ansi-bright-yellow-dim': '#ffff5580', 'ansi-bright-blue': '#5555ff', 'ansi-bright-blue-dim': '#5555ff80', 'ansi-bright-magenta': '#ff55ff', 'ansi-bright-magenta-dim': '#ff55ff80', 'ansi-bright-cyan': '#55ffff', 'ansi-bright-cyan-dim': '#55ffff80', 'ansi-bright-white': '#ffffff', 'ansi-bright-white-dim': '#ffffff80', }, fontStyle: true, }); export const DEFAULT_LANG = 'text' as const; export const DEFAULT_DARK_THEME: ShikiTheme = 'dark-plus' as const; export const DEFAULT_LIGHT_THEME: ShikiTheme = 'github-light-default' as const; export const DEFAULT_THEMES: [ShikiTheme, ShikiTheme, ThemeRegistration] = [ DEFAULT_LIGHT_THEME, DEFAULT_DARK_THEME, SHIKI_CSS_THEME, ] as const; export const shikiColorReplacements: Partial>> = { 'dark-plus': { '#1e1e1e': '#0B0C0E', }, }; export const DEFAULT_LANG_ALIASES: Record = { ansi: 'ansi', abap: 'abap', 'actionscript-3': 'actionscript-3', ada: 'ada', 'angular-html': 'angular-html', 'angular-ts': 'angular-ts', apache: 'apache', apex: 'apex', apl: 'apl', applescript: 'applescript', ara: 'ara', asciidoc: 'asciidoc', adoc: 'asciidoc', asm: 'asm', astro: 'astro', awk: 'awk', ballerina: 'ballerina', bat: 'bat', batch: 'bat', beancount: 'beancount', berry: 'berry', be: 'berry', bibtex: 'bibtex', bicep: 'bicep', blade: 'blade', bsl: 'bsl', '1c': 'bsl', c: 'c', h: 'c', cadence: 'cadence', cdc: 'cadence', cairo: 'cairo', clarity: 'clarity', clojure: 'clojure', clj: 'clojure', cmake: 'cmake', cobol: 'cobol', codeowners: 'codeowners', codeql: 'codeql', ql: 'codeql', coffee: 'coffee', coffeescript: 'coffee', 'common-lisp': 'common-lisp', lisp: 'common-lisp', coq: 'coq', cpp: 'cpp', cc: 'cpp', hh: 'cpp', 'c++': 'cpp', crystal: 'crystal', csharp: 'csharp', 'c#': 'csharp', cs: 'csharp', css: 'css', csv: 'csv', cue: 'cue', cypher: 'cypher', cql: 'cypher', d: 'd', dart: 'dart', dax: 'dax', desktop: 'desktop', diff: 'diff', docker: 'docker', dockerfile: 'docker', dotenv: 'dotenv', 'dream-maker': 'dream-maker', edge: 'edge', elixir: 'elixir', elm: 'elm', 'emacs-lisp': 'emacs-lisp', elisp: 'emacs-lisp', erb: 'erb', erlang: 'erlang', erl: 'erlang', fennel: 'fennel', fish: 'fish', fluent: 'fluent', ftl: 'fluent', 'fortran-fixed-form': 'fortran-fixed-form', f: 'fortran-fixed-form', for: 'fortran-fixed-form', f77: 'fortran-fixed-form', 'fortran-free-form': 'fortran-free-form', f90: 'fortran-free-form', f95: 'fortran-free-form', f03: 'fortran-free-form', f08: 'fortran-free-form', f18: 'fortran-free-form', fsharp: 'fsharp', 'f#': 'fsharp', fs: 'fsharp', gdresource: 'gdresource', gdscript: 'gdscript', gdshader: 'gdshader', genie: 'genie', gherkin: 'gherkin', 'git-commit': 'git-commit', 'git-rebase': 'git-rebase', gleam: 'gleam', 'glimmer-js': 'glimmer-js', gjs: 'glimmer-js', 'glimmer-ts': 'glimmer-ts', gts: 'glimmer-ts', glsl: 'glsl', gnuplot: 'gnuplot', go: 'go', graphql: 'graphql', gql: 'graphql', groovy: 'groovy', hack: 'hack', haml: 'haml', handlebars: 'handlebars', hbs: 'handlebars', haskell: 'haskell', hs: 'haskell', haxe: 'haxe', hcl: 'hcl', hjson: 'hjson', hlsl: 'hlsl', html: 'html', 'html-derivative': 'html-derivative', http: 'http', hxml: 'hxml', hy: 'hy', imba: 'imba', ini: 'ini', properties: 'ini', java: 'java', javascript: 'javascript', js: 'javascript', jinja: 'jinja', jison: 'jison', json: 'json', json5: 'json5', jsonc: 'jsonc', jsonl: 'jsonl', jsonnet: 'jsonnet', jssm: 'jssm', fsl: 'jssm', jsx: 'jsx', julia: 'julia', jl: 'julia', kotlin: 'kotlin', kt: 'kotlin', kts: 'kotlin', kusto: 'kusto', kql: 'kusto', latex: 'latex', lean: 'lean', lean4: 'lean', less: 'less', liquid: 'liquid', llvm: 'llvm', log: 'log', logo: 'logo', lua: 'lua', luau: 'luau', make: 'make', makefile: 'make', markdown: 'markdown', md: 'markdown', marko: 'marko', matlab: 'matlab', mdc: 'mdc', mdx: 'mdx', mermaid: 'mermaid', mmd: 'mermaid', mipsasm: 'mipsasm', mips: 'mipsasm', mojo: 'mojo', move: 'move', narrat: 'narrat', nar: 'narrat', nextflow: 'nextflow', nf: 'nextflow', nginx: 'nginx', nim: 'nim', nix: 'nix', nushell: 'nushell', nu: 'nushell', 'objective-c': 'objective-c', objc: 'objective-c', 'objective-cpp': 'objective-cpp', ocaml: 'ocaml', pascal: 'pascal', perl: 'perl', php: 'php', plsql: 'plsql', po: 'po', pot: 'po', potx: 'po', polar: 'polar', postcss: 'postcss', powerquery: 'powerquery', powershell: 'powershell', ps: 'powershell', ps1: 'powershell', prisma: 'prisma', prolog: 'prolog', proto: 'proto', protobuf: 'proto', pug: 'pug', jade: 'pug', puppet: 'puppet', purescript: 'purescript', python: 'python', py: 'python', qml: 'qml', qmldir: 'qmldir', qss: 'qss', r: 'r', racket: 'racket', raku: 'raku', perl6: 'raku', razor: 'razor', reg: 'reg', regexp: 'regexp', regex: 'regexp', rel: 'rel', riscv: 'riscv', rst: 'rst', ruby: 'ruby', rb: 'ruby', rust: 'rust', rs: 'rust', sas: 'sas', sass: 'sass', scala: 'scala', scheme: 'scheme', scss: 'scss', sdbl: 'sdbl', '1c-query': 'sdbl', shaderlab: 'shaderlab', shader: 'shaderlab', shellscript: 'shellscript', bash: 'shellscript', sh: 'shellscript', shell: 'shellscript', zsh: 'shellscript', shellsession: 'shellsession', console: 'shellsession', smalltalk: 'smalltalk', solidity: 'solidity', soy: 'soy', 'closure-templates': 'soy', sparql: 'sparql', splunk: 'splunk', spl: 'splunk', sql: 'sql', 'ssh-config': 'ssh-config', stata: 'stata', stylus: 'stylus', styl: 'stylus', svelte: 'svelte', swift: 'swift', 'system-verilog': 'system-verilog', systemd: 'systemd', talonscript: 'talonscript', talon: 'talonscript', tasl: 'tasl', tcl: 'tcl', templ: 'templ', terraform: 'terraform', tf: 'terraform', tfvars: 'terraform', tex: 'tex', toml: 'toml', 'ts-tags': 'ts-tags', lit: 'ts-tags', tsv: 'tsv', tsx: 'tsx', turtle: 'turtle', twig: 'twig', typescript: 'typescript', ts: 'typescript', typespec: 'typespec', tsp: 'typespec', typst: 'typst', typ: 'typst', txt: 'text', text: 'text', plaintext: 'text', plain: 'text', v: 'v', vala: 'vala', vb: 'vb', cmd: 'vb', verilog: 'verilog', vhdl: 'vhdl', viml: 'viml', vim: 'viml', vimscript: 'viml', vue: 'vue', 'vue-html': 'vue-html', vyper: 'vyper', vy: 'vyper', wasm: 'wasm', wenyan: 'wenyan', 文言: 'wenyan', wgsl: 'wgsl', wikitext: 'wikitext', mediawiki: 'wikitext', wiki: 'wikitext', wit: 'wit', wolfram: 'wolfram', wl: 'wolfram', xml: 'xml', xsl: 'xsl', yaml: 'yaml', yml: 'yaml', zenscript: 'zenscript', zig: 'zig', }; export const UNIQUE_LANGS = Array.from(new Set(Object.values(DEFAULT_LANG_ALIASES))); export const SHIKI_THEMES = [ 'andromeeda', 'aurora-x', 'ayu-dark', 'catppuccin-frappe', 'catppuccin-latte', 'catppuccin-macchiato', 'catppuccin-mocha', 'dark-plus', 'dracula', 'dracula-soft', 'everforest-dark', 'everforest-light', 'github-dark', 'github-dark-default', 'github-dark-dimmed', 'github-dark-high-contrast', 'github-light', 'github-light-default', 'github-light-high-contrast', 'gruvbox-dark-hard', 'gruvbox-dark-medium', 'gruvbox-dark-soft', 'gruvbox-light-hard', 'gruvbox-light-medium', 'gruvbox-light-soft', 'houston', 'kanagawa-dragon', 'kanagawa-lotus', 'kanagawa-wave', 'laserwave', 'light-plus', 'material-theme', 'material-theme-darker', 'material-theme-lighter', 'material-theme-ocean', 'material-theme-palenight', 'min-dark', 'min-light', 'monokai', 'night-owl', 'nord', 'one-dark-pro', 'one-light', 'plastic', 'poimandres', 'red', 'rose-pine', 'rose-pine-dawn', 'rose-pine-moon', 'slack-dark', 'slack-ochin', 'snazzy-light', 'solarized-dark', 'solarized-light', 'synthwave-84', 'tokyo-night', 'vesper', 'vitesse-black', 'vitesse-dark', 'vitesse-light', 'css-variables', // for users who want to use custom CSS to style their code blocks ] as const; export const DEFAULT_LANGS = [ 'bash', 'blade', 'c', 'css', 'c#', 'c++', 'dart', 'diff', 'go', 'html', 'java', 'javascript', 'jsx', 'json', 'kotlin', 'log', 'lua', 'markdown', 'mdx', 'php', 'powershell', 'python', 'ruby', 'rust', 'solidity', 'swift', 'toml', 'typescript', 'tsx', 'yaml', ]; export const matchAlgorithm = { matchAlgorithm: 'v3', } as const; export const SHIKI_TRANSFORMERS: ShikiTransformer[] = [ transformerMetaHighlight({ className: LINE_HIGHLIGHT_CLASS_NAME, }), transformerNotationHighlight({ ...matchAlgorithm, classActiveLine: LINE_HIGHLIGHT_CLASS_NAME, }), transformerNotationFocus({ ...matchAlgorithm, classActiveLine: LINE_FOCUS_CLASS_NAME, }), transformerNotationDiff({ ...matchAlgorithm, classLineAdd: LINE_DIFF_ADD_CLASS_NAME, classLineRemove: LINE_DIFF_REMOVE_CLASS_NAME, }), ]; ================================================ FILE: packages/mdx/src/plugins/rehype/twoslash/config.ts ================================================ import { rendererRich, type TransformerTwoslashOptions } from '@shikijs/twoslash'; import type { Element, ElementContent } from 'hast'; import type { Code } from 'mdast'; import { fromMarkdown } from 'mdast-util-from-markdown'; import { gfmFromMarkdown } from 'mdast-util-gfm'; import { defaultHandlers, toHast } from 'mdast-util-to-hast'; import type { ShikiTransformerContextCommon } from 'shiki/types'; import ts from 'typescript'; const twoslashCompilerOptions: ts.CompilerOptions = { target: ts.ScriptTarget.ESNext, lib: ['ESNext', 'DOM', 'esnext', 'dom', 'es2020'], }; function onTwoslashError(err: unknown, code: string, lang: string) { console.error(JSON.stringify({ err, code, lang })); } function onShikiError(err: unknown, code: string, lang: string) { console.error(JSON.stringify({ err, code, lang })); } export function getTwoslashOptions( { linkMap }: { linkMap: Map } = { linkMap: new Map() } ): TransformerTwoslashOptions { return { onTwoslashError, onShikiError, // copied fuma's approach for custom popup // https://github.com/fuma-nama/fumadocs/blob/dev/packages/twoslash/src/index.ts renderer: rendererRich({ renderMarkdown, renderMarkdownInline, queryRendering: 'line', hast: { hoverToken: { tagName: 'Popup', children(input) { for (const rootElement of input) { if (!('children' in rootElement)) continue; for (const [i, element] of rootElement.children.entries()) { if (element.type !== 'text') continue; const href = linkMap.get(element.value); if (!href) continue; const linkProperties = { href, ...(checkIsExternalLink(href) && { target: '_blank', rel: 'noopener noreferrer', }), }; if (rootElement.type === 'element' && rootElement.tagName === 'PopupTrigger') { rootElement.properties = { ...rootElement.properties, ...linkProperties }; } else { const newElement: ElementContent = { type: 'element', tagName: 'a', properties: linkProperties, children: [{ type: 'text', value: element.value }], }; input.splice(i, 1, newElement); } } } return input; }, }, hoverPopup: { tagName: 'PopupContent', }, hoverCompose: ({ popup, token }) => [ popup, { type: 'element', tagName: 'PopupTrigger', properties: {}, children: [token], }, ], popupDocs: { class: 'prose-sm prose-gray dark:prose-dark twoslash-popup-docs', }, popupTypes: { tagName: 'span', class: 'mint-twoslash-popover-pre', children: (v) => { if (v.length === 1 && v[0]?.type === 'element' && v[0]?.tagName === 'pre') return v; return [ { type: 'element', tagName: 'code', properties: { class: 'twoslash-popup-code shiki', }, children: v, }, ]; }, }, popupDocsTags: { class: 'prose-sm prose-gray dark:prose-dark twoslash-popup-docs twoslash-popup-docs-tags', }, nodesHighlight: { class: 'highlighted-word twoslash-highlighted', }, }, }), langs: ['ts', 'typescript', 'js', 'javascript', 'tsx', 'jsx'], explicitTrigger: true, twoslashOptions: { compilerOptions: twoslashCompilerOptions, }, }; } /** https://github.com/fuma-nama/fumadocs/blob/2862a10c2d78b52c0a3f479ad21b255cc0031fc9/packages/twoslash/src/index.ts#L121-L150 */ function renderMarkdown(this: ShikiTransformerContextCommon, md: string): ElementContent[] { const mdast = fromMarkdown( md.replace(/{@link (?[^}]*)}/g, '$1'), // replace jsdoc links { mdastExtensions: [gfmFromMarkdown()] } ); return ( toHast(mdast, { handlers: { code: (state, node: Code) => { if (node.lang) { return this.codeToHast(node.value, { ...this.options, transformers: [], meta: { __raw: node.meta ?? undefined, }, lang: node.lang, }).children[0] as Element; } return defaultHandlers.code(state, node); }, }, }) as Element ).children; } /** https://github.com/fuma-nama/fumadocs/blob/2862a10c2d78b52c0a3f479ad21b255cc0031fc9/packages/twoslash/src/index.ts#L152-L168 */ function renderMarkdownInline( this: ShikiTransformerContextCommon, md: string, context?: string ): ElementContent[] { const text = context === 'tag:param' ? md.replace(/^(?[\w$-]+)/, '`$1` ') : md; const children = renderMarkdown.call(this, text); if (children.length === 1 && children[0]?.type === 'element' && children[0].tagName === 'p') return children[0].children; return children; } export function parseLineComment(line: string): { word: string; href: string } | undefined { line = line.trim(); if (!line.startsWith('//')) return; line = line.replace(/^[\/\s]+/, '').trim(); if (!line.startsWith('@link ') && !line.startsWith('@link:')) return; line = line.replace('@link:', '@link '); const parts = line.split('@link ')[1]; if (!parts) return; const words = parts.split(' ').filter(Boolean); if (words.length === 1 && words[0]) { let word = words[0]; if (word.endsWith(':')) word = word.slice(0, -1); const lowercaseWord = word.toLowerCase(); const href = word.startsWith('#') ? lowercaseWord : `#${encodeURIComponent(lowercaseWord)}`; return { word, href }; } else if (words.length === 2 && words[0] && words[1]) { let word = words[0]; if (word.endsWith(':')) word = word.slice(0, -1); const href = words[1]; if (!href.startsWith('#') && !href.startsWith('https://')) return; return { word, href }; } return; } type Url = `https://${string}`; function checkIsExternalLink(href: string | undefined): href is Url { let isExternalLink = false; try { if (href && URL.canParse(href)) isExternalLink = true; } catch {} return isExternalLink; } ================================================ FILE: packages/mdx/src/plugins/rehype/utils.ts ================================================ import type { Element } from 'hast'; import { type ShikiLang } from './shiki-constants.js'; export function classNameOrEmptyArray(element: Element): string[] { const className = element.properties.className; if (Array.isArray(className) && className.every((el) => typeof el === 'string')) return className; return []; } export function getLanguage( node: Element, aliases: Record ): ShikiLang | undefined { const className = classNameOrEmptyArray(node); for (const classListItem of className) { if (classListItem.startsWith('language-')) { const lang = classListItem.slice(9).toLowerCase(); if (lang) return aliases[lang] ?? (lang as ShikiLang); } } return undefined; } ================================================ FILE: packages/mdx/src/server/index.ts ================================================ import { serialize as baseSerialize } from 'next-mdx-remote-client/serialize'; import rehypeKatex from 'rehype-katex'; import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; import remarkSmartypants from 'remark-smartypants'; import { rehypeSyntaxHighlighting, RehypeSyntaxHighlightingOptions } from '../plugins/index.js'; import type { SerializeOptions } from '../types/index.js'; export const serialize = async ({ source, mdxOptions, scope, parseFrontmatter = true, syntaxHighlightingOptions, }: { source: string; mdxOptions?: SerializeOptions['mdxOptions']; scope?: SerializeOptions['scope']; parseFrontmatter?: SerializeOptions['parseFrontmatter']; syntaxHighlightingOptions?: RehypeSyntaxHighlightingOptions; }) => { try { return await baseSerialize({ source, options: { mdxOptions: { ...mdxOptions, remarkPlugins: [ remarkGfm, remarkSmartypants, remarkMath, ...(mdxOptions?.remarkPlugins || []), ], rehypePlugins: [ rehypeKatex, [rehypeSyntaxHighlighting, syntaxHighlightingOptions], ...(mdxOptions?.rehypePlugins || []), ], format: mdxOptions?.format || 'mdx', }, scope, parseFrontmatter, }, }); } catch (error) { console.error(`Error occurred while serializing MDX: ${error}`); throw error; } }; ================================================ FILE: packages/mdx/src/types/index.ts ================================================ import type { SerializeOptions, SerializeResult } from 'next-mdx-remote-client/serialize'; type SerializeSuccess = SerializeResult & { compiledSource: string }; export type { SerializeOptions, SerializeResult, SerializeSuccess }; ================================================ FILE: packages/mdx/src/ui/index.ts ================================================ export * from './popup.js'; ================================================ FILE: packages/mdx/src/ui/popup.tsx ================================================ 'use client'; // copied from fuma's approach for custom popup // https://github.com/fuma-nama/fumadocs/blob/dev/packages/twoslash/src/ui/popup.tsx import { Popover, PopoverContent, PopoverPortal, PopoverTrigger } from '@radix-ui/react-popover'; import { type ComponentPropsWithoutRef, type ComponentRef, createContext, forwardRef, type ReactNode, useContext, useMemo, useRef, useState, } from 'react'; interface PopupContextObject { open: boolean; setOpen: (open: boolean) => void; handleOpen: (e: React.PointerEvent) => void; handleClose: (e: React.PointerEvent) => void; } const PopupContext = createContext(undefined); function Popup({ delay = 300, children }: { delay?: number; children: ReactNode }) { const [open, setOpen] = useState(false); const openTimeoutRef = useRef(undefined); const closeTimeoutRef = useRef(undefined); return ( ({ open, setOpen, handleOpen(e) { if (e.pointerType === 'touch') return; if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current); openTimeoutRef.current = window.setTimeout(() => { setOpen(true); }, delay); }, handleClose(e) { if (e.pointerType === 'touch') return; if (openTimeoutRef.current) clearTimeout(openTimeoutRef.current); closeTimeoutRef.current = window.setTimeout(() => { setOpen(false); }, delay); }, }), [delay, open] )} > {children} ); } const PopupTrigger = forwardRef< ComponentRef, ComponentPropsWithoutRef & { href?: string; target?: string; rel?: string } >(({ children, href, target, rel, ...props }, ref) => { const ctx = useContext(PopupContext); if (!ctx) throw new Error('Missing Popup Context'); let element; if (href) { element = ( {children} ); } else { element = {children}; } return ( {element} ); }); PopupTrigger.displayName = 'PopupTrigger'; const PopupContent = forwardRef< ComponentRef, ComponentPropsWithoutRef >(({ className, side = 'bottom', align = 'center', sideOffset = 4, ...props }, ref) => { const ctx = useContext(PopupContext); if (!ctx) throw new Error('Missing Popup Context'); return ( { e.preventDefault(); }} onCloseAutoFocus={(e) => { e.preventDefault(); }} {...props} /> ); }); PopupContent.displayName = 'PopupContent'; export { Popup, PopupTrigger, PopupContent }; ================================================ FILE: packages/mdx/tsconfig.build.json ================================================ { "extends": "./tsconfig.json", "include": ["src/**/*.ts", "src/**/*.tsx"] } ================================================ FILE: packages/mdx/tsconfig.json ================================================ { "extends": "@mintlify/ts-config", "compilerOptions": { "lib": ["dom", "dom.iterable", "esnext"], "jsx": "react-jsx", "target": "ES2021", "outDir": "dist", "declaration": true, "module": "Node16" }, "include": ["**/*.ts", "**/*.tsx"], "exclude": ["node_modules", "dist"] } ================================================ FILE: readme.md ================================================
Mintlify Logo

Mint

Open source docs builder that's beautiful, fast, and easy to work with.

![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen?logo=github) [![Tweet](https://img.shields.io/twitter/url?url=https%3A%2F%2Fmintlify.com%2F)](https://twitter.com/intent/tweet?url=&text=Check%20out%20%40mintlify)

# Mintlify's markdown parser **@mintlify/mdx** is a thin layer on top of [next-mdx-remote-client](https://github.com/ipikuka/next-mdx-remote-client) that provides a better developer experience for Next.js users by adding support for syntax highlighting. ## Installation ```bash # using npm npm i @mintlify/mdx # using yarn yarn add @mintlify/mdx # using pnpm pnpm add @mintlify/mdx ``` ## Examples ### Next.js pages router [You can check the example app here](https://github.com/mintlify/mdx/tree/main/examples/pages-router). 1. Call the `serialize` function inside `getStaticProps` and return the `mdxSource` object. ```tsx export const getStaticProps = (async () => { const mdxSource = await serialize({ source: '## Markdown H2', }); if ('error' in mdxSource) { // handle error case } return { props: { mdxSource } }; }) satisfies GetStaticProps<{ mdxSource: SerializeSuccess; }>; ``` 2. Pass the `mdxSource` object as props inside the `MDXComponent`. ```tsx export default function Page({ mdxSource }: InferGetStaticPropsType) { return ; } ``` ### Next.js app router [You can check the example app here](https://github.com/mintlify/mdx/tree/main/examples/app-router). 1. Use the `MDXRemote` component directly inside your async React Server Component. ```tsx import { MDXRemote } from '@mintlify/mdx'; export default async function Home() { const source: `--- title: Title --- ## Markdown H2 `; return (
); } ``` ## APIs Similar to [next-mdx-remote-client](https://github.com/ipikuka/next-mdx-remote-client), this package exports the following APIs: - `serialize` - a function that compiles MDX source to SerializeResult. - `MDXClient` - a component that renders SerializeSuccess on the client. - `MDXRemote` - a component that both serializes and renders the source - should be used inside async React Server Component. ### serialize ```tsx import { serialize } from '@mintlify/mdx'; const mdxSource = await serialize({ source: '## Markdown H2', mdxOptions: { remarkPlugins: [ // Remark plugins ], rehypePlugins: [ // Rehype plugins ], }, }); ``` ### MDXClient ```tsx 'use client'; import { MDXClient } from '@mintlify/mdx'; ; ``` ### MDXRemote ```tsx import { MDXRemote } from '@mintlify/mdx'; ; ```

Built with ❤︎ by Mintlify