├── packages └── mdx │ ├── src │ ├── index.ts │ ├── ui │ │ ├── index.ts │ │ └── popup.tsx │ ├── plugins │ │ ├── index.ts │ │ └── rehype │ │ │ ├── index.ts │ │ │ ├── utils.ts │ │ │ ├── shiki │ │ │ └── custom-language.ts │ │ │ ├── twoslash │ │ │ └── config.ts │ │ │ ├── rehypeSyntaxHighlighting.ts │ │ │ └── shiki-constants.ts │ ├── types │ │ └── index.ts │ ├── client │ │ ├── default.tsx │ │ └── rsc.tsx │ └── server │ │ └── index.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── package.json ├── .yarnrc.yml ├── .prettierignore ├── .prettierrc ├── examples ├── app-router │ ├── .eslintrc.json │ ├── app │ │ ├── loading.tsx │ │ ├── favicon.ico │ │ ├── page.tsx │ │ ├── layout.tsx │ │ └── globals.css │ ├── postcss.config.js │ ├── next.config.js │ ├── .gitignore │ ├── tailwind.config.ts │ ├── public │ │ ├── vercel.svg │ │ └── next.svg │ ├── tsconfig.json │ ├── package.json │ ├── readme.md │ └── examples │ │ └── highlight-example.mdx └── pages-router │ ├── .eslintrc.json │ ├── public │ ├── favicon.ico │ ├── vercel.svg │ └── next.svg │ ├── postcss.config.js │ ├── pages │ ├── _app.tsx │ ├── _document.tsx │ └── index.tsx │ ├── eslint.config.mjs │ ├── next.config.js │ ├── .gitignore │ ├── tailwind.config.ts │ ├── tsconfig.json │ ├── package.json │ ├── readme.md │ ├── examples │ └── highlight-example.mdx │ └── styles │ └── globals.css ├── .yarn └── install-state.gz ├── package.json ├── .gitignore ├── .eslintrc.cjs └── readme.md /packages/mdx/src/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules 3 | 4 | .next -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | "@mintlify/prettier-config/config.js" 2 | -------------------------------------------------------------------------------- /packages/mdx/src/ui/index.ts: -------------------------------------------------------------------------------- 1 | export * from './popup.js'; 2 | -------------------------------------------------------------------------------- /packages/mdx/src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export * from './rehype/index.js'; 2 | -------------------------------------------------------------------------------- /examples/app-router/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /examples/pages-router/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.yarn/install-state.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mintlify/mdx/HEAD/.yarn/install-state.gz -------------------------------------------------------------------------------- /packages/mdx/src/plugins/rehype/index.ts: -------------------------------------------------------------------------------- 1 | export * from './rehypeSyntaxHighlighting.js'; 2 | -------------------------------------------------------------------------------- /examples/app-router/app/loading.tsx: -------------------------------------------------------------------------------- 1 | export default function Loading() { 2 | return <>Loading...; 3 | } 4 | -------------------------------------------------------------------------------- /examples/app-router/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mintlify/mdx/HEAD/examples/app-router/app/favicon.ico -------------------------------------------------------------------------------- /examples/pages-router/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mintlify/mdx/HEAD/examples/pages-router/public/favicon.ico -------------------------------------------------------------------------------- /packages/mdx/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/**/*.ts", "src/**/*.tsx"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/app-router/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /examples/pages-router/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /examples/pages-router/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from 'next/app'; 2 | 3 | import '@/styles/globals.css'; 4 | 5 | export default function App({ Component, pageProps }: AppProps) { 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /packages/mdx/src/types/index.ts: -------------------------------------------------------------------------------- 1 | import type { SerializeOptions, SerializeResult } from 'next-mdx-remote-client/serialize'; 2 | 3 | type SerializeSuccess = SerializeResult & { compiledSource: string }; 4 | 5 | export type { SerializeOptions, SerializeResult, SerializeSuccess }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdx", 3 | "private": true, 4 | "scripts": { 5 | "build": "yarn workspaces foreach --topological-dev -Av run build" 6 | }, 7 | "workspaces": [ 8 | "packages/*", 9 | "examples/*" 10 | ], 11 | "packageManager": "yarn@4.3.1" 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NPM 2 | node_modules/ 3 | .eslintcache 4 | yarn-error.log 5 | 6 | # Output 7 | dist/ 8 | 9 | # Misc 10 | .DS_Store 11 | 12 | # TypeScript 13 | *.tsbuildinfo 14 | 15 | # yarn 16 | .pnp.* 17 | .yarn/* 18 | !.yarn/patches 19 | !.yarn/plugins 20 | !.yarn/releases 21 | !.yarn/sdks 22 | !.yarn/versions -------------------------------------------------------------------------------- /examples/pages-router/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document'; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /packages/mdx/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@mintlify/ts-config", 3 | "compilerOptions": { 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "jsx": "react-jsx", 6 | "target": "ES2021", 7 | "outDir": "dist", 8 | "declaration": true, 9 | "module": "Node16" 10 | }, 11 | "include": ["**/*.ts", "**/*.tsx"], 12 | "exclude": ["node_modules", "dist"] 13 | } 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@mintlify/eslint-config-typescript'], 3 | parserOptions: { 4 | tsconfigRootDir: __dirname, 5 | project: './tsconfig.json', 6 | }, 7 | ignorePatterns: ['.eslintrc.cjs', 'dist'], 8 | overrides: [ 9 | { 10 | files: ['*.js'], 11 | extends: ['plugin:@typescript-eslint/disable-type-checked'], 12 | }, 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /examples/app-router/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { MDXRemote } from '@mintlify/mdx/rsc'; 2 | import { promises as fs } from 'fs'; 3 | 4 | export default async function Home() { 5 | const data = await fs.readFile(process.cwd() + '/examples/highlight-example.mdx', 'utf8'); 6 | 7 | return ( 8 |
9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /examples/pages-router/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "eslint/config"; 2 | import nextCoreWebVitals from "eslint-config-next/core-web-vitals"; 3 | import path from "node:path"; 4 | import { fileURLToPath } from "node:url"; 5 | 6 | const __filename = fileURLToPath(import.meta.url); 7 | const __dirname = path.dirname(__filename); 8 | 9 | export default defineConfig([{ 10 | extends: [...nextCoreWebVitals], 11 | }]); -------------------------------------------------------------------------------- /examples/app-router/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next'; 2 | 3 | import '@/app/globals.css'; 4 | 5 | export const metadata: Metadata = { 6 | title: 'Create Next App', 7 | description: 'Generated by create next app', 8 | }; 9 | 10 | export default function RootLayout({ children }: { children: React.ReactNode }) { 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/mdx/src/client/default.tsx: -------------------------------------------------------------------------------- 1 | import { MDXClient as BaseMDXClient, MDXClientProps } from 'next-mdx-remote-client/csr'; 2 | 3 | import { Popup, PopupContent, PopupTrigger } from '../ui/index.js'; 4 | 5 | export function MDXClient(props: MDXClientProps) { 6 | const mergedComponents = { 7 | Popup, 8 | PopupContent, 9 | PopupTrigger, 10 | ...props.components, 11 | }; 12 | 13 | return ; 14 | } 15 | -------------------------------------------------------------------------------- /examples/app-router/next.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | serverExternalPackages: ['@shikijs/twoslash'], 6 | outputFileTracingIncludes: { 7 | '/render': [ 8 | path.relative( 9 | process.cwd(), 10 | path.resolve(require.resolve('typescript/package.json'), '..', 'lib', 'lib.*.d.ts') 11 | ), 12 | './node_modules/@types/node/**', 13 | ], 14 | }, 15 | }; 16 | 17 | module.exports = nextConfig; 18 | -------------------------------------------------------------------------------- /examples/pages-router/next.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | /** @type {import('next').NextConfig} */ 4 | const nextConfig = { 5 | serverExternalPackages: ['@shikijs/twoslash'], 6 | outputFileTracingIncludes: { 7 | '/render': [ 8 | path.relative( 9 | process.cwd(), 10 | path.resolve(require.resolve('typescript/package.json'), '..', 'lib', 'lib.*.d.ts') 11 | ), 12 | './node_modules/@types/node/**', 13 | ], 14 | }, 15 | }; 16 | 17 | module.exports = nextConfig; 18 | -------------------------------------------------------------------------------- /examples/app-router/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/pages-router/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/app-router/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss'; 2 | 3 | const config: Config = { 4 | content: [ 5 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 14 | }, 15 | }, 16 | }, 17 | plugins: [require('@tailwindcss/typography')], 18 | }; 19 | export default config; 20 | -------------------------------------------------------------------------------- /examples/pages-router/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss'; 2 | 3 | const config: Config = { 4 | content: [ 5 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 14 | }, 15 | }, 16 | }, 17 | plugins: [require('@tailwindcss/typography')], 18 | }; 19 | export default config; 20 | -------------------------------------------------------------------------------- /examples/pages-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "paths": { 17 | "@/*": ["./*"] 18 | } 19 | }, 20 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /examples/app-router/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/pages-router/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/app-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /examples/app-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app-router", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@mintlify/mdx": "workspace:^", 13 | "@radix-ui/react-popover": "^1.1.15", 14 | "next": "16.0.9", 15 | "react": "^19.2.1", 16 | "react-dom": "^19.2.1" 17 | }, 18 | "devDependencies": { 19 | "@tailwindcss/typography": "^0.5.10", 20 | "@types/node": "^20", 21 | "@types/react": "^19.2.1", 22 | "@types/react-dom": "^19.2.1", 23 | "autoprefixer": "^10.0.1", 24 | "eslint": "^8", 25 | "eslint-config-next": "16.0.7", 26 | "postcss": "^8", 27 | "tailwindcss": "^3.3.0", 28 | "typescript": "^5" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/mdx/src/plugins/rehype/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Element } from 'hast'; 2 | 3 | import { type ShikiLang } from './shiki-constants.js'; 4 | 5 | export function classNameOrEmptyArray(element: Element): string[] { 6 | const className = element.properties.className; 7 | if (Array.isArray(className) && className.every((el) => typeof el === 'string')) return className; 8 | return []; 9 | } 10 | 11 | export function getLanguage( 12 | node: Element, 13 | aliases: Record 14 | ): ShikiLang | undefined { 15 | const className = classNameOrEmptyArray(node); 16 | 17 | for (const classListItem of className) { 18 | if (classListItem.startsWith('language-')) { 19 | const lang = classListItem.slice(9).toLowerCase(); 20 | if (lang) return aliases[lang] ?? (lang as ShikiLang); 21 | } 22 | } 23 | 24 | return undefined; 25 | } 26 | -------------------------------------------------------------------------------- /examples/pages-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pages-router", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "eslint ." 10 | }, 11 | "dependencies": { 12 | "@mintlify/mdx": "workspace:^", 13 | "@radix-ui/react-popover": "^1.1.15", 14 | "next": "16.0.9", 15 | "react": "^19.2.1", 16 | "react-dom": "^19.2.1" 17 | }, 18 | "devDependencies": { 19 | "@tailwindcss/typography": "^0.5.10", 20 | "@types/node": "^20", 21 | "@types/react": "^19.2.1", 22 | "@types/react-dom": "^19.2.1", 23 | "autoprefixer": "^10.0.1", 24 | "eslint": "^9", 25 | "eslint-config-next": "16.0.7", 26 | "postcss": "^8", 27 | "tailwindcss": "^3.3.0", 28 | "typescript": "^5" 29 | }, 30 | "resolutions": { 31 | "@types/react": "19.2.7", 32 | "@types/react-dom": "19.2.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/pages-router/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { MDXClient } from '@mintlify/mdx/client'; 2 | import { serialize } from '@mintlify/mdx/server'; 3 | import type { SerializeResult } from '@mintlify/mdx/types'; 4 | import { promises as fs } from 'fs'; 5 | import type { GetStaticProps, InferGetStaticPropsType } from 'next'; 6 | 7 | export const getStaticProps = (async () => { 8 | const data = await fs.readFile(process.cwd() + '/examples/highlight-example.mdx', 'utf8'); 9 | 10 | const mdxSource = await serialize({ source: data }); 11 | if ('error' in mdxSource) { 12 | throw mdxSource.error; 13 | } 14 | 15 | return { props: { mdxSource } }; 16 | }) satisfies GetStaticProps<{ 17 | mdxSource: Omit; 18 | }>; 19 | 20 | export default function Home({ mdxSource }: InferGetStaticPropsType) { 21 | return ( 22 |
23 |

{String(mdxSource.frontmatter.title)}

24 | 25 | 26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /examples/app-router/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/pages-router/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/app-router/readme.md: -------------------------------------------------------------------------------- 1 | ## Getting Started 2 | 3 | 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. 4 | 5 | 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). 6 | 7 | ## Demo 8 | 9 | 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). 10 | 11 | ## How to use 12 | 13 | 1. Use the `MDXRemote` component directly inside your async React Server Component. 14 | 15 | ```tsx 16 | import { MDXRemote } from '@mintlify/mdx/rsc'; 17 | 18 | export default async function Home() { 19 | const source: `--- 20 | title: Title 21 | --- 22 | 23 | ## Markdown H2 24 | `; 25 | 26 | return ( 27 |
28 | 29 |
30 | ); 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /examples/pages-router/readme.md: -------------------------------------------------------------------------------- 1 | ## Getting Started 2 | 3 | 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. 4 | 5 | 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). 6 | 7 | ## Demo 8 | 9 | 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). 10 | 11 | ## How to use 12 | 13 | 1. Call the `serialize` function inside `getStaticProps` and return the `mdxSource` object. 14 | 15 | ```tsx 16 | export const getStaticProps = (async () => { 17 | const mdxSource = await serialize({ 18 | source: '## Markdown H2', 19 | }); 20 | 21 | if ('error' in mdxSource) { 22 | // handle error case 23 | } 24 | 25 | return { props: { mdxSource } }; 26 | }) satisfies GetStaticProps<{ 27 | mdxSource: SerializeSuccess; 28 | }>; 29 | ``` 30 | 31 | 2. Pass the `mdxSource` object as props inside the `MDXComponent`. 32 | 33 | ```tsx 34 | export default function Page({ mdxSource }: InferGetStaticPropsType) { 35 | return ; 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /packages/mdx/src/server/index.ts: -------------------------------------------------------------------------------- 1 | import { serialize as baseSerialize } from 'next-mdx-remote-client/serialize'; 2 | import rehypeKatex from 'rehype-katex'; 3 | import remarkGfm from 'remark-gfm'; 4 | import remarkMath from 'remark-math'; 5 | import remarkSmartypants from 'remark-smartypants'; 6 | 7 | import { rehypeSyntaxHighlighting, RehypeSyntaxHighlightingOptions } from '../plugins/index.js'; 8 | import type { SerializeOptions } from '../types/index.js'; 9 | 10 | export const serialize = async ({ 11 | source, 12 | mdxOptions, 13 | scope, 14 | parseFrontmatter = true, 15 | syntaxHighlightingOptions, 16 | }: { 17 | source: string; 18 | mdxOptions?: SerializeOptions['mdxOptions']; 19 | scope?: SerializeOptions['scope']; 20 | parseFrontmatter?: SerializeOptions['parseFrontmatter']; 21 | syntaxHighlightingOptions?: RehypeSyntaxHighlightingOptions; 22 | }) => { 23 | try { 24 | return await baseSerialize({ 25 | source, 26 | options: { 27 | mdxOptions: { 28 | ...mdxOptions, 29 | remarkPlugins: [ 30 | remarkGfm, 31 | remarkSmartypants, 32 | remarkMath, 33 | ...(mdxOptions?.remarkPlugins || []), 34 | ], 35 | rehypePlugins: [ 36 | rehypeKatex, 37 | [rehypeSyntaxHighlighting, syntaxHighlightingOptions], 38 | ...(mdxOptions?.rehypePlugins || []), 39 | ], 40 | format: mdxOptions?.format || 'mdx', 41 | }, 42 | scope, 43 | parseFrontmatter, 44 | }, 45 | }); 46 | } catch (error) { 47 | console.error(`Error occurred while serializing MDX: ${error}`); 48 | 49 | throw error; 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /packages/mdx/src/client/rsc.tsx: -------------------------------------------------------------------------------- 1 | import { MDXRemote as BaseMDXRemote, MDXComponents } from 'next-mdx-remote-client/rsc'; 2 | import { SerializeOptions } from 'next-mdx-remote-client/serialize'; 3 | import rehypeKatex from 'rehype-katex'; 4 | import remarkGfm from 'remark-gfm'; 5 | import remarkMath from 'remark-math'; 6 | import remarkSmartypants from 'remark-smartypants'; 7 | 8 | import { rehypeSyntaxHighlighting, RehypeSyntaxHighlightingOptions } from '../plugins/index.js'; 9 | import { Popup, PopupContent, PopupTrigger } from '../ui/index.js'; 10 | 11 | export async function MDXRemote({ 12 | source, 13 | mdxOptions, 14 | scope, 15 | components, 16 | parseFrontmatter, 17 | syntaxHighlightingOptions, 18 | }: { 19 | source: string; 20 | mdxOptions?: SerializeOptions['mdxOptions']; 21 | scope?: SerializeOptions['scope']; 22 | components?: MDXComponents; 23 | parseFrontmatter?: SerializeOptions['parseFrontmatter']; 24 | syntaxHighlightingOptions?: RehypeSyntaxHighlightingOptions; 25 | }) { 26 | const mergedComponents = { 27 | Popup, 28 | PopupContent, 29 | PopupTrigger, 30 | ...components, 31 | }; 32 | 33 | return ( 34 | 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /packages/mdx/src/plugins/rehype/shiki/custom-language.ts: -------------------------------------------------------------------------------- 1 | import { scope } from 'arktype'; 2 | 3 | // Types come from the LanguageRegistration type in Shiki: node_modules/@shikijs/types/dist/index.d.ts 4 | const types = scope({ 5 | ScopeName: 'string', 6 | ScopePath: 'string', 7 | ScopePattern: 'string', 8 | IncludeString: 'string', 9 | RegExpString: 'string | RegExp', 10 | 11 | ILocation: { 12 | filename: 'string', 13 | line: 'number', 14 | char: 'number', 15 | }, 16 | 17 | ILocatable: { 18 | '$vscodeTextmateLocation?': 'ILocation', 19 | }, 20 | 21 | IRawCapturesMap: { 22 | '[string]': 'IRawRule', 23 | }, 24 | 25 | IRawRepositoryMap: { 26 | '[string]': 'IRawRule', 27 | }, 28 | 29 | IRawCaptures: 'IRawCapturesMap & ILocatable', 30 | 31 | _IRawRule: { 32 | 'include?': 'IncludeString', 33 | 'name?': 'ScopeName', 34 | 'contentName?': 'ScopeName', 35 | 'match?': 'RegExpString', 36 | 'captures?': 'IRawCaptures', 37 | 'begin?': 'RegExpString', 38 | 'beginCaptures?': 'IRawCaptures', 39 | 'end?': 'RegExpString', 40 | 'endCaptures?': 'IRawCaptures', 41 | 'while?': 'RegExpString', 42 | 'whileCaptures?': 'IRawCaptures', 43 | 'patterns?': 'IRawRule[]', 44 | 'repository?': 'IRawRepository', 45 | 'applyEndPatternLast?': 'boolean', 46 | '[string]': 'unknown', 47 | }, 48 | 49 | IRawRule: '_IRawRule & ILocatable', 50 | 51 | IRawRepository: 'IRawRepositoryMap & ILocatable', 52 | 53 | _IRawGrammar: { 54 | repository: 'IRawRepository', 55 | scopeName: 'ScopeName', 56 | patterns: 'IRawRule[]', 57 | 'injections?': { 58 | '[string]': 'IRawRule', 59 | }, 60 | 'injectionSelector?': 'string', 61 | 'fileTypes?': 'string[]', 62 | 'name?': 'string', 63 | 'firstLineMatch?': 'string', 64 | '[string]': 'unknown', 65 | }, 66 | 67 | IRawGrammar: 'ILocatable & _IRawGrammar', 68 | 69 | LanguageRegistration: { 70 | name: 'string', 71 | scopeName: 'string', 72 | 'displayName?': 'string', 73 | 'aliases?': 'string[]', 74 | 'embeddedLangs?': 'string[]', 75 | 'embeddedLangsLazy?': 'string[]', 76 | 'balancedBracketSelectors?': 'string[]', 77 | 'unbalancedBracketSelectors?': 'string[]', 78 | 'foldingStopMarker?': 'string', 79 | 'foldingStartMarker?': 'string', 80 | 'injectTo?': 'string[]', 81 | '[string]': 'unknown', 82 | }, 83 | 84 | TextMateGrammar: 'LanguageRegistration & IRawGrammar', 85 | }).export(); 86 | 87 | export const TextMateGrammar = types.TextMateGrammar; 88 | export type TextMateGrammarType = typeof TextMateGrammar.infer; 89 | -------------------------------------------------------------------------------- /packages/mdx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mintlify/mdx", 3 | "version": "4.0.0", 4 | "description": "Markdown parser from Mintlify", 5 | "main": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "sideEffects": false, 8 | "files": [ 9 | "dist" 10 | ], 11 | "exports": { 12 | ".": { 13 | "import": "./dist/index.js", 14 | "types": "./dist/index.d.ts" 15 | }, 16 | "./rsc": { 17 | "import": "./dist/client/rsc.js", 18 | "types": "./dist/client/rsc.d.ts" 19 | }, 20 | "./client": { 21 | "import": "./dist/client/default.js", 22 | "types": "./dist/client/default.d.ts" 23 | }, 24 | "./server": { 25 | "import": "./dist/server/index.js", 26 | "types": "./dist/server/index.d.ts" 27 | }, 28 | "./types": { 29 | "import": "./dist/types/index.js", 30 | "types": "./dist/types/index.d.ts" 31 | }, 32 | "./plugins": { 33 | "import": "./dist/plugins/index.js", 34 | "types": "./dist/plugins/index.d.ts" 35 | }, 36 | "./constants": { 37 | "import": "./dist/plugins/rehype/shiki-constants.js", 38 | "types": "./dist/plugins/rehype/shiki-constants.d.ts" 39 | }, 40 | "./ui": { 41 | "import": "./dist/ui/index.js", 42 | "types": "./dist/ui/index.d.ts" 43 | } 44 | }, 45 | "type": "module", 46 | "publishConfig": { 47 | "access": "public", 48 | "registry": "https://registry.npmjs.org/" 49 | }, 50 | "repository": { 51 | "type": "git", 52 | "url": "https://github.com/mintlify/mdx.git" 53 | }, 54 | "scripts": { 55 | "prepare": "npm run build", 56 | "build": "tsc --project tsconfig.build.json", 57 | "clean:build": "rimraf dist", 58 | "clean:all": "rimraf node_modules .eslintcache && yarn clean:build", 59 | "watch": "tsc --watch", 60 | "type": "tsc --noEmit", 61 | "lint": "eslint . --cache", 62 | "format": "prettier . --write", 63 | "format:check": "prettier . --check" 64 | }, 65 | "author": "Mintlify, Inc.", 66 | "license": "MIT", 67 | "devDependencies": { 68 | "@mintlify/eslint-config": "^1.0.4", 69 | "@mintlify/eslint-config-typescript": "^1.0.9", 70 | "@mintlify/prettier-config": "^1.0.1", 71 | "@mintlify/ts-config": "^2.0.2", 72 | "@trivago/prettier-plugin-sort-imports": "^4.3.0", 73 | "@tsconfig/recommended": "1.x", 74 | "@types/hast": "^3.0.4", 75 | "@types/react": "^19.2.1", 76 | "@types/react-dom": "^19.2.1", 77 | "@types/unist": "^3.0.3", 78 | "@typescript-eslint/eslint-plugin": "6.x", 79 | "@typescript-eslint/parser": "6.x", 80 | "eslint": "8.x", 81 | "eslint-config-prettier": "8.x", 82 | "eslint-plugin-unused-imports": "^3.x", 83 | "prettier": "^3.1.1", 84 | "prettier-plugin-tailwindcss": "^0.6.8", 85 | "react": "^19.2.1", 86 | "react-dom": "^19.2.1", 87 | "rimraf": "^5.0.1", 88 | "typescript": "^5.7.2" 89 | }, 90 | "peerDependencies": { 91 | "@radix-ui/react-popover": "^19.2.1", 92 | "react": "^19.2.1", 93 | "react-dom": "^19.2.1" 94 | }, 95 | "dependencies": { 96 | "@shikijs/transformers": "^3.11.0", 97 | "@shikijs/twoslash": "^3.12.2", 98 | "arktype": "^2.1.26", 99 | "hast-util-to-string": "^3.0.1", 100 | "mdast-util-from-markdown": "^2.0.2", 101 | "mdast-util-gfm": "^3.1.0", 102 | "mdast-util-mdx-jsx": "^3.2.0", 103 | "mdast-util-to-hast": "^13.2.0", 104 | "next-mdx-remote-client": "^1.0.3", 105 | "rehype-katex": "^7.0.1", 106 | "remark-gfm": "^4.0.0", 107 | "remark-math": "^6.0.0", 108 | "remark-smartypants": "^3.0.2", 109 | "shiki": "^3.11.0", 110 | "unified": "^11.0.0", 111 | "unist-util-visit": "^5.0.0" 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /packages/mdx/src/ui/popup.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | // copied from fuma's approach for custom popup 4 | // https://github.com/fuma-nama/fumadocs/blob/dev/packages/twoslash/src/ui/popup.tsx 5 | import { Popover, PopoverContent, PopoverPortal, PopoverTrigger } from '@radix-ui/react-popover'; 6 | import { 7 | type ComponentPropsWithoutRef, 8 | type ComponentRef, 9 | createContext, 10 | forwardRef, 11 | type ReactNode, 12 | useContext, 13 | useMemo, 14 | useRef, 15 | useState, 16 | } from 'react'; 17 | 18 | interface PopupContextObject { 19 | open: boolean; 20 | setOpen: (open: boolean) => void; 21 | 22 | handleOpen: (e: React.PointerEvent) => void; 23 | handleClose: (e: React.PointerEvent) => void; 24 | } 25 | 26 | const PopupContext = createContext(undefined); 27 | 28 | function Popup({ delay = 300, children }: { delay?: number; children: ReactNode }) { 29 | const [open, setOpen] = useState(false); 30 | const openTimeoutRef = useRef(undefined); 31 | const closeTimeoutRef = useRef(undefined); 32 | 33 | return ( 34 | 35 | ({ 38 | open, 39 | setOpen, 40 | handleOpen(e) { 41 | if (e.pointerType === 'touch') return; 42 | if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current); 43 | 44 | openTimeoutRef.current = window.setTimeout(() => { 45 | setOpen(true); 46 | }, delay); 47 | }, 48 | handleClose(e) { 49 | if (e.pointerType === 'touch') return; 50 | if (openTimeoutRef.current) clearTimeout(openTimeoutRef.current); 51 | 52 | closeTimeoutRef.current = window.setTimeout(() => { 53 | setOpen(false); 54 | }, delay); 55 | }, 56 | }), 57 | [delay, open] 58 | )} 59 | > 60 | {children} 61 | 62 | 63 | ); 64 | } 65 | 66 | const PopupTrigger = forwardRef< 67 | ComponentRef, 68 | ComponentPropsWithoutRef & { href?: string; target?: string; rel?: string } 69 | >(({ children, href, target, rel, ...props }, ref) => { 70 | const ctx = useContext(PopupContext); 71 | if (!ctx) throw new Error('Missing Popup Context'); 72 | 73 | let element; 74 | if (href) { 75 | element = ( 76 | 77 | {children} 78 | 79 | ); 80 | } else { 81 | element = {children}; 82 | } 83 | 84 | return ( 85 | 92 | {element} 93 | 94 | ); 95 | }); 96 | 97 | PopupTrigger.displayName = 'PopupTrigger'; 98 | 99 | const PopupContent = forwardRef< 100 | ComponentRef, 101 | ComponentPropsWithoutRef 102 | >(({ className, side = 'bottom', align = 'center', sideOffset = 4, ...props }, ref) => { 103 | const ctx = useContext(PopupContext); 104 | if (!ctx) throw new Error('Missing Popup Context'); 105 | 106 | return ( 107 | 108 | { 117 | e.preventDefault(); 118 | }} 119 | onCloseAutoFocus={(e) => { 120 | e.preventDefault(); 121 | }} 122 | {...props} 123 | /> 124 | 125 | ); 126 | }); 127 | 128 | PopupContent.displayName = 'PopupContent'; 129 | 130 | export { Popup, PopupTrigger, PopupContent }; 131 | -------------------------------------------------------------------------------- /examples/app-router/examples/highlight-example.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Line Highlighting' 3 | description: 'Highlights specific lines and/or line ranges' 4 | --- 5 | 6 | This MDX file demonstrates syntax highlighting for various programming languages. 7 | 8 | ## JavaScript 9 | 10 | ```js index.js {2} 11 | console.log('Hello, world!'); 12 | function add(a, b) { 13 | return a + b; 14 | } 15 | 16 | function subtract(a, b) { 17 | return a - b; 18 | } 19 | ``` 20 | 21 | ## Python 22 | 23 | ```python index.py {1-2,4-5} 24 | def add(a, b): 25 | return a + b 26 | 27 | def subtract(a, b): 28 | return a - b 29 | ``` 30 | 31 | ## Java 32 | 33 | ```java {3} 34 | public class Main { 35 | public static void main(String[] args) { 36 | System.out.println("Hello, World!"); 37 | } 38 | } 39 | ``` 40 | 41 | ## C# 42 | 43 | ```csharp index.cs {1,3-4} 44 | public class Program 45 | { 46 | public static void Main(string[] args) 47 | { 48 | Console.WriteLine("Hello, World!"); 49 | } 50 | } 51 | ``` 52 | 53 | ## Testing Twoslash 54 | 55 | ### Twoslash disabled without any additional configs or filenames 56 | 57 | ```ts 58 | // This is a tooltip that will appear on the next line 59 | const myVariable = 'hello world'; 60 | // ^? 61 | 62 | // This is the second line 63 | // You can include [links](#anchor) in your hover content 64 | function myFunction() { 65 | // ^? 66 | return myVariable; 67 | } 68 | ``` 69 | 70 | ### Twoslash enabled without any additional configs or filenames 71 | 72 | ```ts twoslash 73 | // This is a tooltip that will appear on the next line 74 | const myVariable = 'hello world'; 75 | // ^? 76 | 77 | // This is the second line 78 | // You can include [links](#anchor) in your hover content 79 | function myFunction() { 80 | // ^? 81 | return myVariable; 82 | } 83 | ``` 84 | 85 | ### Twoslash disabled with additional configs and filename 86 | 87 | ```js something_with_external_packages.tsx icon="js" lines 88 | import { useEffect, useState } from 'react'; 89 | 90 | export function Component() { 91 | // ^? 92 | const [count, setCount] = useState(0); 93 | // ^? ^? 94 | 95 | useEffect(() => { 96 | setTimeout(() => setCount(count + 1), 1000); 97 | // ^? 98 | }, [count]); 99 | 100 | return
{count}
; 101 | } 102 | ``` 103 | 104 | ### Twoslash enabled with additional configs 105 | 106 | ```js something_with_external_packages.tsx icon="js" lines twoslash 107 | import { useEffect, useState } from 'react'; 108 | 109 | export function Component() { 110 | // ^? 111 | const [count, setCount] = useState(0); 112 | // ^? ^? 113 | 114 | useEffect(() => { 115 | setTimeout(() => setCount(count + 1), 1000); 116 | // ^? 117 | }, [count]); 118 | 119 | return
{count}
; 120 | } 121 | ``` 122 | 123 | ## Link support 124 | 125 | ```js Link Testing icon="js" lines twoslash 126 | import { useEffect, useState } from 'react'; 127 | 128 | // @link Component 129 | export function Component() { 130 | // ^? 131 | return
{count}
; 132 | } 133 | 134 | // @link OtherFunction: #hola-there 135 | export function OtherFunction() { 136 | // ^? 137 | return
{count}
; 138 | } 139 | 140 | // @link ExternalLink: https://google.com 141 | export function ExternalLink() { 142 | // ^? 143 | const str = 144 | "Don't worry, only hover targets with ExternalLink will be affected, not random strings"; 145 | return
{count}
; 146 | } 147 | ``` 148 | 149 | ```ts twoslash 150 | type PermissionResult = 151 | | { 152 | behavior: 'allow'; 153 | updatedInput: ToolInput; 154 | updatedPermissions?: PermissionUpdate[]; 155 | } 156 | | { 157 | behavior: 'deny'; 158 | message: string; 159 | interrupt?: boolean; 160 | }; 161 | 162 | type ToolInput = string[]; 163 | 164 | type PermissionUpdate = { 165 | name: string; 166 | permission: Array; 167 | }; 168 | 169 | // ---cut-before--- 170 | 171 | type CanUseTool = ( 172 | toolName: string, 173 | input: ToolInput, 174 | options: { 175 | signal: AbortSignal; 176 | suggestions?: PermissionUpdate[]; 177 | // ^? 178 | } 179 | ) => Promise; 180 | ``` 181 | 182 | ### Component 183 | 184 | Hello world from the `Component` section 185 | -------------------------------------------------------------------------------- /examples/pages-router/examples/highlight-example.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Line Highlighting' 3 | description: 'Highlights specific lines and/or line ranges' 4 | --- 5 | 6 | This MDX file demonstrates syntax highlighting for various programming languages. 7 | 8 | ## JavaScript 9 | 10 | ```js index.js {2} 11 | console.log('Hello, world!'); 12 | function add(a, b) { 13 | return a + b; 14 | } 15 | 16 | function subtract(a, b) { 17 | return a - b; 18 | } 19 | ``` 20 | 21 | ## Python 22 | 23 | ```python index.py {1-2,4-5} 24 | def add(a, b): 25 | return a + b 26 | 27 | def subtract(a, b): 28 | return a - b 29 | ``` 30 | 31 | ## Java 32 | 33 | ```java {3} 34 | public class Main { 35 | public static void main(String[] args) { 36 | System.out.println("Hello, World!"); 37 | } 38 | } 39 | ``` 40 | 41 | ## C# 42 | 43 | ```csharp index.cs {1,3-4} 44 | public class Program 45 | { 46 | public static void Main(string[] args) 47 | { 48 | Console.WriteLine("Hello, World!"); 49 | } 50 | } 51 | ``` 52 | 53 | ## Testing Twoslash 54 | 55 | ### Twoslash disabled without any additional configs or filenames 56 | 57 | ```ts 58 | // This is a tooltip that will appear on the next line 59 | const myVariable = 'hello world'; 60 | // ^? 61 | 62 | // This is the second line 63 | // You can include [links](#anchor) in your hover content 64 | function myFunction() { 65 | // ^? 66 | return myVariable; 67 | } 68 | ``` 69 | 70 | ### Twoslash enabled without any additional configs or filenames 71 | 72 | ```ts twoslash 73 | // This is a tooltip that will appear on the next line 74 | const myVariable = 'hello world'; 75 | // ^? 76 | 77 | // This is the second line 78 | // You can include [links](#anchor) in your hover content 79 | function myFunction() { 80 | // ^? 81 | return myVariable; 82 | } 83 | ``` 84 | 85 | ### Twoslash disabled with additional configs and filename 86 | 87 | ```js something_with_external_packages.tsx icon="js" lines 88 | import { useEffect, useState } from 'react'; 89 | 90 | export function Component() { 91 | // ^? 92 | const [count, setCount] = useState(0); 93 | // ^? ^? 94 | 95 | useEffect(() => { 96 | setTimeout(() => setCount(count + 1), 1000); 97 | // ^? 98 | }, [count]); 99 | 100 | return
{count}
; 101 | } 102 | ``` 103 | 104 | ### Twoslash enabled with additional configs 105 | 106 | ```js something_with_external_packages.tsx icon="js" lines twoslash 107 | import { useEffect, useState } from 'react'; 108 | 109 | export function Component() { 110 | // ^? 111 | const [count, setCount] = useState(0); 112 | // ^? ^? 113 | 114 | useEffect(() => { 115 | setTimeout(() => setCount(count + 1), 1000); 116 | // ^? 117 | }, [count]); 118 | 119 | return
{count}
; 120 | } 121 | ``` 122 | 123 | ## Link support 124 | 125 | ```js Link Testing icon="js" lines twoslash 126 | import { useEffect, useState } from 'react'; 127 | 128 | // @link Component 129 | export function Component() { 130 | // ^? 131 | return
{count}
; 132 | } 133 | 134 | // @link OtherFunction: #hola-there 135 | export function OtherFunction() { 136 | // ^? 137 | return
{count}
; 138 | } 139 | 140 | // @link ExternalLink: https://google.com 141 | export function ExternalLink() { 142 | // ^? 143 | const str = 144 | "Don't worry, only hover targets with ExternalLink will be affected, not random strings"; 145 | return
{count}
; 146 | } 147 | ``` 148 | 149 | ```ts twoslash 150 | type PermissionResult = 151 | | { 152 | behavior: 'allow'; 153 | updatedInput: ToolInput; 154 | updatedPermissions?: PermissionUpdate[]; 155 | } 156 | | { 157 | behavior: 'deny'; 158 | message: string; 159 | interrupt?: boolean; 160 | }; 161 | 162 | type ToolInput = string[]; 163 | 164 | type PermissionUpdate = { 165 | name: string; 166 | permission: Array; 167 | }; 168 | 169 | // ---cut-before--- 170 | 171 | type CanUseTool = ( 172 | toolName: string, 173 | input: ToolInput, 174 | options: { 175 | signal: AbortSignal; 176 | suggestions?: PermissionUpdate[]; 177 | // ^? 178 | } 179 | ) => Promise; 180 | ``` 181 | 182 | ### Component 183 | 184 | Hello world from the `Component` section 185 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | Mintlify Logo 8 | 9 |
10 |

