├── 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 |
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 |
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 |  [](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 |
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 |
--------------------------------------------------------------------------------