11 |

12 | 13 | Mint 14 | 15 |

16 |

17 |

18 | 19 | Open source docs builder that's beautiful, fast, and easy to work with. 20 | 21 |

22 |

23 | 24 | ![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) 25 | 26 |

27 |
28 | 29 | # Mintlify's markdown parser 30 | 31 | **@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. 32 | 33 | ## Installation 34 | 35 | ```bash 36 | # using npm 37 | npm i @mintlify/mdx 38 | 39 | # using yarn 40 | yarn add @mintlify/mdx 41 | 42 | # using pnpm 43 | pnpm add @mintlify/mdx 44 | ``` 45 | 46 | ## Examples 47 | 48 | ### Next.js pages router 49 | 50 | [You can check the example app here](https://github.com/mintlify/mdx/tree/main/examples/pages-router). 51 | 52 | 1. Call the `serialize` function inside `getStaticProps` and return the `mdxSource` object. 53 | 54 | ```tsx 55 | export const getStaticProps = (async () => { 56 | const mdxSource = await serialize({ 57 | source: '## Markdown H2', 58 | }); 59 | 60 | if ('error' in mdxSource) { 61 | // handle error case 62 | } 63 | 64 | return { props: { mdxSource } }; 65 | }) satisfies GetStaticProps<{ 66 | mdxSource: SerializeSuccess; 67 | }>; 68 | ``` 69 | 70 | 2. Pass the `mdxSource` object as props inside the `MDXComponent`. 71 | 72 | ```tsx 73 | export default function Page({ mdxSource }: InferGetStaticPropsType) { 74 | return ; 75 | } 76 | ``` 77 | 78 | ### Next.js app router 79 | 80 | [You can check the example app here](https://github.com/mintlify/mdx/tree/main/examples/app-router). 81 | 82 | 1. Use the `MDXRemote` component directly inside your async React Server Component. 83 | 84 | ```tsx 85 | import { MDXRemote } from '@mintlify/mdx'; 86 | 87 | export default async function Home() { 88 | const source: `--- 89 | title: Title 90 | --- 91 | 92 | ## Markdown H2 93 | `; 94 | 95 | return ( 96 |
97 | 98 |
99 | ); 100 | } 101 | ``` 102 | 103 | ## APIs 104 | 105 | Similar to [next-mdx-remote-client](https://github.com/ipikuka/next-mdx-remote-client), this package exports the following APIs: 106 | 107 | - `serialize` - a function that compiles MDX source to SerializeResult. 108 | - `MDXClient` - a component that renders SerializeSuccess on the client. 109 | - `MDXRemote` - a component that both serializes and renders the source - should be used inside async React Server Component. 110 | 111 | ### serialize 112 | 113 | ```tsx 114 | import { serialize } from '@mintlify/mdx'; 115 | 116 | const mdxSource = await serialize({ 117 | source: '## Markdown H2', 118 | mdxOptions: { 119 | remarkPlugins: [ 120 | // Remark plugins 121 | ], 122 | rehypePlugins: [ 123 | // Rehype plugins 124 | ], 125 | }, 126 | }); 127 | ``` 128 | 129 | ### MDXClient 130 | 131 | ```tsx 132 | 'use client'; 133 | 134 | import { MDXClient } from '@mintlify/mdx'; 135 | 136 | ; 144 | ``` 145 | 146 | ### MDXRemote 147 | 148 | ```tsx 149 | import { MDXRemote } from '@mintlify/mdx'; 150 | 151 | ; 167 | ``` 168 | 169 |
170 |

171 | 172 | Built with ❤︎ by 173 | 174 | Mintlify 175 | 176 | 177 |

178 |
179 | -------------------------------------------------------------------------------- /packages/mdx/src/plugins/rehype/twoslash/config.ts: -------------------------------------------------------------------------------- 1 | import { rendererRich, type TransformerTwoslashOptions } from '@shikijs/twoslash'; 2 | import type { Element, ElementContent } from 'hast'; 3 | import type { Code } from 'mdast'; 4 | import { fromMarkdown } from 'mdast-util-from-markdown'; 5 | import { gfmFromMarkdown } from 'mdast-util-gfm'; 6 | import { defaultHandlers, toHast } from 'mdast-util-to-hast'; 7 | import type { ShikiTransformerContextCommon } from 'shiki/types'; 8 | import ts from 'typescript'; 9 | 10 | const twoslashCompilerOptions: ts.CompilerOptions = { 11 | target: ts.ScriptTarget.ESNext, 12 | lib: ['ESNext', 'DOM', 'esnext', 'dom', 'es2020'], 13 | }; 14 | 15 | function onTwoslashError(err: unknown, code: string, lang: string) { 16 | console.error(JSON.stringify({ err, code, lang })); 17 | } 18 | 19 | function onShikiError(err: unknown, code: string, lang: string) { 20 | console.error(JSON.stringify({ err, code, lang })); 21 | } 22 | 23 | export function getTwoslashOptions( 24 | { linkMap }: { linkMap: Map } = { linkMap: new Map() } 25 | ): TransformerTwoslashOptions { 26 | return { 27 | onTwoslashError, 28 | onShikiError, 29 | // copied fuma's approach for custom popup 30 | // https://github.com/fuma-nama/fumadocs/blob/dev/packages/twoslash/src/index.ts 31 | renderer: rendererRich({ 32 | renderMarkdown, 33 | renderMarkdownInline, 34 | queryRendering: 'line', 35 | hast: { 36 | hoverToken: { 37 | tagName: 'Popup', 38 | children(input) { 39 | for (const rootElement of input) { 40 | if (!('children' in rootElement)) continue; 41 | for (const [i, element] of rootElement.children.entries()) { 42 | if (element.type !== 'text') continue; 43 | const href = linkMap.get(element.value); 44 | if (!href) continue; 45 | const linkProperties = { 46 | href, 47 | ...(checkIsExternalLink(href) && { 48 | target: '_blank', 49 | rel: 'noopener noreferrer', 50 | }), 51 | }; 52 | if (rootElement.type === 'element' && rootElement.tagName === 'PopupTrigger') { 53 | rootElement.properties = { ...rootElement.properties, ...linkProperties }; 54 | } else { 55 | const newElement: ElementContent = { 56 | type: 'element', 57 | tagName: 'a', 58 | properties: linkProperties, 59 | children: [{ type: 'text', value: element.value }], 60 | }; 61 | input.splice(i, 1, newElement); 62 | } 63 | } 64 | } 65 | return input; 66 | }, 67 | }, 68 | hoverPopup: { 69 | tagName: 'PopupContent', 70 | }, 71 | hoverCompose: ({ popup, token }) => [ 72 | popup, 73 | { 74 | type: 'element', 75 | tagName: 'PopupTrigger', 76 | properties: {}, 77 | children: [token], 78 | }, 79 | ], 80 | popupDocs: { 81 | class: 'prose-sm prose-gray dark:prose-dark twoslash-popup-docs', 82 | }, 83 | popupTypes: { 84 | tagName: 'span', 85 | class: 'mint-twoslash-popover-pre', 86 | children: (v) => { 87 | if (v.length === 1 && v[0]?.type === 'element' && v[0]?.tagName === 'pre') return v; 88 | 89 | return [ 90 | { 91 | type: 'element', 92 | tagName: 'code', 93 | properties: { 94 | class: 'twoslash-popup-code shiki', 95 | }, 96 | children: v, 97 | }, 98 | ]; 99 | }, 100 | }, 101 | popupDocsTags: { 102 | class: 'prose-sm prose-gray dark:prose-dark twoslash-popup-docs twoslash-popup-docs-tags', 103 | }, 104 | nodesHighlight: { 105 | class: 'highlighted-word twoslash-highlighted', 106 | }, 107 | }, 108 | }), 109 | langs: ['ts', 'typescript', 'js', 'javascript', 'tsx', 'jsx'], 110 | explicitTrigger: true, 111 | twoslashOptions: { 112 | compilerOptions: twoslashCompilerOptions, 113 | }, 114 | }; 115 | } 116 | 117 | /** https://github.com/fuma-nama/fumadocs/blob/2862a10c2d78b52c0a3f479ad21b255cc0031fc9/packages/twoslash/src/index.ts#L121-L150 */ 118 | function renderMarkdown(this: ShikiTransformerContextCommon, md: string): ElementContent[] { 119 | const mdast = fromMarkdown( 120 | md.replace(/{@link (?[^}]*)}/g, '$1'), // replace jsdoc links 121 | { mdastExtensions: [gfmFromMarkdown()] } 122 | ); 123 | 124 | return ( 125 | toHast(mdast, { 126 | handlers: { 127 | code: (state, node: Code) => { 128 | if (node.lang) { 129 | return this.codeToHast(node.value, { 130 | ...this.options, 131 | transformers: [], 132 | meta: { 133 | __raw: node.meta ?? undefined, 134 | }, 135 | lang: node.lang, 136 | }).children[0] as Element; 137 | } 138 | return defaultHandlers.code(state, node); 139 | }, 140 | }, 141 | }) as Element 142 | ).children; 143 | } 144 | 145 | /** https://github.com/fuma-nama/fumadocs/blob/2862a10c2d78b52c0a3f479ad21b255cc0031fc9/packages/twoslash/src/index.ts#L152-L168 */ 146 | function renderMarkdownInline( 147 | this: ShikiTransformerContextCommon, 148 | md: string, 149 | context?: string 150 | ): ElementContent[] { 151 | const text = context === 'tag:param' ? md.replace(/^(?[\w$-]+)/, '`$1` ') : md; 152 | 153 | const children = renderMarkdown.call(this, text); 154 | if (children.length === 1 && children[0]?.type === 'element' && children[0].tagName === 'p') 155 | return children[0].children; 156 | return children; 157 | } 158 | 159 | export function parseLineComment(line: string): { word: string; href: string } | undefined { 160 | line = line.trim(); 161 | if (!line.startsWith('//')) return; 162 | 163 | line = line.replace(/^[\/\s]+/, '').trim(); 164 | if (!line.startsWith('@link ') && !line.startsWith('@link:')) return; 165 | 166 | line = line.replace('@link:', '@link '); 167 | const parts = line.split('@link ')[1]; 168 | if (!parts) return; 169 | 170 | const words = parts.split(' ').filter(Boolean); 171 | if (words.length === 1 && words[0]) { 172 | let word = words[0]; 173 | if (word.endsWith(':')) word = word.slice(0, -1); 174 | const lowercaseWord = word.toLowerCase(); 175 | const href = word.startsWith('#') ? lowercaseWord : `#${encodeURIComponent(lowercaseWord)}`; 176 | return { word, href }; 177 | } else if (words.length === 2 && words[0] && words[1]) { 178 | let word = words[0]; 179 | if (word.endsWith(':')) word = word.slice(0, -1); 180 | const href = words[1]; 181 | if (!href.startsWith('#') && !href.startsWith('https://')) return; 182 | return { word, href }; 183 | } 184 | 185 | return; 186 | } 187 | 188 | type Url = `https://${string}`; 189 | function checkIsExternalLink(href: string | undefined): href is Url { 190 | let isExternalLink = false; 191 | try { 192 | if (href && URL.canParse(href)) isExternalLink = true; 193 | } catch {} 194 | return isExternalLink; 195 | } 196 | -------------------------------------------------------------------------------- /packages/mdx/src/plugins/rehype/rehypeSyntaxHighlighting.ts: -------------------------------------------------------------------------------- 1 | import { transformerTwoslash } from '@shikijs/twoslash'; 2 | import { type } from 'arktype'; 3 | import type { Element, Root } from 'hast'; 4 | import { toString } from 'hast-util-to-string'; 5 | import type { MdxJsxFlowElementHast, MdxJsxTextElementHast } from 'mdast-util-mdx-jsx'; 6 | import { createHighlighter, type Highlighter } from 'shiki'; 7 | import type { Plugin } from 'unified'; 8 | import { visit } from 'unist-util-visit'; 9 | 10 | import { 11 | type ShikiLang, 12 | type ShikiTheme, 13 | shikiColorReplacements, 14 | DEFAULT_LANG_ALIASES, 15 | DEFAULT_LANG, 16 | DEFAULT_DARK_THEME, 17 | DEFAULT_LIGHT_THEME, 18 | DEFAULT_THEMES, 19 | DEFAULT_LANGS, 20 | SHIKI_TRANSFORMERS, 21 | UNIQUE_LANGS, 22 | } from './shiki-constants.js'; 23 | import { TextMateGrammar, TextMateGrammarType } from './shiki/custom-language.js'; 24 | import { getTwoslashOptions, parseLineComment } from './twoslash/config.js'; 25 | import { getLanguage } from './utils.js'; 26 | 27 | export type RehypeSyntaxHighlightingOptions = { 28 | theme?: ShikiTheme; 29 | themes?: Record<'light' | 'dark', ShikiTheme>; 30 | codeStyling?: 'dark' | 'system' | 'light' | Record | null; 31 | linkMap?: Map; 32 | customLanguages?: string[]; 33 | }; 34 | 35 | let highlighterPromise: Promise | null = null; 36 | 37 | async function getHighlighter(): Promise { 38 | if (!highlighterPromise) { 39 | highlighterPromise = createHighlighter({ 40 | themes: DEFAULT_THEMES, 41 | langs: DEFAULT_LANGS, 42 | }); 43 | } 44 | return highlighterPromise; 45 | } 46 | 47 | export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?], Root, Root> = ( 48 | options = {} 49 | ) => { 50 | return async (tree) => { 51 | const nodesToProcess: Promise[] = []; 52 | const customLanguageNames: string[] = []; 53 | 54 | const themesToLoad: ShikiTheme[] = []; 55 | if (options.themes) { 56 | themesToLoad.push(options.themes.dark); 57 | themesToLoad.push(options.themes.light); 58 | } else if (options.theme) { 59 | themesToLoad.push(options.theme); 60 | } 61 | 62 | const highlighter = await getHighlighter(); 63 | 64 | await Promise.all([ 65 | ...themesToLoad 66 | .filter( 67 | (theme): theme is Exclude => 68 | !DEFAULT_THEMES.includes(theme) && theme !== 'css-variables' 69 | ) 70 | .map((theme) => highlighter.loadTheme(theme)), 71 | ...(options.customLanguages?.map(async (unparsedLang) => { 72 | const parsedLang = JSON.parse(unparsedLang); 73 | const lang = TextMateGrammar(parsedLang); 74 | if (lang instanceof type.errors) { 75 | console.error(lang.summary); 76 | return; 77 | } 78 | await highlighter.loadLanguage(lang); 79 | const possibleNames = [lang.name, lang.displayName, ...(lang.aliases ?? [])]; 80 | customLanguageNames.push(...possibleNames.filter((l) => l != undefined)); 81 | }) ?? []), 82 | ]); 83 | 84 | visit(tree, 'element', (node, index, parent) => { 85 | const child = node.children[0]; 86 | if ( 87 | !parent || 88 | index === undefined || 89 | node.type !== 'element' || 90 | node.tagName !== 'pre' || 91 | !child || 92 | child.type !== 'element' || 93 | child.tagName !== 'code' 94 | ) { 95 | return; 96 | } 97 | 98 | // set the metadata of `node` (which is a pre element) to that of 99 | // `child` (which is the code element that likely contains all the metadata) 100 | if (!Object.keys(node.properties).length) { 101 | node.properties = child.properties; 102 | } 103 | if (!node.data) { 104 | node.data = child.data; 105 | } 106 | 107 | let lang = 108 | getLanguage(node, DEFAULT_LANG_ALIASES) ?? 109 | getLanguage(child, DEFAULT_LANG_ALIASES) ?? 110 | DEFAULT_LANG; 111 | 112 | if ( 113 | !DEFAULT_LANGS.includes(lang) && 114 | !customLanguageNames.includes(lang) && 115 | UNIQUE_LANGS.includes(lang) 116 | ) { 117 | nodesToProcess.push( 118 | highlighter.loadLanguage(lang).then(() => { 119 | traverseNode({ node, index, parent, highlighter, lang, options }); 120 | }) 121 | ); 122 | } else { 123 | if (!UNIQUE_LANGS.includes(lang) && !customLanguageNames.includes(lang)) { 124 | lang = DEFAULT_LANG; 125 | } 126 | traverseNode({ node, index, parent, highlighter, lang, options }); 127 | } 128 | }); 129 | await Promise.all(nodesToProcess); 130 | }; 131 | }; 132 | 133 | function traverseNode({ 134 | node, 135 | index, 136 | parent, 137 | highlighter, 138 | lang, 139 | options, 140 | }: { 141 | node: Element; 142 | index: number; 143 | parent: Element | Root | MdxJsxTextElementHast | MdxJsxFlowElementHast; 144 | highlighter: Highlighter; 145 | lang: ShikiLang; 146 | options: RehypeSyntaxHighlightingOptions; 147 | }) { 148 | try { 149 | let code = toString(node); 150 | 151 | const meta = node.data?.meta?.split(' ') ?? []; 152 | const twoslashIndex = meta.findIndex((str) => str.toLowerCase() === 'twoslash'); 153 | const shouldUseTwoslash = twoslashIndex > -1; 154 | 155 | if (node.data && node.data.meta && shouldUseTwoslash) { 156 | meta.splice(twoslashIndex, 1); 157 | node.data.meta = meta.join(' ').trim() || undefined; 158 | } 159 | 160 | const linkMap = options.linkMap ?? new Map(); 161 | if (shouldUseTwoslash) { 162 | const splitCode = code.split('\n'); 163 | 164 | for (const [i, line] of splitCode.entries()) { 165 | const parsedLineComment = parseLineComment(line); 166 | if (!parsedLineComment) continue; 167 | const { word, href } = parsedLineComment; 168 | linkMap.set(word, href); 169 | splitCode.splice(i, 1); 170 | } 171 | 172 | code = splitCode.join('\n'); 173 | } 174 | 175 | const twoslashOptions = getTwoslashOptions({ linkMap }); 176 | 177 | const hast = highlighter.codeToHast(code, { 178 | lang: lang ?? DEFAULT_LANG, 179 | meta: shouldUseTwoslash ? { __raw: 'twoslash' } : undefined, 180 | themes: { 181 | light: 182 | options.themes?.light ?? 183 | options.theme ?? 184 | (options.codeStyling === 'dark' ? DEFAULT_DARK_THEME : DEFAULT_LIGHT_THEME), 185 | dark: options.themes?.dark ?? options.theme ?? DEFAULT_DARK_THEME, 186 | }, 187 | colorReplacements: shikiColorReplacements, 188 | tabindex: false, 189 | tokenizeMaxLineLength: 1000, 190 | transformers: [...SHIKI_TRANSFORMERS, transformerTwoslash(twoslashOptions)], 191 | }); 192 | 193 | const codeElement = hast.children[0] as Element; 194 | if (!codeElement) return; 195 | 196 | const preChild = codeElement.children[0] as Element; 197 | 198 | node.data = node.data ?? {}; 199 | codeElement.data = node.data; 200 | codeElement.properties.language = lang; 201 | if (preChild) { 202 | preChild.data = node.data; 203 | preChild.properties.language = lang; 204 | } 205 | parent.children.splice(index, 1, codeElement); 206 | } catch (err) { 207 | if (err instanceof Error && /Unknown language/.test(err.message)) { 208 | return; 209 | } 210 | throw err; 211 | } 212 | } 213 | 214 | export { TextMateGrammar, type TextMateGrammarType }; 215 | -------------------------------------------------------------------------------- /examples/app-router/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --primary-light: 85 215 153; 8 | } 9 | } 10 | 11 | /* modified from https://github.com/shikijs/shiki/blob/main/packages/twoslash/style-rich.css */ 12 | /* ===== Basic ===== */ 13 | :root { 14 | --twoslash-border-color: #8888; 15 | --twoslash-underline-color: currentColor; 16 | --twoslash-highlighted-border: #c37d0d50; 17 | --twoslash-highlighted-bg: #c37d0d20; 18 | --twoslash-popup-bg: #f8f8f8; 19 | --twoslash-popup-color: inherit; 20 | --twoslash-popup-shadow: rgba(0, 0, 0, 0.08) 0px 1px 4px; 21 | --twoslash-docs-color: #888; 22 | --twoslash-docs-font: sans-serif; 23 | --twoslash-code-font: inherit; 24 | --twoslash-code-font-size: 1em; 25 | --twoslash-matched-color: inherit; 26 | --twoslash-unmatched-color: #888; 27 | --twoslash-cursor-color: #8888; 28 | --twoslash-error-color: #d45656; 29 | --twoslash-error-bg: #d4565620; 30 | --twoslash-warn-color: #c37d0d; 31 | --twoslash-warn-bg: #c37d0d20; 32 | --twoslash-tag-color: #3772cf; 33 | --twoslash-tag-bg: #3772cf20; 34 | --twoslash-tag-warn-color: var(--twoslash-warn-color); 35 | --twoslash-tag-warn-bg: var(--twoslash-warn-bg); 36 | --twoslash-tag-annotate-color: #1ba673; 37 | --twoslash-tag-annotate-bg: #1ba67320; 38 | --twoslash-text-size: 0.8rem; 39 | --twoslash-docs-tag-style: italic; 40 | } 41 | 42 | /* Respect people's wishes to not have animations */ 43 | @media (prefers-reduced-motion: reduce) { 44 | .twoslash * { 45 | transition: none !important; 46 | } 47 | } 48 | 49 | /* ===== Hover Info ===== */ 50 | .twoslash:hover .twoslash-hover { 51 | border-color: var(--twoslash-underline-color); 52 | } 53 | 54 | .twoslash .twoslash-hover { 55 | border-bottom: 1px dotted transparent; 56 | transition-timing-function: ease; 57 | transition: border-color 0.3s; 58 | position: relative; 59 | } 60 | 61 | 62 | /* ===== Popup Override ===== */ 63 | .mint-twoslash-popover { 64 | background: var(--twoslash-popup-bg); 65 | color: var(--twoslash-popup-color); 66 | border: 1px solid var(--twoslash-border-color); 67 | border-radius: 4px; 68 | pointer-events: auto; 69 | text-align: left; 70 | box-shadow: var(--twoslash-popup-shadow); 71 | display: inline-flex; 72 | flex-direction: column; 73 | } 74 | 75 | .mint-twoslash-popover-pre { 76 | display: flex; 77 | font-size: var(--twoslash-text-size); 78 | font-family: var(--twoslash-code-font); 79 | } 80 | 81 | .mint-twoslash-popover:hover { 82 | user-select: auto; 83 | } 84 | 85 | .twoslash .twoslash-popup-arrow { 86 | display: none; 87 | } 88 | 89 | .twoslash-popup-code, 90 | .twoslash-popup-error, 91 | .twoslash-popup-docs { 92 | padding: 6px 8px !important; 93 | } 94 | 95 | .mint-twoslash-popover .twoslash-popup-docs { 96 | color: var(--twoslash-docs-color); 97 | font-family: var(--twoslash-docs-font); 98 | font-size: var(--twoslash-text-size); 99 | max-width: unset; 100 | } 101 | 102 | .mint-twoslash-popover .twoslash-popup-error { 103 | color: var(--twoslash-error-color); 104 | background-color: var(--twoslash-error-bg); 105 | font-family: var(--twoslash-docs-font); 106 | font-size: var(--twoslash-text-size); 107 | } 108 | 109 | .mint-twoslash-popover .twoslash-popup-docs-tags { 110 | display: flex; 111 | flex-direction: column; 112 | font-family: var(--twoslash-docs-font); 113 | } 114 | 115 | .mint-twoslash-popover .twoslash-popup-docs-tag-name { 116 | margin-right: 0.5em; 117 | font-style: var(--twoslash-docs-tag-style); 118 | } 119 | 120 | .mint-twoslash-popover .twoslash-popup-docs-tag-name { 121 | font-family: var(--twoslash-code-font); 122 | } 123 | 124 | /* ===== Query Line ===== */ 125 | .mint-twoslash-popover .twoslash-query-line .twoslash-popup-container { 126 | position: relative; 127 | margin-bottom: 1.4em; 128 | transform: translateY(0.6em); 129 | } 130 | 131 | /* ===== Error Line ===== */ 132 | .mint-twoslash-popover .twoslash-error-line { 133 | position: relative; 134 | background-color: var(--twoslash-error-bg); 135 | border-left: 3px solid var(--twoslash-error-color); 136 | color: var(--twoslash-error-color); 137 | padding: 6px 12px; 138 | margin: 0.2em 0; 139 | min-width: 100%; 140 | width: max-content; 141 | } 142 | 143 | .mint-twoslash-popover .twoslash-error-line.twoslash-error-level-warning { 144 | background-color: var(--twoslash-warn-bg); 145 | border-left: 3px solid var(--twoslash-warn-color); 146 | color: var(--twoslash-warn-color); 147 | } 148 | 149 | .mint-twoslash-popover .twoslash-error { 150 | 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; 151 | padding-bottom: 2px; 152 | } 153 | 154 | .mint-twoslash-popover .twoslash-error.twoslash-error-level-warning { 155 | 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; 156 | padding-bottom: 2px; 157 | } 158 | 159 | /* ===== Completeions ===== */ 160 | .mint-twoslash-popover .twoslash-completion-cursor { 161 | position: relative; 162 | } 163 | 164 | .mint-twoslash-popover .twoslash-completion-cursor .twoslash-completion-list { 165 | user-select: none; 166 | position: absolute; 167 | top: 0; 168 | left: 0; 169 | transform: translate(0, 1.2em); 170 | margin: 3px 0 0 -1px; 171 | display: inline-block; 172 | z-index: 8; 173 | box-shadow: var(--twoslash-popup-shadow); 174 | background: var(--twoslash-popup-bg); 175 | border: 1px solid var(--twoslash-border-color); 176 | } 177 | 178 | .twoslash-completion-list { 179 | width: 240px; 180 | font-size: var(--twoslash-text-size); 181 | padding: 4px; 182 | display: flex; 183 | flex-direction: column; 184 | gap: 4px; 185 | } 186 | 187 | .twoslash-completion-list:hover { 188 | user-select: auto; 189 | } 190 | 191 | .twoslash-completion-list::before { 192 | background-color: var(--twoslash-cursor-color); 193 | width: 2px; 194 | position: absolute; 195 | top: -1.6em; 196 | height: 1.4em; 197 | left: -1px; 198 | content: ' '; 199 | } 200 | 201 | .twoslash-completion-list li { 202 | overflow: hidden; 203 | display: flex; 204 | align-items: center; 205 | gap: 0.25em; 206 | line-height: 1em; 207 | } 208 | 209 | .twoslash-completion-list li span.twoslash-completions-unmatched { 210 | color: var(--twoslash-unmatched-color); 211 | } 212 | 213 | .twoslash-completion-list .deprecated { 214 | text-decoration: line-through; 215 | opacity: 0.5; 216 | } 217 | 218 | .twoslash-completion-list li span.twoslash-completions-matched { 219 | color: var(--twoslash-matched-color); 220 | } 221 | 222 | /* Highlights */ 223 | .twoslash-highlighted { 224 | background-color: var(--twoslash-highlighted-bg); 225 | border: 1px solid var(--twoslash-highlighted-border); 226 | padding: 1px 2px; 227 | margin: -1px -3px; 228 | border-radius: 4px; 229 | } 230 | 231 | /* Icons */ 232 | .twoslash-completion-list .twoslash-completions-icon { 233 | color: var(--twoslash-unmatched-color); 234 | width: 1em; 235 | flex: none; 236 | } 237 | 238 | /* Custom Tags */ 239 | .mint-twoslash-popover .twoslash-tag-line { 240 | position: relative; 241 | background-color: var(--twoslash-tag-bg); 242 | border-left: 3px solid var(--twoslash-tag-color); 243 | color: var(--twoslash-tag-color); 244 | padding: 6px 10px; 245 | margin: 0.2em 0; 246 | display: flex; 247 | align-items: center; 248 | gap: 0.3em; 249 | min-width: 100%; 250 | width: max-content; 251 | } 252 | 253 | .mint-twoslash-popover .twoslash-tag-line .twoslash-tag-icon { 254 | width: 1.1em; 255 | color: inherit; 256 | } 257 | 258 | .mint-twoslash-popover .twoslash-tag-line.twoslash-tag-error-line { 259 | background-color: var(--twoslash-error-bg); 260 | border-left: 3px solid var(--twoslash-error-color); 261 | color: var(--twoslash-error-color); 262 | } 263 | 264 | .mint-twoslash-popover .twoslash-tag-line.twoslash-tag-warn-line { 265 | background-color: var(--twoslash-tag-warn-bg); 266 | border-left: 3px solid var(--twoslash-tag-warn-color); 267 | color: var(--twoslash-tag-warn-color); 268 | } 269 | 270 | .mint-twoslash-popover .twoslash-tag-line.twoslash-tag-annotate-line { 271 | background-color: var(--twoslash-tag-annotate-bg); 272 | border-left: 3px solid var(--twoslash-tag-annotate-color); 273 | color: var(--twoslash-tag-annotate-color); 274 | } 275 | -------------------------------------------------------------------------------- /examples/pages-router/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --primary-light: 85 215 153; 8 | } 9 | } 10 | 11 | /* modified from https://github.com/shikijs/shiki/blob/main/packages/twoslash/style-rich.css */ 12 | /* ===== Basic ===== */ 13 | :root { 14 | --twoslash-border-color: #8888; 15 | --twoslash-underline-color: currentColor; 16 | --twoslash-highlighted-border: #c37d0d50; 17 | --twoslash-highlighted-bg: #c37d0d20; 18 | --twoslash-popup-bg: #f8f8f8; 19 | --twoslash-popup-color: inherit; 20 | --twoslash-popup-shadow: rgba(0, 0, 0, 0.08) 0px 1px 4px; 21 | --twoslash-docs-color: #888; 22 | --twoslash-docs-font: sans-serif; 23 | --twoslash-code-font: inherit; 24 | --twoslash-code-font-size: 1em; 25 | --twoslash-matched-color: inherit; 26 | --twoslash-unmatched-color: #888; 27 | --twoslash-cursor-color: #8888; 28 | --twoslash-error-color: #d45656; 29 | --twoslash-error-bg: #d4565620; 30 | --twoslash-warn-color: #c37d0d; 31 | --twoslash-warn-bg: #c37d0d20; 32 | --twoslash-tag-color: #3772cf; 33 | --twoslash-tag-bg: #3772cf20; 34 | --twoslash-tag-warn-color: var(--twoslash-warn-color); 35 | --twoslash-tag-warn-bg: var(--twoslash-warn-bg); 36 | --twoslash-tag-annotate-color: #1ba673; 37 | --twoslash-tag-annotate-bg: #1ba67320; 38 | --twoslash-text-size: 0.8rem; 39 | --twoslash-docs-tag-style: italic; 40 | } 41 | 42 | /* Respect people's wishes to not have animations */ 43 | @media (prefers-reduced-motion: reduce) { 44 | .twoslash * { 45 | transition: none !important; 46 | } 47 | } 48 | 49 | /* ===== Hover Info ===== */ 50 | .twoslash:hover .twoslash-hover { 51 | border-color: var(--twoslash-underline-color); 52 | } 53 | 54 | .twoslash .twoslash-hover { 55 | border-bottom: 1px dotted transparent; 56 | transition-timing-function: ease; 57 | transition: border-color 0.3s; 58 | position: relative; 59 | } 60 | 61 | 62 | /* ===== Popup Override ===== */ 63 | .mint-twoslash-popover { 64 | background: var(--twoslash-popup-bg); 65 | color: var(--twoslash-popup-color); 66 | border: 1px solid var(--twoslash-border-color); 67 | border-radius: 4px; 68 | pointer-events: auto; 69 | text-align: left; 70 | box-shadow: var(--twoslash-popup-shadow); 71 | display: inline-flex; 72 | flex-direction: column; 73 | } 74 | 75 | .mint-twoslash-popover-pre { 76 | display: flex; 77 | font-size: var(--twoslash-text-size); 78 | font-family: var(--twoslash-code-font); 79 | } 80 | 81 | .mint-twoslash-popover:hover { 82 | user-select: auto; 83 | } 84 | 85 | .twoslash .twoslash-popup-arrow { 86 | display: none; 87 | } 88 | 89 | .twoslash-popup-code, 90 | .twoslash-popup-error, 91 | .twoslash-popup-docs { 92 | padding: 6px 8px !important; 93 | } 94 | 95 | .mint-twoslash-popover .twoslash-popup-docs { 96 | color: var(--twoslash-docs-color); 97 | font-family: var(--twoslash-docs-font); 98 | font-size: var(--twoslash-text-size); 99 | max-width: unset; 100 | } 101 | 102 | .mint-twoslash-popover .twoslash-popup-error { 103 | color: var(--twoslash-error-color); 104 | background-color: var(--twoslash-error-bg); 105 | font-family: var(--twoslash-docs-font); 106 | font-size: var(--twoslash-text-size); 107 | } 108 | 109 | .mint-twoslash-popover .twoslash-popup-docs-tags { 110 | display: flex; 111 | flex-direction: column; 112 | font-family: var(--twoslash-docs-font); 113 | } 114 | 115 | .mint-twoslash-popover .twoslash-popup-docs-tag-name { 116 | margin-right: 0.5em; 117 | font-style: var(--twoslash-docs-tag-style); 118 | } 119 | 120 | .mint-twoslash-popover .twoslash-popup-docs-tag-name { 121 | font-family: var(--twoslash-code-font); 122 | } 123 | 124 | /* ===== Query Line ===== */ 125 | .mint-twoslash-popover .twoslash-query-line .twoslash-popup-container { 126 | position: relative; 127 | margin-bottom: 1.4em; 128 | transform: translateY(0.6em); 129 | } 130 | 131 | /* ===== Error Line ===== */ 132 | .mint-twoslash-popover .twoslash-error-line { 133 | position: relative; 134 | background-color: var(--twoslash-error-bg); 135 | border-left: 3px solid var(--twoslash-error-color); 136 | color: var(--twoslash-error-color); 137 | padding: 6px 12px; 138 | margin: 0.2em 0; 139 | min-width: 100%; 140 | width: max-content; 141 | } 142 | 143 | .mint-twoslash-popover .twoslash-error-line.twoslash-error-level-warning { 144 | background-color: var(--twoslash-warn-bg); 145 | border-left: 3px solid var(--twoslash-warn-color); 146 | color: var(--twoslash-warn-color); 147 | } 148 | 149 | .mint-twoslash-popover .twoslash-error { 150 | 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; 151 | padding-bottom: 2px; 152 | } 153 | 154 | .mint-twoslash-popover .twoslash-error.twoslash-error-level-warning { 155 | 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; 156 | padding-bottom: 2px; 157 | } 158 | 159 | /* ===== Completeions ===== */ 160 | .mint-twoslash-popover .twoslash-completion-cursor { 161 | position: relative; 162 | } 163 | 164 | .mint-twoslash-popover .twoslash-completion-cursor .twoslash-completion-list { 165 | user-select: none; 166 | position: absolute; 167 | top: 0; 168 | left: 0; 169 | transform: translate(0, 1.2em); 170 | margin: 3px 0 0 -1px; 171 | display: inline-block; 172 | z-index: 8; 173 | box-shadow: var(--twoslash-popup-shadow); 174 | background: var(--twoslash-popup-bg); 175 | border: 1px solid var(--twoslash-border-color); 176 | } 177 | 178 | .twoslash-completion-list { 179 | width: 240px; 180 | font-size: var(--twoslash-text-size); 181 | padding: 4px; 182 | display: flex; 183 | flex-direction: column; 184 | gap: 4px; 185 | } 186 | 187 | .twoslash-completion-list:hover { 188 | user-select: auto; 189 | } 190 | 191 | .twoslash-completion-list::before { 192 | background-color: var(--twoslash-cursor-color); 193 | width: 2px; 194 | position: absolute; 195 | top: -1.6em; 196 | height: 1.4em; 197 | left: -1px; 198 | content: ' '; 199 | } 200 | 201 | .twoslash-completion-list li { 202 | overflow: hidden; 203 | display: flex; 204 | align-items: center; 205 | gap: 0.25em; 206 | line-height: 1em; 207 | } 208 | 209 | .twoslash-completion-list li span.twoslash-completions-unmatched { 210 | color: var(--twoslash-unmatched-color); 211 | } 212 | 213 | .twoslash-completion-list .deprecated { 214 | text-decoration: line-through; 215 | opacity: 0.5; 216 | } 217 | 218 | .twoslash-completion-list li span.twoslash-completions-matched { 219 | color: var(--twoslash-matched-color); 220 | } 221 | 222 | /* Highlights */ 223 | .twoslash-highlighted { 224 | background-color: var(--twoslash-highlighted-bg); 225 | border: 1px solid var(--twoslash-highlighted-border); 226 | padding: 1px 2px; 227 | margin: -1px -3px; 228 | border-radius: 4px; 229 | } 230 | 231 | /* Icons */ 232 | .twoslash-completion-list .twoslash-completions-icon { 233 | color: var(--twoslash-unmatched-color); 234 | width: 1em; 235 | flex: none; 236 | } 237 | 238 | /* Custom Tags */ 239 | .mint-twoslash-popover .twoslash-tag-line { 240 | position: relative; 241 | background-color: var(--twoslash-tag-bg); 242 | border-left: 3px solid var(--twoslash-tag-color); 243 | color: var(--twoslash-tag-color); 244 | padding: 6px 10px; 245 | margin: 0.2em 0; 246 | display: flex; 247 | align-items: center; 248 | gap: 0.3em; 249 | min-width: 100%; 250 | width: max-content; 251 | } 252 | 253 | .mint-twoslash-popover .twoslash-tag-line .twoslash-tag-icon { 254 | width: 1.1em; 255 | color: inherit; 256 | } 257 | 258 | .mint-twoslash-popover .twoslash-tag-line.twoslash-tag-error-line { 259 | background-color: var(--twoslash-error-bg); 260 | border-left: 3px solid var(--twoslash-error-color); 261 | color: var(--twoslash-error-color); 262 | } 263 | 264 | .mint-twoslash-popover .twoslash-tag-line.twoslash-tag-warn-line { 265 | background-color: var(--twoslash-tag-warn-bg); 266 | border-left: 3px solid var(--twoslash-tag-warn-color); 267 | color: var(--twoslash-tag-warn-color); 268 | } 269 | 270 | .mint-twoslash-popover .twoslash-tag-line.twoslash-tag-annotate-line { 271 | background-color: var(--twoslash-tag-annotate-bg); 272 | border-left: 3px solid var(--twoslash-tag-annotate-color); 273 | color: var(--twoslash-tag-annotate-color); 274 | } 275 | -------------------------------------------------------------------------------- /packages/mdx/src/plugins/rehype/shiki-constants.ts: -------------------------------------------------------------------------------- 1 | import { 2 | transformerNotationHighlight, 3 | transformerNotationFocus, 4 | transformerMetaHighlight, 5 | transformerNotationDiff, 6 | } from '@shikijs/transformers'; 7 | import type { ShikiTransformer } from '@shikijs/types'; 8 | import { createCssVariablesTheme } from 'shiki/core'; 9 | import type { BundledLanguage, ThemeRegistration } from 'shiki/types'; 10 | 11 | export const LINE_HIGHLIGHT_CLASS_NAME = 'line-highlight'; 12 | export const LINE_FOCUS_CLASS_NAME = 'line-focus'; 13 | export const LINE_DIFF_ADD_CLASS_NAME = 'line-diff line-add'; 14 | export const LINE_DIFF_REMOVE_CLASS_NAME = 'line-diff line-remove'; 15 | 16 | export type ShikiLang = BundledLanguage | 'ansi' | 'text'; 17 | export type ShikiTheme = (typeof SHIKI_THEMES)[number]; 18 | 19 | export const SHIKI_CSS_THEME = createCssVariablesTheme({ 20 | name: 'css-variables', 21 | variablePrefix: '--mint-', 22 | variableDefaults: { 23 | 'color-text': '#171717', 24 | 'color-background': 'transparent', 25 | 'token-constant': '#171717', 26 | 'token-string': '#297a3a', 27 | 'token-comment': '#666666', 28 | 'token-keyword': '#bd2864', 29 | 'token-parameter': '#a35200', 30 | 'token-function': '#0068d6', 31 | 'token-string-expression': '#297a3a', 32 | 'token-punctuation': '#171717', 33 | 'token-link': '#297a3a', 34 | 35 | 'ansi-black': '#000000', 36 | 'ansi-black-dim': '#00000080', 37 | 'ansi-red': '#bb0000', 38 | 'ansi-red-dim': '#bb000080', 39 | 'ansi-green': '#00bb00', 40 | 'ansi-green-dim': '#00bb0080', 41 | 'ansi-yellow': '#bbbb00', 42 | 'ansi-yellow-dim': '#bbbb0080', 43 | 'ansi-blue': '#0000bb', 44 | 'ansi-blue-dim': '#0000bb80', 45 | 'ansi-magenta': '#ff00ff', 46 | 'ansi-magenta-dim': '#ff00ff80', 47 | 'ansi-cyan': '#00bbbb', 48 | 'ansi-cyan-dim': '#00bbbb80', 49 | 'ansi-white': '#eeeeee', 50 | 'ansi-white-dim': '#eeeeee80', 51 | 'ansi-bright-black': '#555555', 52 | 'ansi-bright-black-dim': '#55555580', 53 | 'ansi-bright-red': '#ff5555', 54 | 'ansi-bright-red-dim': '#ff555580', 55 | 'ansi-bright-green': '#00ff00', 56 | 'ansi-bright-green-dim': '#00ff0080', 57 | 'ansi-bright-yellow': '#ffff55', 58 | 'ansi-bright-yellow-dim': '#ffff5580', 59 | 'ansi-bright-blue': '#5555ff', 60 | 'ansi-bright-blue-dim': '#5555ff80', 61 | 'ansi-bright-magenta': '#ff55ff', 62 | 'ansi-bright-magenta-dim': '#ff55ff80', 63 | 'ansi-bright-cyan': '#55ffff', 64 | 'ansi-bright-cyan-dim': '#55ffff80', 65 | 'ansi-bright-white': '#ffffff', 66 | 'ansi-bright-white-dim': '#ffffff80', 67 | }, 68 | fontStyle: true, 69 | }); 70 | 71 | export const DEFAULT_LANG = 'text' as const; 72 | export const DEFAULT_DARK_THEME: ShikiTheme = 'dark-plus' as const; 73 | export const DEFAULT_LIGHT_THEME: ShikiTheme = 'github-light-default' as const; 74 | export const DEFAULT_THEMES: [ShikiTheme, ShikiTheme, ThemeRegistration] = [ 75 | DEFAULT_LIGHT_THEME, 76 | DEFAULT_DARK_THEME, 77 | SHIKI_CSS_THEME, 78 | ] as const; 79 | 80 | export const shikiColorReplacements: Partial>> = 81 | { 82 | 'dark-plus': { 83 | '#1e1e1e': '#0B0C0E', 84 | }, 85 | }; 86 | 87 | export const DEFAULT_LANG_ALIASES: Record = { 88 | ansi: 'ansi', 89 | abap: 'abap', 90 | 'actionscript-3': 'actionscript-3', 91 | ada: 'ada', 92 | 'angular-html': 'angular-html', 93 | 'angular-ts': 'angular-ts', 94 | apache: 'apache', 95 | apex: 'apex', 96 | apl: 'apl', 97 | applescript: 'applescript', 98 | ara: 'ara', 99 | asciidoc: 'asciidoc', 100 | adoc: 'asciidoc', 101 | asm: 'asm', 102 | astro: 'astro', 103 | awk: 'awk', 104 | ballerina: 'ballerina', 105 | bat: 'bat', 106 | batch: 'bat', 107 | beancount: 'beancount', 108 | berry: 'berry', 109 | be: 'berry', 110 | bibtex: 'bibtex', 111 | bicep: 'bicep', 112 | blade: 'blade', 113 | bsl: 'bsl', 114 | '1c': 'bsl', 115 | c: 'c', 116 | h: 'c', 117 | cadence: 'cadence', 118 | cdc: 'cadence', 119 | cairo: 'cairo', 120 | clarity: 'clarity', 121 | clojure: 'clojure', 122 | clj: 'clojure', 123 | cmake: 'cmake', 124 | cobol: 'cobol', 125 | codeowners: 'codeowners', 126 | codeql: 'codeql', 127 | ql: 'codeql', 128 | coffee: 'coffee', 129 | coffeescript: 'coffee', 130 | 'common-lisp': 'common-lisp', 131 | lisp: 'common-lisp', 132 | coq: 'coq', 133 | cpp: 'cpp', 134 | cc: 'cpp', 135 | hh: 'cpp', 136 | 'c++': 'cpp', 137 | crystal: 'crystal', 138 | csharp: 'csharp', 139 | 'c#': 'csharp', 140 | cs: 'csharp', 141 | css: 'css', 142 | csv: 'csv', 143 | cue: 'cue', 144 | cypher: 'cypher', 145 | cql: 'cypher', 146 | d: 'd', 147 | dart: 'dart', 148 | dax: 'dax', 149 | desktop: 'desktop', 150 | diff: 'diff', 151 | docker: 'docker', 152 | dockerfile: 'docker', 153 | dotenv: 'dotenv', 154 | 'dream-maker': 'dream-maker', 155 | edge: 'edge', 156 | elixir: 'elixir', 157 | elm: 'elm', 158 | 'emacs-lisp': 'emacs-lisp', 159 | elisp: 'emacs-lisp', 160 | erb: 'erb', 161 | erlang: 'erlang', 162 | erl: 'erlang', 163 | fennel: 'fennel', 164 | fish: 'fish', 165 | fluent: 'fluent', 166 | ftl: 'fluent', 167 | 'fortran-fixed-form': 'fortran-fixed-form', 168 | f: 'fortran-fixed-form', 169 | for: 'fortran-fixed-form', 170 | f77: 'fortran-fixed-form', 171 | 'fortran-free-form': 'fortran-free-form', 172 | f90: 'fortran-free-form', 173 | f95: 'fortran-free-form', 174 | f03: 'fortran-free-form', 175 | f08: 'fortran-free-form', 176 | f18: 'fortran-free-form', 177 | fsharp: 'fsharp', 178 | 'f#': 'fsharp', 179 | fs: 'fsharp', 180 | gdresource: 'gdresource', 181 | gdscript: 'gdscript', 182 | gdshader: 'gdshader', 183 | genie: 'genie', 184 | gherkin: 'gherkin', 185 | 'git-commit': 'git-commit', 186 | 'git-rebase': 'git-rebase', 187 | gleam: 'gleam', 188 | 'glimmer-js': 'glimmer-js', 189 | gjs: 'glimmer-js', 190 | 'glimmer-ts': 'glimmer-ts', 191 | gts: 'glimmer-ts', 192 | glsl: 'glsl', 193 | gnuplot: 'gnuplot', 194 | go: 'go', 195 | graphql: 'graphql', 196 | gql: 'graphql', 197 | groovy: 'groovy', 198 | hack: 'hack', 199 | haml: 'haml', 200 | handlebars: 'handlebars', 201 | hbs: 'handlebars', 202 | haskell: 'haskell', 203 | hs: 'haskell', 204 | haxe: 'haxe', 205 | hcl: 'hcl', 206 | hjson: 'hjson', 207 | hlsl: 'hlsl', 208 | html: 'html', 209 | 'html-derivative': 'html-derivative', 210 | http: 'http', 211 | hxml: 'hxml', 212 | hy: 'hy', 213 | imba: 'imba', 214 | ini: 'ini', 215 | properties: 'ini', 216 | java: 'java', 217 | javascript: 'javascript', 218 | js: 'javascript', 219 | jinja: 'jinja', 220 | jison: 'jison', 221 | json: 'json', 222 | json5: 'json5', 223 | jsonc: 'jsonc', 224 | jsonl: 'jsonl', 225 | jsonnet: 'jsonnet', 226 | jssm: 'jssm', 227 | fsl: 'jssm', 228 | jsx: 'jsx', 229 | julia: 'julia', 230 | jl: 'julia', 231 | kotlin: 'kotlin', 232 | kt: 'kotlin', 233 | kts: 'kotlin', 234 | kusto: 'kusto', 235 | kql: 'kusto', 236 | latex: 'latex', 237 | lean: 'lean', 238 | lean4: 'lean', 239 | less: 'less', 240 | liquid: 'liquid', 241 | llvm: 'llvm', 242 | log: 'log', 243 | logo: 'logo', 244 | lua: 'lua', 245 | luau: 'luau', 246 | make: 'make', 247 | makefile: 'make', 248 | markdown: 'markdown', 249 | md: 'markdown', 250 | marko: 'marko', 251 | matlab: 'matlab', 252 | mdc: 'mdc', 253 | mdx: 'mdx', 254 | mermaid: 'mermaid', 255 | mmd: 'mermaid', 256 | mipsasm: 'mipsasm', 257 | mips: 'mipsasm', 258 | mojo: 'mojo', 259 | move: 'move', 260 | narrat: 'narrat', 261 | nar: 'narrat', 262 | nextflow: 'nextflow', 263 | nf: 'nextflow', 264 | nginx: 'nginx', 265 | nim: 'nim', 266 | nix: 'nix', 267 | nushell: 'nushell', 268 | nu: 'nushell', 269 | 'objective-c': 'objective-c', 270 | objc: 'objective-c', 271 | 'objective-cpp': 'objective-cpp', 272 | ocaml: 'ocaml', 273 | pascal: 'pascal', 274 | perl: 'perl', 275 | php: 'php', 276 | plsql: 'plsql', 277 | po: 'po', 278 | pot: 'po', 279 | potx: 'po', 280 | polar: 'polar', 281 | postcss: 'postcss', 282 | powerquery: 'powerquery', 283 | powershell: 'powershell', 284 | ps: 'powershell', 285 | ps1: 'powershell', 286 | prisma: 'prisma', 287 | prolog: 'prolog', 288 | proto: 'proto', 289 | protobuf: 'proto', 290 | pug: 'pug', 291 | jade: 'pug', 292 | puppet: 'puppet', 293 | purescript: 'purescript', 294 | python: 'python', 295 | py: 'python', 296 | qml: 'qml', 297 | qmldir: 'qmldir', 298 | qss: 'qss', 299 | r: 'r', 300 | racket: 'racket', 301 | raku: 'raku', 302 | perl6: 'raku', 303 | razor: 'razor', 304 | reg: 'reg', 305 | regexp: 'regexp', 306 | regex: 'regexp', 307 | rel: 'rel', 308 | riscv: 'riscv', 309 | rst: 'rst', 310 | ruby: 'ruby', 311 | rb: 'ruby', 312 | rust: 'rust', 313 | rs: 'rust', 314 | sas: 'sas', 315 | sass: 'sass', 316 | scala: 'scala', 317 | scheme: 'scheme', 318 | scss: 'scss', 319 | sdbl: 'sdbl', 320 | '1c-query': 'sdbl', 321 | shaderlab: 'shaderlab', 322 | shader: 'shaderlab', 323 | shellscript: 'shellscript', 324 | bash: 'shellscript', 325 | sh: 'shellscript', 326 | shell: 'shellscript', 327 | zsh: 'shellscript', 328 | shellsession: 'shellsession', 329 | console: 'shellsession', 330 | smalltalk: 'smalltalk', 331 | solidity: 'solidity', 332 | soy: 'soy', 333 | 'closure-templates': 'soy', 334 | sparql: 'sparql', 335 | splunk: 'splunk', 336 | spl: 'splunk', 337 | sql: 'sql', 338 | 'ssh-config': 'ssh-config', 339 | stata: 'stata', 340 | stylus: 'stylus', 341 | styl: 'stylus', 342 | svelte: 'svelte', 343 | swift: 'swift', 344 | 'system-verilog': 'system-verilog', 345 | systemd: 'systemd', 346 | talonscript: 'talonscript', 347 | talon: 'talonscript', 348 | tasl: 'tasl', 349 | tcl: 'tcl', 350 | templ: 'templ', 351 | terraform: 'terraform', 352 | tf: 'terraform', 353 | tfvars: 'terraform', 354 | tex: 'tex', 355 | toml: 'toml', 356 | 'ts-tags': 'ts-tags', 357 | lit: 'ts-tags', 358 | tsv: 'tsv', 359 | tsx: 'tsx', 360 | turtle: 'turtle', 361 | twig: 'twig', 362 | typescript: 'typescript', 363 | ts: 'typescript', 364 | typespec: 'typespec', 365 | tsp: 'typespec', 366 | typst: 'typst', 367 | typ: 'typst', 368 | txt: 'text', 369 | text: 'text', 370 | plaintext: 'text', 371 | plain: 'text', 372 | v: 'v', 373 | vala: 'vala', 374 | vb: 'vb', 375 | cmd: 'vb', 376 | verilog: 'verilog', 377 | vhdl: 'vhdl', 378 | viml: 'viml', 379 | vim: 'viml', 380 | vimscript: 'viml', 381 | vue: 'vue', 382 | 'vue-html': 'vue-html', 383 | vyper: 'vyper', 384 | vy: 'vyper', 385 | wasm: 'wasm', 386 | wenyan: 'wenyan', 387 | 文言: 'wenyan', 388 | wgsl: 'wgsl', 389 | wikitext: 'wikitext', 390 | mediawiki: 'wikitext', 391 | wiki: 'wikitext', 392 | wit: 'wit', 393 | wolfram: 'wolfram', 394 | wl: 'wolfram', 395 | xml: 'xml', 396 | xsl: 'xsl', 397 | yaml: 'yaml', 398 | yml: 'yaml', 399 | zenscript: 'zenscript', 400 | zig: 'zig', 401 | }; 402 | 403 | export const UNIQUE_LANGS = Array.from(new Set(Object.values(DEFAULT_LANG_ALIASES))); 404 | 405 | export const SHIKI_THEMES = [ 406 | 'andromeeda', 407 | 'aurora-x', 408 | 'ayu-dark', 409 | 'catppuccin-frappe', 410 | 'catppuccin-latte', 411 | 'catppuccin-macchiato', 412 | 'catppuccin-mocha', 413 | 'dark-plus', 414 | 'dracula', 415 | 'dracula-soft', 416 | 'everforest-dark', 417 | 'everforest-light', 418 | 'github-dark', 419 | 'github-dark-default', 420 | 'github-dark-dimmed', 421 | 'github-dark-high-contrast', 422 | 'github-light', 423 | 'github-light-default', 424 | 'github-light-high-contrast', 425 | 'gruvbox-dark-hard', 426 | 'gruvbox-dark-medium', 427 | 'gruvbox-dark-soft', 428 | 'gruvbox-light-hard', 429 | 'gruvbox-light-medium', 430 | 'gruvbox-light-soft', 431 | 'houston', 432 | 'kanagawa-dragon', 433 | 'kanagawa-lotus', 434 | 'kanagawa-wave', 435 | 'laserwave', 436 | 'light-plus', 437 | 'material-theme', 438 | 'material-theme-darker', 439 | 'material-theme-lighter', 440 | 'material-theme-ocean', 441 | 'material-theme-palenight', 442 | 'min-dark', 443 | 'min-light', 444 | 'monokai', 445 | 'night-owl', 446 | 'nord', 447 | 'one-dark-pro', 448 | 'one-light', 449 | 'plastic', 450 | 'poimandres', 451 | 'red', 452 | 'rose-pine', 453 | 'rose-pine-dawn', 454 | 'rose-pine-moon', 455 | 'slack-dark', 456 | 'slack-ochin', 457 | 'snazzy-light', 458 | 'solarized-dark', 459 | 'solarized-light', 460 | 'synthwave-84', 461 | 'tokyo-night', 462 | 'vesper', 463 | 'vitesse-black', 464 | 'vitesse-dark', 465 | 'vitesse-light', 466 | 467 | 'css-variables', // for users who want to use custom CSS to style their code blocks 468 | ] as const; 469 | 470 | export const DEFAULT_LANGS = [ 471 | 'bash', 472 | 'blade', 473 | 'c', 474 | 'css', 475 | 'c#', 476 | 'c++', 477 | 'dart', 478 | 'diff', 479 | 'go', 480 | 'html', 481 | 'java', 482 | 'javascript', 483 | 'jsx', 484 | 'json', 485 | 'kotlin', 486 | 'log', 487 | 'lua', 488 | 'markdown', 489 | 'mdx', 490 | 'php', 491 | 'powershell', 492 | 'python', 493 | 'ruby', 494 | 'rust', 495 | 'solidity', 496 | 'swift', 497 | 'toml', 498 | 'typescript', 499 | 'tsx', 500 | 'yaml', 501 | ]; 502 | 503 | export const matchAlgorithm = { 504 | matchAlgorithm: 'v3', 505 | } as const; 506 | 507 | export const SHIKI_TRANSFORMERS: ShikiTransformer[] = [ 508 | transformerMetaHighlight({ 509 | className: LINE_HIGHLIGHT_CLASS_NAME, 510 | }), 511 | transformerNotationHighlight({ 512 | ...matchAlgorithm, 513 | classActiveLine: LINE_HIGHLIGHT_CLASS_NAME, 514 | }), 515 | transformerNotationFocus({ 516 | ...matchAlgorithm, 517 | classActiveLine: LINE_FOCUS_CLASS_NAME, 518 | }), 519 | transformerNotationDiff({ 520 | ...matchAlgorithm, 521 | classLineAdd: LINE_DIFF_ADD_CLASS_NAME, 522 | classLineRemove: LINE_DIFF_REMOVE_CLASS_NAME, 523 | }), 524 | ]; 525 | --------------------------------------------------------------------------------