Repository: mintlify/mdx
Branch: main
Commit: 439e0d177984
Files: 51
Total size: 74.1 KB
Directory structure:
gitextract_yzwoer2e/
├── .eslintrc.cjs
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .yarnrc.yml
├── examples/
│ ├── app-router/
│ │ ├── .eslintrc.json
│ │ ├── .gitignore
│ │ ├── app/
│ │ │ ├── globals.css
│ │ │ ├── layout.tsx
│ │ │ ├── loading.tsx
│ │ │ └── page.tsx
│ │ ├── examples/
│ │ │ └── highlight-example.mdx
│ │ ├── next.config.js
│ │ ├── package.json
│ │ ├── postcss.config.js
│ │ ├── readme.md
│ │ ├── tailwind.config.ts
│ │ └── tsconfig.json
│ └── pages-router/
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── eslint.config.mjs
│ ├── examples/
│ │ └── highlight-example.mdx
│ ├── next.config.js
│ ├── package.json
│ ├── pages/
│ │ ├── _app.tsx
│ │ ├── _document.tsx
│ │ └── index.tsx
│ ├── postcss.config.js
│ ├── readme.md
│ ├── styles/
│ │ └── globals.css
│ ├── tailwind.config.ts
│ └── tsconfig.json
├── package.json
├── packages/
│ └── mdx/
│ ├── package.json
│ ├── src/
│ │ ├── client/
│ │ │ ├── default.tsx
│ │ │ └── rsc.tsx
│ │ ├── index.ts
│ │ ├── plugins/
│ │ │ ├── index.ts
│ │ │ └── rehype/
│ │ │ ├── index.ts
│ │ │ ├── rehypeSyntaxHighlighting.ts
│ │ │ ├── shiki/
│ │ │ │ └── custom-language.ts
│ │ │ ├── shiki-constants.ts
│ │ │ ├── twoslash/
│ │ │ │ └── config.ts
│ │ │ └── utils.ts
│ │ ├── server/
│ │ │ └── index.ts
│ │ ├── types/
│ │ │ └── index.ts
│ │ └── ui/
│ │ ├── index.ts
│ │ └── popup.tsx
│ ├── tsconfig.build.json
│ └── tsconfig.json
└── readme.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.cjs
================================================
module.exports = {
extends: ['@mintlify/eslint-config-typescript'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json',
},
ignorePatterns: ['.eslintrc.cjs', 'dist'],
overrides: [
{
files: ['*.js'],
extends: ['plugin:@typescript-eslint/disable-type-checked'],
},
],
};
================================================
FILE: .gitignore
================================================
# NPM
node_modules/
.eslintcache
yarn-error.log
# Output
dist/
# Misc
.DS_Store
# TypeScript
*.tsbuildinfo
# yarn
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
================================================
FILE: .prettierignore
================================================
/dist
/node_modules
.next
================================================
FILE: .prettierrc
================================================
"@mintlify/prettier-config/config.js"
================================================
FILE: .yarnrc.yml
================================================
nodeLinker: node-modules
================================================
FILE: examples/app-router/.eslintrc.json
================================================
{
"extends": "next/core-web-vitals"
}
================================================
FILE: examples/app-router/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
================================================
FILE: examples/app-router/app/globals.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--primary-light: 85 215 153;
}
}
/* modified from https://github.com/shikijs/shiki/blob/main/packages/twoslash/style-rich.css */
/* ===== Basic ===== */
:root {
--twoslash-border-color: #8888;
--twoslash-underline-color: currentColor;
--twoslash-highlighted-border: #c37d0d50;
--twoslash-highlighted-bg: #c37d0d20;
--twoslash-popup-bg: #f8f8f8;
--twoslash-popup-color: inherit;
--twoslash-popup-shadow: rgba(0, 0, 0, 0.08) 0px 1px 4px;
--twoslash-docs-color: #888;
--twoslash-docs-font: sans-serif;
--twoslash-code-font: inherit;
--twoslash-code-font-size: 1em;
--twoslash-matched-color: inherit;
--twoslash-unmatched-color: #888;
--twoslash-cursor-color: #8888;
--twoslash-error-color: #d45656;
--twoslash-error-bg: #d4565620;
--twoslash-warn-color: #c37d0d;
--twoslash-warn-bg: #c37d0d20;
--twoslash-tag-color: #3772cf;
--twoslash-tag-bg: #3772cf20;
--twoslash-tag-warn-color: var(--twoslash-warn-color);
--twoslash-tag-warn-bg: var(--twoslash-warn-bg);
--twoslash-tag-annotate-color: #1ba673;
--twoslash-tag-annotate-bg: #1ba67320;
--twoslash-text-size: 0.8rem;
--twoslash-docs-tag-style: italic;
}
/* Respect people's wishes to not have animations */
@media (prefers-reduced-motion: reduce) {
.twoslash * {
transition: none !important;
}
}
/* ===== Hover Info ===== */
.twoslash:hover .twoslash-hover {
border-color: var(--twoslash-underline-color);
}
.twoslash .twoslash-hover {
border-bottom: 1px dotted transparent;
transition-timing-function: ease;
transition: border-color 0.3s;
position: relative;
}
/* ===== Popup Override ===== */
.mint-twoslash-popover {
background: var(--twoslash-popup-bg);
color: var(--twoslash-popup-color);
border: 1px solid var(--twoslash-border-color);
border-radius: 4px;
pointer-events: auto;
text-align: left;
box-shadow: var(--twoslash-popup-shadow);
display: inline-flex;
flex-direction: column;
}
.mint-twoslash-popover-pre {
display: flex;
font-size: var(--twoslash-text-size);
font-family: var(--twoslash-code-font);
}
.mint-twoslash-popover:hover {
user-select: auto;
}
.twoslash .twoslash-popup-arrow {
display: none;
}
.twoslash-popup-code,
.twoslash-popup-error,
.twoslash-popup-docs {
padding: 6px 8px !important;
}
.mint-twoslash-popover .twoslash-popup-docs {
color: var(--twoslash-docs-color);
font-family: var(--twoslash-docs-font);
font-size: var(--twoslash-text-size);
max-width: unset;
}
.mint-twoslash-popover .twoslash-popup-error {
color: var(--twoslash-error-color);
background-color: var(--twoslash-error-bg);
font-family: var(--twoslash-docs-font);
font-size: var(--twoslash-text-size);
}
.mint-twoslash-popover .twoslash-popup-docs-tags {
display: flex;
flex-direction: column;
font-family: var(--twoslash-docs-font);
}
.mint-twoslash-popover .twoslash-popup-docs-tag-name {
margin-right: 0.5em;
font-style: var(--twoslash-docs-tag-style);
}
.mint-twoslash-popover .twoslash-popup-docs-tag-name {
font-family: var(--twoslash-code-font);
}
/* ===== Query Line ===== */
.mint-twoslash-popover .twoslash-query-line .twoslash-popup-container {
position: relative;
margin-bottom: 1.4em;
transform: translateY(0.6em);
}
/* ===== Error Line ===== */
.mint-twoslash-popover .twoslash-error-line {
position: relative;
background-color: var(--twoslash-error-bg);
border-left: 3px solid var(--twoslash-error-color);
color: var(--twoslash-error-color);
padding: 6px 12px;
margin: 0.2em 0;
min-width: 100%;
width: max-content;
}
.mint-twoslash-popover .twoslash-error-line.twoslash-error-level-warning {
background-color: var(--twoslash-warn-bg);
border-left: 3px solid var(--twoslash-warn-color);
color: var(--twoslash-warn-color);
}
.mint-twoslash-popover .twoslash-error {
background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") repeat-x bottom left;
padding-bottom: 2px;
}
.mint-twoslash-popover .twoslash-error.twoslash-error-level-warning {
background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c37d0d'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") repeat-x bottom left;
padding-bottom: 2px;
}
/* ===== Completeions ===== */
.mint-twoslash-popover .twoslash-completion-cursor {
position: relative;
}
.mint-twoslash-popover .twoslash-completion-cursor .twoslash-completion-list {
user-select: none;
position: absolute;
top: 0;
left: 0;
transform: translate(0, 1.2em);
margin: 3px 0 0 -1px;
display: inline-block;
z-index: 8;
box-shadow: var(--twoslash-popup-shadow);
background: var(--twoslash-popup-bg);
border: 1px solid var(--twoslash-border-color);
}
.twoslash-completion-list {
width: 240px;
font-size: var(--twoslash-text-size);
padding: 4px;
display: flex;
flex-direction: column;
gap: 4px;
}
.twoslash-completion-list:hover {
user-select: auto;
}
.twoslash-completion-list::before {
background-color: var(--twoslash-cursor-color);
width: 2px;
position: absolute;
top: -1.6em;
height: 1.4em;
left: -1px;
content: ' ';
}
.twoslash-completion-list li {
overflow: hidden;
display: flex;
align-items: center;
gap: 0.25em;
line-height: 1em;
}
.twoslash-completion-list li span.twoslash-completions-unmatched {
color: var(--twoslash-unmatched-color);
}
.twoslash-completion-list .deprecated {
text-decoration: line-through;
opacity: 0.5;
}
.twoslash-completion-list li span.twoslash-completions-matched {
color: var(--twoslash-matched-color);
}
/* Highlights */
.twoslash-highlighted {
background-color: var(--twoslash-highlighted-bg);
border: 1px solid var(--twoslash-highlighted-border);
padding: 1px 2px;
margin: -1px -3px;
border-radius: 4px;
}
/* Icons */
.twoslash-completion-list .twoslash-completions-icon {
color: var(--twoslash-unmatched-color);
width: 1em;
flex: none;
}
/* Custom Tags */
.mint-twoslash-popover .twoslash-tag-line {
position: relative;
background-color: var(--twoslash-tag-bg);
border-left: 3px solid var(--twoslash-tag-color);
color: var(--twoslash-tag-color);
padding: 6px 10px;
margin: 0.2em 0;
display: flex;
align-items: center;
gap: 0.3em;
min-width: 100%;
width: max-content;
}
.mint-twoslash-popover .twoslash-tag-line .twoslash-tag-icon {
width: 1.1em;
color: inherit;
}
.mint-twoslash-popover .twoslash-tag-line.twoslash-tag-error-line {
background-color: var(--twoslash-error-bg);
border-left: 3px solid var(--twoslash-error-color);
color: var(--twoslash-error-color);
}
.mint-twoslash-popover .twoslash-tag-line.twoslash-tag-warn-line {
background-color: var(--twoslash-tag-warn-bg);
border-left: 3px solid var(--twoslash-tag-warn-color);
color: var(--twoslash-tag-warn-color);
}
.mint-twoslash-popover .twoslash-tag-line.twoslash-tag-annotate-line {
background-color: var(--twoslash-tag-annotate-bg);
border-left: 3px solid var(--twoslash-tag-annotate-color);
color: var(--twoslash-tag-annotate-color);
}
================================================
FILE: examples/app-router/app/layout.tsx
================================================
import type { Metadata } from 'next';
import '@/app/globals.css';
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
================================================
FILE: examples/app-router/app/loading.tsx
================================================
export default function Loading() {
return <>Loading...</>;
}
================================================
FILE: examples/app-router/app/page.tsx
================================================
import { MDXRemote } from '@mintlify/mdx/rsc';
import { promises as fs } from 'fs';
export default async function Home() {
const data = await fs.readFile(process.cwd() + '/examples/highlight-example.mdx', 'utf8');
return (
<article className="prose mx-auto py-8">
<MDXRemote source={data} parseFrontmatter />
</article>
);
}
================================================
FILE: examples/app-router/examples/highlight-example.mdx
================================================
---
title: 'Line Highlighting'
description: 'Highlights specific lines and/or line ranges'
---
This MDX file demonstrates syntax highlighting for various programming languages.
## JavaScript
```js index.js {2}
console.log('Hello, world!');
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
```
## Python
```python index.py {1-2,4-5}
def add(a, b):
return a + b
def subtract(a, b):
return a - b
```
## Java
```java {3}
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
```
## C#
```csharp index.cs {1,3-4}
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
```
## Testing Twoslash
### Twoslash disabled without any additional configs or filenames
```ts
// This is a tooltip that will appear on the next line
const myVariable = 'hello world';
// ^?
// This is the second line
// You can include [links](#anchor) in your hover content
function myFunction() {
// ^?
return myVariable;
}
```
### Twoslash enabled without any additional configs or filenames
```ts twoslash
// This is a tooltip that will appear on the next line
const myVariable = 'hello world';
// ^?
// This is the second line
// You can include [links](#anchor) in your hover content
function myFunction() {
// ^?
return myVariable;
}
```
### Twoslash disabled with additional configs and filename
```js something_with_external_packages.tsx icon="js" lines
import { useEffect, useState } from 'react';
export function Component() {
// ^?
const [count, setCount] = useState(0);
// ^? ^?
useEffect(() => {
setTimeout(() => setCount(count + 1), 1000);
// ^?
}, [count]);
return <div>{count}</div>;
}
```
### Twoslash enabled with additional configs
```js something_with_external_packages.tsx icon="js" lines twoslash
import { useEffect, useState } from 'react';
export function Component() {
// ^?
const [count, setCount] = useState(0);
// ^? ^?
useEffect(() => {
setTimeout(() => setCount(count + 1), 1000);
// ^?
}, [count]);
return <div>{count}</div>;
}
```
## Link support
```js Link Testing icon="js" lines twoslash
import { useEffect, useState } from 'react';
// @link Component
export function Component() {
// ^?
return <div>{count}</div>;
}
// @link OtherFunction: #hola-there
export function OtherFunction() {
// ^?
return <div>{count}</div>;
}
// @link ExternalLink: https://google.com
export function ExternalLink() {
// ^?
const str =
"Don't worry, only hover targets with ExternalLink will be affected, not random strings";
return <div>{count}</div>;
}
```
```ts twoslash
type PermissionResult =
| {
behavior: 'allow';
updatedInput: ToolInput;
updatedPermissions?: PermissionUpdate[];
}
| {
behavior: 'deny';
message: string;
interrupt?: boolean;
};
type ToolInput = string[];
type PermissionUpdate = {
name: string;
permission: Array<number>;
};
// ---cut-before---
type CanUseTool = (
toolName: string,
input: ToolInput,
options: {
signal: AbortSignal;
suggestions?: PermissionUpdate[];
// ^?
}
) => Promise<PermissionResult>;
```
### Component
Hello world from the `Component` section
================================================
FILE: examples/app-router/next.config.js
================================================
const path = require('path');
/** @type {import('next').NextConfig} */
const nextConfig = {
serverExternalPackages: ['@shikijs/twoslash'],
outputFileTracingIncludes: {
'/render': [
path.relative(
process.cwd(),
path.resolve(require.resolve('typescript/package.json'), '..', 'lib', 'lib.*.d.ts')
),
'./node_modules/@types/node/**',
],
},
};
module.exports = nextConfig;
================================================
FILE: examples/app-router/package.json
================================================
{
"name": "app-router",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@mintlify/mdx": "workspace:^",
"@radix-ui/react-popover": "^1.1.15",
"next": "16.0.9",
"react": "^19.2.1",
"react-dom": "^19.2.1"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.10",
"@types/node": "^20",
"@types/react": "^19.2.1",
"@types/react-dom": "^19.2.1",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "16.0.7",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
}
================================================
FILE: examples/app-router/postcss.config.js
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
================================================
FILE: examples/app-router/readme.md
================================================
## Getting Started
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) and it uses the [App Router](https://nextjs.org/docs/app). It also uses [Tailwind CSS](https://tailwindcss.com/) for styling.
You can check out the code at [https://github.com/mintlify/mdx/blob/main/examples/app-router/app/page.tsx](https://github.com/mintlify/mdx/blob/main/examples/app-router/app/page.tsx) to understand how to parse your markdown using [@mintlify/mdx](https://www.npmjs.com/package/@mintlify/mdx).
## Demo
You can check out the demo of [this page](https://github.com/mintlify/mdx/blob/main/examples/app-router/app/page.tsx) at [https://mdx-app-router.vercel.app](https://mdx-app-router.vercel.app).
## How to use
1. Use the `MDXRemote` component directly inside your async React Server Component.
```tsx
import { MDXRemote } from '@mintlify/mdx/rsc';
export default async function Home() {
const source: `---
title: Title
---
## Markdown H2
`;
return (
<article className="prose mx-auto py-8">
<MDXRemote source={source} parseFrontmatter />
</article>
);
}
```
================================================
FILE: examples/app-router/tailwind.config.ts
================================================
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
},
},
},
plugins: [require('@tailwindcss/typography')],
};
export default config;
================================================
FILE: examples/app-router/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
================================================
FILE: examples/pages-router/.eslintrc.json
================================================
{
"extends": "next/core-web-vitals"
}
================================================
FILE: examples/pages-router/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
================================================
FILE: examples/pages-router/eslint.config.mjs
================================================
import { defineConfig } from "eslint/config";
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default defineConfig([{
extends: [...nextCoreWebVitals],
}]);
================================================
FILE: examples/pages-router/examples/highlight-example.mdx
================================================
---
title: 'Line Highlighting'
description: 'Highlights specific lines and/or line ranges'
---
This MDX file demonstrates syntax highlighting for various programming languages.
## JavaScript
```js index.js {2}
console.log('Hello, world!');
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
```
## Python
```python index.py {1-2,4-5}
def add(a, b):
return a + b
def subtract(a, b):
return a - b
```
## Java
```java {3}
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
```
## C#
```csharp index.cs {1,3-4}
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
```
## Testing Twoslash
### Twoslash disabled without any additional configs or filenames
```ts
// This is a tooltip that will appear on the next line
const myVariable = 'hello world';
// ^?
// This is the second line
// You can include [links](#anchor) in your hover content
function myFunction() {
// ^?
return myVariable;
}
```
### Twoslash enabled without any additional configs or filenames
```ts twoslash
// This is a tooltip that will appear on the next line
const myVariable = 'hello world';
// ^?
// This is the second line
// You can include [links](#anchor) in your hover content
function myFunction() {
// ^?
return myVariable;
}
```
### Twoslash disabled with additional configs and filename
```js something_with_external_packages.tsx icon="js" lines
import { useEffect, useState } from 'react';
export function Component() {
// ^?
const [count, setCount] = useState(0);
// ^? ^?
useEffect(() => {
setTimeout(() => setCount(count + 1), 1000);
// ^?
}, [count]);
return <div>{count}</div>;
}
```
### Twoslash enabled with additional configs
```js something_with_external_packages.tsx icon="js" lines twoslash
import { useEffect, useState } from 'react';
export function Component() {
// ^?
const [count, setCount] = useState(0);
// ^? ^?
useEffect(() => {
setTimeout(() => setCount(count + 1), 1000);
// ^?
}, [count]);
return <div>{count}</div>;
}
```
## Link support
```js Link Testing icon="js" lines twoslash
import { useEffect, useState } from 'react';
// @link Component
export function Component() {
// ^?
return <div>{count}</div>;
}
// @link OtherFunction: #hola-there
export function OtherFunction() {
// ^?
return <div>{count}</div>;
}
// @link ExternalLink: https://google.com
export function ExternalLink() {
// ^?
const str =
"Don't worry, only hover targets with ExternalLink will be affected, not random strings";
return <div>{count}</div>;
}
```
```ts twoslash
type PermissionResult =
| {
behavior: 'allow';
updatedInput: ToolInput;
updatedPermissions?: PermissionUpdate[];
}
| {
behavior: 'deny';
message: string;
interrupt?: boolean;
};
type ToolInput = string[];
type PermissionUpdate = {
name: string;
permission: Array<number>;
};
// ---cut-before---
type CanUseTool = (
toolName: string,
input: ToolInput,
options: {
signal: AbortSignal;
suggestions?: PermissionUpdate[];
// ^?
}
) => Promise<PermissionResult>;
```
### Component
Hello world from the `Component` section
================================================
FILE: examples/pages-router/next.config.js
================================================
const path = require('path');
/** @type {import('next').NextConfig} */
const nextConfig = {
serverExternalPackages: ['@shikijs/twoslash'],
outputFileTracingIncludes: {
'/render': [
path.relative(
process.cwd(),
path.resolve(require.resolve('typescript/package.json'), '..', 'lib', 'lib.*.d.ts')
),
'./node_modules/@types/node/**',
],
},
};
module.exports = nextConfig;
================================================
FILE: examples/pages-router/package.json
================================================
{
"name": "pages-router",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint ."
},
"dependencies": {
"@mintlify/mdx": "workspace:^",
"@radix-ui/react-popover": "^1.1.15",
"next": "16.0.9",
"react": "^19.2.1",
"react-dom": "^19.2.1"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.10",
"@types/node": "^20",
"@types/react": "^19.2.1",
"@types/react-dom": "^19.2.1",
"autoprefixer": "^10.0.1",
"eslint": "^9",
"eslint-config-next": "16.0.7",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"typescript": "^5"
},
"resolutions": {
"@types/react": "19.2.7",
"@types/react-dom": "19.2.3"
}
}
================================================
FILE: examples/pages-router/pages/_app.tsx
================================================
import { AppProps } from 'next/app';
import '@/styles/globals.css';
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
================================================
FILE: examples/pages-router/pages/_document.tsx
================================================
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
================================================
FILE: examples/pages-router/pages/index.tsx
================================================
import { MDXClient } from '@mintlify/mdx/client';
import { serialize } from '@mintlify/mdx/server';
import type { SerializeResult } from '@mintlify/mdx/types';
import { promises as fs } from 'fs';
import type { GetStaticProps, InferGetStaticPropsType } from 'next';
export const getStaticProps = (async () => {
const data = await fs.readFile(process.cwd() + '/examples/highlight-example.mdx', 'utf8');
const mdxSource = await serialize({ source: data });
if ('error' in mdxSource) {
throw mdxSource.error;
}
return { props: { mdxSource } };
}) satisfies GetStaticProps<{
mdxSource: Omit<SerializeResult, 'error'>;
}>;
export default function Home({ mdxSource }: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<article className="prose mx-auto py-8">
<h1>{String(mdxSource.frontmatter.title)}</h1>
<MDXClient {...mdxSource} />
</article>
);
}
================================================
FILE: examples/pages-router/postcss.config.js
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
================================================
FILE: examples/pages-router/readme.md
================================================
## Getting Started
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) and it uses the [Pages Router](https://nextjs.org/docs/pages). It also uses [Tailwind CSS](https://tailwindcss.com/) for styling.
You can check out the code at [https://github.com/mintlify/mdx/blob/main/examples/pages-router/pages/index.tsx](https://github.com/mintlify/mdx/blob/main/examples/pages-router/pages/index.tsx) to understand how to parse your markdown using [@mintlify/mdx](https://www.npmjs.com/package/@mintlify/mdx).
## Demo
You can check out the demo of [this page](https://github.com/mintlify/mdx/blob/main/examples/pages-router/pages/index.tsx) at [https://mdx-pages-router.vercel.app](https://mdx-pages-router.vercel.app).
## How to use
1. Call the `serialize` function inside `getStaticProps` and return the `mdxSource` object.
```tsx
export const getStaticProps = (async () => {
const mdxSource = await serialize({
source: '## Markdown H2',
});
if ('error' in mdxSource) {
// handle error case
}
return { props: { mdxSource } };
}) satisfies GetStaticProps<{
mdxSource: SerializeSuccess;
}>;
```
2. Pass the `mdxSource` object as props inside the `MDXComponent`.
```tsx
export default function Page({ mdxSource }: InferGetStaticPropsType<typeof getStaticProps>) {
return <MDXClient {...mdxSource} />;
}
```
================================================
FILE: examples/pages-router/styles/globals.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--primary-light: 85 215 153;
}
}
/* modified from https://github.com/shikijs/shiki/blob/main/packages/twoslash/style-rich.css */
/* ===== Basic ===== */
:root {
--twoslash-border-color: #8888;
--twoslash-underline-color: currentColor;
--twoslash-highlighted-border: #c37d0d50;
--twoslash-highlighted-bg: #c37d0d20;
--twoslash-popup-bg: #f8f8f8;
--twoslash-popup-color: inherit;
--twoslash-popup-shadow: rgba(0, 0, 0, 0.08) 0px 1px 4px;
--twoslash-docs-color: #888;
--twoslash-docs-font: sans-serif;
--twoslash-code-font: inherit;
--twoslash-code-font-size: 1em;
--twoslash-matched-color: inherit;
--twoslash-unmatched-color: #888;
--twoslash-cursor-color: #8888;
--twoslash-error-color: #d45656;
--twoslash-error-bg: #d4565620;
--twoslash-warn-color: #c37d0d;
--twoslash-warn-bg: #c37d0d20;
--twoslash-tag-color: #3772cf;
--twoslash-tag-bg: #3772cf20;
--twoslash-tag-warn-color: var(--twoslash-warn-color);
--twoslash-tag-warn-bg: var(--twoslash-warn-bg);
--twoslash-tag-annotate-color: #1ba673;
--twoslash-tag-annotate-bg: #1ba67320;
--twoslash-text-size: 0.8rem;
--twoslash-docs-tag-style: italic;
}
/* Respect people's wishes to not have animations */
@media (prefers-reduced-motion: reduce) {
.twoslash * {
transition: none !important;
}
}
/* ===== Hover Info ===== */
.twoslash:hover .twoslash-hover {
border-color: var(--twoslash-underline-color);
}
.twoslash .twoslash-hover {
border-bottom: 1px dotted transparent;
transition-timing-function: ease;
transition: border-color 0.3s;
position: relative;
}
/* ===== Popup Override ===== */
.mint-twoslash-popover {
background: var(--twoslash-popup-bg);
color: var(--twoslash-popup-color);
border: 1px solid var(--twoslash-border-color);
border-radius: 4px;
pointer-events: auto;
text-align: left;
box-shadow: var(--twoslash-popup-shadow);
display: inline-flex;
flex-direction: column;
}
.mint-twoslash-popover-pre {
display: flex;
font-size: var(--twoslash-text-size);
font-family: var(--twoslash-code-font);
}
.mint-twoslash-popover:hover {
user-select: auto;
}
.twoslash .twoslash-popup-arrow {
display: none;
}
.twoslash-popup-code,
.twoslash-popup-error,
.twoslash-popup-docs {
padding: 6px 8px !important;
}
.mint-twoslash-popover .twoslash-popup-docs {
color: var(--twoslash-docs-color);
font-family: var(--twoslash-docs-font);
font-size: var(--twoslash-text-size);
max-width: unset;
}
.mint-twoslash-popover .twoslash-popup-error {
color: var(--twoslash-error-color);
background-color: var(--twoslash-error-bg);
font-family: var(--twoslash-docs-font);
font-size: var(--twoslash-text-size);
}
.mint-twoslash-popover .twoslash-popup-docs-tags {
display: flex;
flex-direction: column;
font-family: var(--twoslash-docs-font);
}
.mint-twoslash-popover .twoslash-popup-docs-tag-name {
margin-right: 0.5em;
font-style: var(--twoslash-docs-tag-style);
}
.mint-twoslash-popover .twoslash-popup-docs-tag-name {
font-family: var(--twoslash-code-font);
}
/* ===== Query Line ===== */
.mint-twoslash-popover .twoslash-query-line .twoslash-popup-container {
position: relative;
margin-bottom: 1.4em;
transform: translateY(0.6em);
}
/* ===== Error Line ===== */
.mint-twoslash-popover .twoslash-error-line {
position: relative;
background-color: var(--twoslash-error-bg);
border-left: 3px solid var(--twoslash-error-color);
color: var(--twoslash-error-color);
padding: 6px 12px;
margin: 0.2em 0;
min-width: 100%;
width: max-content;
}
.mint-twoslash-popover .twoslash-error-line.twoslash-error-level-warning {
background-color: var(--twoslash-warn-bg);
border-left: 3px solid var(--twoslash-warn-color);
color: var(--twoslash-warn-color);
}
.mint-twoslash-popover .twoslash-error {
background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c94824'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") repeat-x bottom left;
padding-bottom: 2px;
}
.mint-twoslash-popover .twoslash-error.twoslash-error-level-warning {
background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23c37d0d'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") repeat-x bottom left;
padding-bottom: 2px;
}
/* ===== Completeions ===== */
.mint-twoslash-popover .twoslash-completion-cursor {
position: relative;
}
.mint-twoslash-popover .twoslash-completion-cursor .twoslash-completion-list {
user-select: none;
position: absolute;
top: 0;
left: 0;
transform: translate(0, 1.2em);
margin: 3px 0 0 -1px;
display: inline-block;
z-index: 8;
box-shadow: var(--twoslash-popup-shadow);
background: var(--twoslash-popup-bg);
border: 1px solid var(--twoslash-border-color);
}
.twoslash-completion-list {
width: 240px;
font-size: var(--twoslash-text-size);
padding: 4px;
display: flex;
flex-direction: column;
gap: 4px;
}
.twoslash-completion-list:hover {
user-select: auto;
}
.twoslash-completion-list::before {
background-color: var(--twoslash-cursor-color);
width: 2px;
position: absolute;
top: -1.6em;
height: 1.4em;
left: -1px;
content: ' ';
}
.twoslash-completion-list li {
overflow: hidden;
display: flex;
align-items: center;
gap: 0.25em;
line-height: 1em;
}
.twoslash-completion-list li span.twoslash-completions-unmatched {
color: var(--twoslash-unmatched-color);
}
.twoslash-completion-list .deprecated {
text-decoration: line-through;
opacity: 0.5;
}
.twoslash-completion-list li span.twoslash-completions-matched {
color: var(--twoslash-matched-color);
}
/* Highlights */
.twoslash-highlighted {
background-color: var(--twoslash-highlighted-bg);
border: 1px solid var(--twoslash-highlighted-border);
padding: 1px 2px;
margin: -1px -3px;
border-radius: 4px;
}
/* Icons */
.twoslash-completion-list .twoslash-completions-icon {
color: var(--twoslash-unmatched-color);
width: 1em;
flex: none;
}
/* Custom Tags */
.mint-twoslash-popover .twoslash-tag-line {
position: relative;
background-color: var(--twoslash-tag-bg);
border-left: 3px solid var(--twoslash-tag-color);
color: var(--twoslash-tag-color);
padding: 6px 10px;
margin: 0.2em 0;
display: flex;
align-items: center;
gap: 0.3em;
min-width: 100%;
width: max-content;
}
.mint-twoslash-popover .twoslash-tag-line .twoslash-tag-icon {
width: 1.1em;
color: inherit;
}
.mint-twoslash-popover .twoslash-tag-line.twoslash-tag-error-line {
background-color: var(--twoslash-error-bg);
border-left: 3px solid var(--twoslash-error-color);
color: var(--twoslash-error-color);
}
.mint-twoslash-popover .twoslash-tag-line.twoslash-tag-warn-line {
background-color: var(--twoslash-tag-warn-bg);
border-left: 3px solid var(--twoslash-tag-warn-color);
color: var(--twoslash-tag-warn-color);
}
.mint-twoslash-popover .twoslash-tag-line.twoslash-tag-annotate-line {
background-color: var(--twoslash-tag-annotate-bg);
border-left: 3px solid var(--twoslash-tag-annotate-color);
color: var(--twoslash-tag-annotate-color);
}
================================================
FILE: examples/pages-router/tailwind.config.ts
================================================
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
},
},
},
plugins: [require('@tailwindcss/typography')],
};
export default config;
================================================
FILE: examples/pages-router/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
================================================
FILE: package.json
================================================
{
"name": "mdx",
"private": true,
"scripts": {
"build": "yarn workspaces foreach --topological-dev -Av run build"
},
"workspaces": [
"packages/*",
"examples/*"
],
"packageManager": "yarn@4.3.1"
}
================================================
FILE: packages/mdx/package.json
================================================
{
"name": "@mintlify/mdx",
"version": "4.0.0",
"description": "Markdown parser from Mintlify",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"sideEffects": false,
"files": [
"dist"
],
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./rsc": {
"import": "./dist/client/rsc.js",
"types": "./dist/client/rsc.d.ts"
},
"./client": {
"import": "./dist/client/default.js",
"types": "./dist/client/default.d.ts"
},
"./server": {
"import": "./dist/server/index.js",
"types": "./dist/server/index.d.ts"
},
"./types": {
"import": "./dist/types/index.js",
"types": "./dist/types/index.d.ts"
},
"./plugins": {
"import": "./dist/plugins/index.js",
"types": "./dist/plugins/index.d.ts"
},
"./constants": {
"import": "./dist/plugins/rehype/shiki-constants.js",
"types": "./dist/plugins/rehype/shiki-constants.d.ts"
},
"./ui": {
"import": "./dist/ui/index.js",
"types": "./dist/ui/index.d.ts"
}
},
"type": "module",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"repository": {
"type": "git",
"url": "https://github.com/mintlify/mdx.git"
},
"scripts": {
"prepare": "npm run build",
"build": "tsc --project tsconfig.build.json",
"clean:build": "rimraf dist",
"clean:all": "rimraf node_modules .eslintcache && yarn clean:build",
"watch": "tsc --watch",
"type": "tsc --noEmit",
"lint": "eslint . --cache",
"format": "prettier . --write",
"format:check": "prettier . --check"
},
"author": "Mintlify, Inc.",
"license": "MIT",
"devDependencies": {
"@mintlify/eslint-config": "^1.0.4",
"@mintlify/eslint-config-typescript": "^1.0.9",
"@mintlify/prettier-config": "^1.0.1",
"@mintlify/ts-config": "^2.0.2",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@tsconfig/recommended": "1.x",
"@types/hast": "^3.0.4",
"@types/react": "^19.2.1",
"@types/react-dom": "^19.2.1",
"@types/unist": "^3.0.3",
"@typescript-eslint/eslint-plugin": "6.x",
"@typescript-eslint/parser": "6.x",
"eslint": "8.x",
"eslint-config-prettier": "8.x",
"eslint-plugin-unused-imports": "^3.x",
"prettier": "^3.1.1",
"prettier-plugin-tailwindcss": "^0.6.8",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"rimraf": "^5.0.1",
"typescript": "^5.7.2"
},
"peerDependencies": {
"@radix-ui/react-popover": "^19.2.1",
"react": "^19.2.1",
"react-dom": "^19.2.1"
},
"dependencies": {
"@shikijs/transformers": "^3.11.0",
"@shikijs/twoslash": "^3.12.2",
"arktype": "^2.1.26",
"hast-util-to-string": "^3.0.1",
"mdast-util-from-markdown": "^2.0.2",
"mdast-util-gfm": "^3.1.0",
"mdast-util-mdx-jsx": "^3.2.0",
"mdast-util-to-hast": "^13.2.0",
"next-mdx-remote-client": "^1.0.3",
"rehype-katex": "^7.0.1",
"remark-gfm": "^4.0.0",
"remark-math": "^6.0.0",
"remark-smartypants": "^3.0.2",
"shiki": "^3.11.0",
"unified": "^11.0.0",
"unist-util-visit": "^5.0.0"
}
}
================================================
FILE: packages/mdx/src/client/default.tsx
================================================
import { MDXClient as BaseMDXClient, MDXClientProps } from 'next-mdx-remote-client/csr';
import { Popup, PopupContent, PopupTrigger } from '../ui/index.js';
export function MDXClient(props: MDXClientProps) {
const mergedComponents = {
Popup,
PopupContent,
PopupTrigger,
...props.components,
};
return <BaseMDXClient {...props} components={mergedComponents} />;
}
================================================
FILE: packages/mdx/src/client/rsc.tsx
================================================
import { MDXRemote as BaseMDXRemote, MDXComponents } from 'next-mdx-remote-client/rsc';
import { SerializeOptions } from 'next-mdx-remote-client/serialize';
import rehypeKatex from 'rehype-katex';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import remarkSmartypants from 'remark-smartypants';
import { rehypeSyntaxHighlighting, RehypeSyntaxHighlightingOptions } from '../plugins/index.js';
import { Popup, PopupContent, PopupTrigger } from '../ui/index.js';
export async function MDXRemote({
source,
mdxOptions,
scope,
components,
parseFrontmatter,
syntaxHighlightingOptions,
}: {
source: string;
mdxOptions?: SerializeOptions['mdxOptions'];
scope?: SerializeOptions['scope'];
components?: MDXComponents;
parseFrontmatter?: SerializeOptions['parseFrontmatter'];
syntaxHighlightingOptions?: RehypeSyntaxHighlightingOptions;
}) {
const mergedComponents = {
Popup,
PopupContent,
PopupTrigger,
...components,
};
return (
<BaseMDXRemote
source={source}
components={mergedComponents}
options={{
scope,
mdxOptions: {
remarkPlugins: [
remarkGfm,
remarkSmartypants,
remarkMath,
...(mdxOptions?.remarkPlugins || []),
],
rehypePlugins: [
rehypeKatex,
[rehypeSyntaxHighlighting, syntaxHighlightingOptions],
...(mdxOptions?.rehypePlugins || []),
],
format: mdxOptions?.format || 'mdx',
},
parseFrontmatter,
}}
/>
);
}
================================================
FILE: packages/mdx/src/index.ts
================================================
================================================
FILE: packages/mdx/src/plugins/index.ts
================================================
export * from './rehype/index.js';
================================================
FILE: packages/mdx/src/plugins/rehype/index.ts
================================================
export * from './rehypeSyntaxHighlighting.js';
================================================
FILE: packages/mdx/src/plugins/rehype/rehypeSyntaxHighlighting.ts
================================================
import { transformerTwoslash } from '@shikijs/twoslash';
import { type } from 'arktype';
import type { Element, Root } from 'hast';
import { toString } from 'hast-util-to-string';
import type { MdxJsxFlowElementHast, MdxJsxTextElementHast } from 'mdast-util-mdx-jsx';
import { createHighlighter, type Highlighter } from 'shiki';
import type { Plugin } from 'unified';
import { visit } from 'unist-util-visit';
import {
type ShikiLang,
type ShikiTheme,
shikiColorReplacements,
DEFAULT_LANG_ALIASES,
DEFAULT_LANG,
DEFAULT_DARK_THEME,
DEFAULT_LIGHT_THEME,
DEFAULT_THEMES,
DEFAULT_LANGS,
SHIKI_TRANSFORMERS,
UNIQUE_LANGS,
} from './shiki-constants.js';
import { TextMateGrammar, TextMateGrammarType } from './shiki/custom-language.js';
import { getTwoslashOptions, parseLineComment } from './twoslash/config.js';
import { getLanguage } from './utils.js';
export type RehypeSyntaxHighlightingOptions = {
theme?: ShikiTheme;
themes?: Record<'light' | 'dark', ShikiTheme>;
codeStyling?: 'dark' | 'system' | 'light' | Record<string, unknown> | null;
linkMap?: Map<string, string>;
customLanguages?: string[];
};
let highlighterPromise: Promise<Highlighter> | null = null;
async function getHighlighter(): Promise<Highlighter> {
if (!highlighterPromise) {
highlighterPromise = createHighlighter({
themes: DEFAULT_THEMES,
langs: DEFAULT_LANGS,
});
}
return highlighterPromise;
}
export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?], Root, Root> = (
options = {}
) => {
return async (tree) => {
const nodesToProcess: Promise<void>[] = [];
const customLanguageNames: string[] = [];
const themesToLoad: ShikiTheme[] = [];
if (options.themes) {
themesToLoad.push(options.themes.dark);
themesToLoad.push(options.themes.light);
} else if (options.theme) {
themesToLoad.push(options.theme);
}
const highlighter = await getHighlighter();
await Promise.all([
...themesToLoad
.filter(
(theme): theme is Exclude<ShikiTheme, 'css-variables'> =>
!DEFAULT_THEMES.includes(theme) && theme !== 'css-variables'
)
.map((theme) => highlighter.loadTheme(theme)),
...(options.customLanguages?.map(async (unparsedLang) => {
const parsedLang = JSON.parse(unparsedLang);
const lang = TextMateGrammar(parsedLang);
if (lang instanceof type.errors) {
console.error(lang.summary);
return;
}
await highlighter.loadLanguage(lang);
const possibleNames = [lang.name, lang.displayName, ...(lang.aliases ?? [])];
customLanguageNames.push(...possibleNames.filter((l) => l != undefined));
}) ?? []),
]);
visit(tree, 'element', (node, index, parent) => {
const child = node.children[0];
if (
!parent ||
index === undefined ||
node.type !== 'element' ||
node.tagName !== 'pre' ||
!child ||
child.type !== 'element' ||
child.tagName !== 'code'
) {
return;
}
// set the metadata of `node` (which is a pre element) to that of
// `child` (which is the code element that likely contains all the metadata)
if (!Object.keys(node.properties).length) {
node.properties = child.properties;
}
if (!node.data) {
node.data = child.data;
}
let lang =
getLanguage(node, DEFAULT_LANG_ALIASES) ??
getLanguage(child, DEFAULT_LANG_ALIASES) ??
DEFAULT_LANG;
if (
!DEFAULT_LANGS.includes(lang) &&
!customLanguageNames.includes(lang) &&
UNIQUE_LANGS.includes(lang)
) {
nodesToProcess.push(
highlighter.loadLanguage(lang).then(() => {
traverseNode({ node, index, parent, highlighter, lang, options });
})
);
} else {
if (!UNIQUE_LANGS.includes(lang) && !customLanguageNames.includes(lang)) {
lang = DEFAULT_LANG;
}
traverseNode({ node, index, parent, highlighter, lang, options });
}
});
await Promise.all(nodesToProcess);
};
};
function traverseNode({
node,
index,
parent,
highlighter,
lang,
options,
}: {
node: Element;
index: number;
parent: Element | Root | MdxJsxTextElementHast | MdxJsxFlowElementHast;
highlighter: Highlighter;
lang: ShikiLang;
options: RehypeSyntaxHighlightingOptions;
}) {
try {
let code = toString(node);
const meta = node.data?.meta?.split(' ') ?? [];
const twoslashIndex = meta.findIndex((str) => str.toLowerCase() === 'twoslash');
const shouldUseTwoslash = twoslashIndex > -1;
if (node.data && node.data.meta && shouldUseTwoslash) {
meta.splice(twoslashIndex, 1);
node.data.meta = meta.join(' ').trim() || undefined;
}
const linkMap = options.linkMap ?? new Map();
if (shouldUseTwoslash) {
const splitCode = code.split('\n');
for (const [i, line] of splitCode.entries()) {
const parsedLineComment = parseLineComment(line);
if (!parsedLineComment) continue;
const { word, href } = parsedLineComment;
linkMap.set(word, href);
splitCode.splice(i, 1);
}
code = splitCode.join('\n');
}
const twoslashOptions = getTwoslashOptions({ linkMap });
const hast = highlighter.codeToHast(code, {
lang: lang ?? DEFAULT_LANG,
meta: shouldUseTwoslash ? { __raw: 'twoslash' } : undefined,
themes: {
light:
options.themes?.light ??
options.theme ??
(options.codeStyling === 'dark' ? DEFAULT_DARK_THEME : DEFAULT_LIGHT_THEME),
dark: options.themes?.dark ?? options.theme ?? DEFAULT_DARK_THEME,
},
colorReplacements: shikiColorReplacements,
tabindex: false,
tokenizeMaxLineLength: 1000,
transformers: [...SHIKI_TRANSFORMERS, transformerTwoslash(twoslashOptions)],
});
const codeElement = hast.children[0] as Element;
if (!codeElement) return;
const preChild = codeElement.children[0] as Element;
node.data = node.data ?? {};
codeElement.data = node.data;
codeElement.properties.language = lang;
if (preChild) {
preChild.data = node.data;
preChild.properties.language = lang;
}
parent.children.splice(index, 1, codeElement);
} catch (err) {
if (err instanceof Error && /Unknown language/.test(err.message)) {
return;
}
throw err;
}
}
export { TextMateGrammar, type TextMateGrammarType };
================================================
FILE: packages/mdx/src/plugins/rehype/shiki/custom-language.ts
================================================
import { scope } from 'arktype';
// Types come from the LanguageRegistration type in Shiki: node_modules/@shikijs/types/dist/index.d.ts
const types = scope({
ScopeName: 'string',
ScopePath: 'string',
ScopePattern: 'string',
IncludeString: 'string',
RegExpString: 'string | RegExp',
ILocation: {
filename: 'string',
line: 'number',
char: 'number',
},
ILocatable: {
'$vscodeTextmateLocation?': 'ILocation',
},
IRawCapturesMap: {
'[string]': 'IRawRule',
},
IRawRepositoryMap: {
'[string]': 'IRawRule',
},
IRawCaptures: 'IRawCapturesMap & ILocatable',
_IRawRule: {
'include?': 'IncludeString',
'name?': 'ScopeName',
'contentName?': 'ScopeName',
'match?': 'RegExpString',
'captures?': 'IRawCaptures',
'begin?': 'RegExpString',
'beginCaptures?': 'IRawCaptures',
'end?': 'RegExpString',
'endCaptures?': 'IRawCaptures',
'while?': 'RegExpString',
'whileCaptures?': 'IRawCaptures',
'patterns?': 'IRawRule[]',
'repository?': 'IRawRepository',
'applyEndPatternLast?': 'boolean',
'[string]': 'unknown',
},
IRawRule: '_IRawRule & ILocatable',
IRawRepository: 'IRawRepositoryMap & ILocatable',
_IRawGrammar: {
repository: 'IRawRepository',
scopeName: 'ScopeName',
patterns: 'IRawRule[]',
'injections?': {
'[string]': 'IRawRule',
},
'injectionSelector?': 'string',
'fileTypes?': 'string[]',
'name?': 'string',
'firstLineMatch?': 'string',
'[string]': 'unknown',
},
IRawGrammar: 'ILocatable & _IRawGrammar',
LanguageRegistration: {
name: 'string',
scopeName: 'string',
'displayName?': 'string',
'aliases?': 'string[]',
'embeddedLangs?': 'string[]',
'embeddedLangsLazy?': 'string[]',
'balancedBracketSelectors?': 'string[]',
'unbalancedBracketSelectors?': 'string[]',
'foldingStopMarker?': 'string',
'foldingStartMarker?': 'string',
'injectTo?': 'string[]',
'[string]': 'unknown',
},
TextMateGrammar: 'LanguageRegistration & IRawGrammar',
}).export();
export const TextMateGrammar = types.TextMateGrammar;
export type TextMateGrammarType = typeof TextMateGrammar.infer;
================================================
FILE: packages/mdx/src/plugins/rehype/shiki-constants.ts
================================================
import {
transformerNotationHighlight,
transformerNotationFocus,
transformerMetaHighlight,
transformerNotationDiff,
} from '@shikijs/transformers';
import type { ShikiTransformer } from '@shikijs/types';
import { createCssVariablesTheme } from 'shiki/core';
import type { BundledLanguage, ThemeRegistration } from 'shiki/types';
export const LINE_HIGHLIGHT_CLASS_NAME = 'line-highlight';
export const LINE_FOCUS_CLASS_NAME = 'line-focus';
export const LINE_DIFF_ADD_CLASS_NAME = 'line-diff line-add';
export const LINE_DIFF_REMOVE_CLASS_NAME = 'line-diff line-remove';
export type ShikiLang = BundledLanguage | 'ansi' | 'text';
export type ShikiTheme = (typeof SHIKI_THEMES)[number];
export const SHIKI_CSS_THEME = createCssVariablesTheme({
name: 'css-variables',
variablePrefix: '--mint-',
variableDefaults: {
'color-text': '#171717',
'color-background': 'transparent',
'token-constant': '#171717',
'token-string': '#297a3a',
'token-comment': '#666666',
'token-keyword': '#bd2864',
'token-parameter': '#a35200',
'token-function': '#0068d6',
'token-string-expression': '#297a3a',
'token-punctuation': '#171717',
'token-link': '#297a3a',
'ansi-black': '#000000',
'ansi-black-dim': '#00000080',
'ansi-red': '#bb0000',
'ansi-red-dim': '#bb000080',
'ansi-green': '#00bb00',
'ansi-green-dim': '#00bb0080',
'ansi-yellow': '#bbbb00',
'ansi-yellow-dim': '#bbbb0080',
'ansi-blue': '#0000bb',
'ansi-blue-dim': '#0000bb80',
'ansi-magenta': '#ff00ff',
'ansi-magenta-dim': '#ff00ff80',
'ansi-cyan': '#00bbbb',
'ansi-cyan-dim': '#00bbbb80',
'ansi-white': '#eeeeee',
'ansi-white-dim': '#eeeeee80',
'ansi-bright-black': '#555555',
'ansi-bright-black-dim': '#55555580',
'ansi-bright-red': '#ff5555',
'ansi-bright-red-dim': '#ff555580',
'ansi-bright-green': '#00ff00',
'ansi-bright-green-dim': '#00ff0080',
'ansi-bright-yellow': '#ffff55',
'ansi-bright-yellow-dim': '#ffff5580',
'ansi-bright-blue': '#5555ff',
'ansi-bright-blue-dim': '#5555ff80',
'ansi-bright-magenta': '#ff55ff',
'ansi-bright-magenta-dim': '#ff55ff80',
'ansi-bright-cyan': '#55ffff',
'ansi-bright-cyan-dim': '#55ffff80',
'ansi-bright-white': '#ffffff',
'ansi-bright-white-dim': '#ffffff80',
},
fontStyle: true,
});
export const DEFAULT_LANG = 'text' as const;
export const DEFAULT_DARK_THEME: ShikiTheme = 'dark-plus' as const;
export const DEFAULT_LIGHT_THEME: ShikiTheme = 'github-light-default' as const;
export const DEFAULT_THEMES: [ShikiTheme, ShikiTheme, ThemeRegistration] = [
DEFAULT_LIGHT_THEME,
DEFAULT_DARK_THEME,
SHIKI_CSS_THEME,
] as const;
export const shikiColorReplacements: Partial<Record<ShikiTheme, string | Record<string, string>>> =
{
'dark-plus': {
'#1e1e1e': '#0B0C0E',
},
};
export const DEFAULT_LANG_ALIASES: Record<string, ShikiLang> = {
ansi: 'ansi',
abap: 'abap',
'actionscript-3': 'actionscript-3',
ada: 'ada',
'angular-html': 'angular-html',
'angular-ts': 'angular-ts',
apache: 'apache',
apex: 'apex',
apl: 'apl',
applescript: 'applescript',
ara: 'ara',
asciidoc: 'asciidoc',
adoc: 'asciidoc',
asm: 'asm',
astro: 'astro',
awk: 'awk',
ballerina: 'ballerina',
bat: 'bat',
batch: 'bat',
beancount: 'beancount',
berry: 'berry',
be: 'berry',
bibtex: 'bibtex',
bicep: 'bicep',
blade: 'blade',
bsl: 'bsl',
'1c': 'bsl',
c: 'c',
h: 'c',
cadence: 'cadence',
cdc: 'cadence',
cairo: 'cairo',
clarity: 'clarity',
clojure: 'clojure',
clj: 'clojure',
cmake: 'cmake',
cobol: 'cobol',
codeowners: 'codeowners',
codeql: 'codeql',
ql: 'codeql',
coffee: 'coffee',
coffeescript: 'coffee',
'common-lisp': 'common-lisp',
lisp: 'common-lisp',
coq: 'coq',
cpp: 'cpp',
cc: 'cpp',
hh: 'cpp',
'c++': 'cpp',
crystal: 'crystal',
csharp: 'csharp',
'c#': 'csharp',
cs: 'csharp',
css: 'css',
csv: 'csv',
cue: 'cue',
cypher: 'cypher',
cql: 'cypher',
d: 'd',
dart: 'dart',
dax: 'dax',
desktop: 'desktop',
diff: 'diff',
docker: 'docker',
dockerfile: 'docker',
dotenv: 'dotenv',
'dream-maker': 'dream-maker',
edge: 'edge',
elixir: 'elixir',
elm: 'elm',
'emacs-lisp': 'emacs-lisp',
elisp: 'emacs-lisp',
erb: 'erb',
erlang: 'erlang',
erl: 'erlang',
fennel: 'fennel',
fish: 'fish',
fluent: 'fluent',
ftl: 'fluent',
'fortran-fixed-form': 'fortran-fixed-form',
f: 'fortran-fixed-form',
for: 'fortran-fixed-form',
f77: 'fortran-fixed-form',
'fortran-free-form': 'fortran-free-form',
f90: 'fortran-free-form',
f95: 'fortran-free-form',
f03: 'fortran-free-form',
f08: 'fortran-free-form',
f18: 'fortran-free-form',
fsharp: 'fsharp',
'f#': 'fsharp',
fs: 'fsharp',
gdresource: 'gdresource',
gdscript: 'gdscript',
gdshader: 'gdshader',
genie: 'genie',
gherkin: 'gherkin',
'git-commit': 'git-commit',
'git-rebase': 'git-rebase',
gleam: 'gleam',
'glimmer-js': 'glimmer-js',
gjs: 'glimmer-js',
'glimmer-ts': 'glimmer-ts',
gts: 'glimmer-ts',
glsl: 'glsl',
gnuplot: 'gnuplot',
go: 'go',
graphql: 'graphql',
gql: 'graphql',
groovy: 'groovy',
hack: 'hack',
haml: 'haml',
handlebars: 'handlebars',
hbs: 'handlebars',
haskell: 'haskell',
hs: 'haskell',
haxe: 'haxe',
hcl: 'hcl',
hjson: 'hjson',
hlsl: 'hlsl',
html: 'html',
'html-derivative': 'html-derivative',
http: 'http',
hxml: 'hxml',
hy: 'hy',
imba: 'imba',
ini: 'ini',
properties: 'ini',
java: 'java',
javascript: 'javascript',
js: 'javascript',
jinja: 'jinja',
jison: 'jison',
json: 'json',
json5: 'json5',
jsonc: 'jsonc',
jsonl: 'jsonl',
jsonnet: 'jsonnet',
jssm: 'jssm',
fsl: 'jssm',
jsx: 'jsx',
julia: 'julia',
jl: 'julia',
kotlin: 'kotlin',
kt: 'kotlin',
kts: 'kotlin',
kusto: 'kusto',
kql: 'kusto',
latex: 'latex',
lean: 'lean',
lean4: 'lean',
less: 'less',
liquid: 'liquid',
llvm: 'llvm',
log: 'log',
logo: 'logo',
lua: 'lua',
luau: 'luau',
make: 'make',
makefile: 'make',
markdown: 'markdown',
md: 'markdown',
marko: 'marko',
matlab: 'matlab',
mdc: 'mdc',
mdx: 'mdx',
mermaid: 'mermaid',
mmd: 'mermaid',
mipsasm: 'mipsasm',
mips: 'mipsasm',
mojo: 'mojo',
move: 'move',
narrat: 'narrat',
nar: 'narrat',
nextflow: 'nextflow',
nf: 'nextflow',
nginx: 'nginx',
nim: 'nim',
nix: 'nix',
nushell: 'nushell',
nu: 'nushell',
'objective-c': 'objective-c',
objc: 'objective-c',
'objective-cpp': 'objective-cpp',
ocaml: 'ocaml',
pascal: 'pascal',
perl: 'perl',
php: 'php',
plsql: 'plsql',
po: 'po',
pot: 'po',
potx: 'po',
polar: 'polar',
postcss: 'postcss',
powerquery: 'powerquery',
powershell: 'powershell',
ps: 'powershell',
ps1: 'powershell',
prisma: 'prisma',
prolog: 'prolog',
proto: 'proto',
protobuf: 'proto',
pug: 'pug',
jade: 'pug',
puppet: 'puppet',
purescript: 'purescript',
python: 'python',
py: 'python',
qml: 'qml',
qmldir: 'qmldir',
qss: 'qss',
r: 'r',
racket: 'racket',
raku: 'raku',
perl6: 'raku',
razor: 'razor',
reg: 'reg',
regexp: 'regexp',
regex: 'regexp',
rel: 'rel',
riscv: 'riscv',
rst: 'rst',
ruby: 'ruby',
rb: 'ruby',
rust: 'rust',
rs: 'rust',
sas: 'sas',
sass: 'sass',
scala: 'scala',
scheme: 'scheme',
scss: 'scss',
sdbl: 'sdbl',
'1c-query': 'sdbl',
shaderlab: 'shaderlab',
shader: 'shaderlab',
shellscript: 'shellscript',
bash: 'shellscript',
sh: 'shellscript',
shell: 'shellscript',
zsh: 'shellscript',
shellsession: 'shellsession',
console: 'shellsession',
smalltalk: 'smalltalk',
solidity: 'solidity',
soy: 'soy',
'closure-templates': 'soy',
sparql: 'sparql',
splunk: 'splunk',
spl: 'splunk',
sql: 'sql',
'ssh-config': 'ssh-config',
stata: 'stata',
stylus: 'stylus',
styl: 'stylus',
svelte: 'svelte',
swift: 'swift',
'system-verilog': 'system-verilog',
systemd: 'systemd',
talonscript: 'talonscript',
talon: 'talonscript',
tasl: 'tasl',
tcl: 'tcl',
templ: 'templ',
terraform: 'terraform',
tf: 'terraform',
tfvars: 'terraform',
tex: 'tex',
toml: 'toml',
'ts-tags': 'ts-tags',
lit: 'ts-tags',
tsv: 'tsv',
tsx: 'tsx',
turtle: 'turtle',
twig: 'twig',
typescript: 'typescript',
ts: 'typescript',
typespec: 'typespec',
tsp: 'typespec',
typst: 'typst',
typ: 'typst',
txt: 'text',
text: 'text',
plaintext: 'text',
plain: 'text',
v: 'v',
vala: 'vala',
vb: 'vb',
cmd: 'vb',
verilog: 'verilog',
vhdl: 'vhdl',
viml: 'viml',
vim: 'viml',
vimscript: 'viml',
vue: 'vue',
'vue-html': 'vue-html',
vyper: 'vyper',
vy: 'vyper',
wasm: 'wasm',
wenyan: 'wenyan',
文言: 'wenyan',
wgsl: 'wgsl',
wikitext: 'wikitext',
mediawiki: 'wikitext',
wiki: 'wikitext',
wit: 'wit',
wolfram: 'wolfram',
wl: 'wolfram',
xml: 'xml',
xsl: 'xsl',
yaml: 'yaml',
yml: 'yaml',
zenscript: 'zenscript',
zig: 'zig',
};
export const UNIQUE_LANGS = Array.from(new Set(Object.values(DEFAULT_LANG_ALIASES)));
export const SHIKI_THEMES = [
'andromeeda',
'aurora-x',
'ayu-dark',
'catppuccin-frappe',
'catppuccin-latte',
'catppuccin-macchiato',
'catppuccin-mocha',
'dark-plus',
'dracula',
'dracula-soft',
'everforest-dark',
'everforest-light',
'github-dark',
'github-dark-default',
'github-dark-dimmed',
'github-dark-high-contrast',
'github-light',
'github-light-default',
'github-light-high-contrast',
'gruvbox-dark-hard',
'gruvbox-dark-medium',
'gruvbox-dark-soft',
'gruvbox-light-hard',
'gruvbox-light-medium',
'gruvbox-light-soft',
'houston',
'kanagawa-dragon',
'kanagawa-lotus',
'kanagawa-wave',
'laserwave',
'light-plus',
'material-theme',
'material-theme-darker',
'material-theme-lighter',
'material-theme-ocean',
'material-theme-palenight',
'min-dark',
'min-light',
'monokai',
'night-owl',
'nord',
'one-dark-pro',
'one-light',
'plastic',
'poimandres',
'red',
'rose-pine',
'rose-pine-dawn',
'rose-pine-moon',
'slack-dark',
'slack-ochin',
'snazzy-light',
'solarized-dark',
'solarized-light',
'synthwave-84',
'tokyo-night',
'vesper',
'vitesse-black',
'vitesse-dark',
'vitesse-light',
'css-variables', // for users who want to use custom CSS to style their code blocks
] as const;
export const DEFAULT_LANGS = [
'bash',
'blade',
'c',
'css',
'c#',
'c++',
'dart',
'diff',
'go',
'html',
'java',
'javascript',
'jsx',
'json',
'kotlin',
'log',
'lua',
'markdown',
'mdx',
'php',
'powershell',
'python',
'ruby',
'rust',
'solidity',
'swift',
'toml',
'typescript',
'tsx',
'yaml',
];
export const matchAlgorithm = {
matchAlgorithm: 'v3',
} as const;
export const SHIKI_TRANSFORMERS: ShikiTransformer[] = [
transformerMetaHighlight({
className: LINE_HIGHLIGHT_CLASS_NAME,
}),
transformerNotationHighlight({
...matchAlgorithm,
classActiveLine: LINE_HIGHLIGHT_CLASS_NAME,
}),
transformerNotationFocus({
...matchAlgorithm,
classActiveLine: LINE_FOCUS_CLASS_NAME,
}),
transformerNotationDiff({
...matchAlgorithm,
classLineAdd: LINE_DIFF_ADD_CLASS_NAME,
classLineRemove: LINE_DIFF_REMOVE_CLASS_NAME,
}),
];
================================================
FILE: packages/mdx/src/plugins/rehype/twoslash/config.ts
================================================
import { rendererRich, type TransformerTwoslashOptions } from '@shikijs/twoslash';
import type { Element, ElementContent } from 'hast';
import type { Code } from 'mdast';
import { fromMarkdown } from 'mdast-util-from-markdown';
import { gfmFromMarkdown } from 'mdast-util-gfm';
import { defaultHandlers, toHast } from 'mdast-util-to-hast';
import type { ShikiTransformerContextCommon } from 'shiki/types';
import ts from 'typescript';
const twoslashCompilerOptions: ts.CompilerOptions = {
target: ts.ScriptTarget.ESNext,
lib: ['ESNext', 'DOM', 'esnext', 'dom', 'es2020'],
};
function onTwoslashError(err: unknown, code: string, lang: string) {
console.error(JSON.stringify({ err, code, lang }));
}
function onShikiError(err: unknown, code: string, lang: string) {
console.error(JSON.stringify({ err, code, lang }));
}
export function getTwoslashOptions(
{ linkMap }: { linkMap: Map<string, string> } = { linkMap: new Map() }
): TransformerTwoslashOptions {
return {
onTwoslashError,
onShikiError,
// copied fuma's approach for custom popup
// https://github.com/fuma-nama/fumadocs/blob/dev/packages/twoslash/src/index.ts
renderer: rendererRich({
renderMarkdown,
renderMarkdownInline,
queryRendering: 'line',
hast: {
hoverToken: {
tagName: 'Popup',
children(input) {
for (const rootElement of input) {
if (!('children' in rootElement)) continue;
for (const [i, element] of rootElement.children.entries()) {
if (element.type !== 'text') continue;
const href = linkMap.get(element.value);
if (!href) continue;
const linkProperties = {
href,
...(checkIsExternalLink(href) && {
target: '_blank',
rel: 'noopener noreferrer',
}),
};
if (rootElement.type === 'element' && rootElement.tagName === 'PopupTrigger') {
rootElement.properties = { ...rootElement.properties, ...linkProperties };
} else {
const newElement: ElementContent = {
type: 'element',
tagName: 'a',
properties: linkProperties,
children: [{ type: 'text', value: element.value }],
};
input.splice(i, 1, newElement);
}
}
}
return input;
},
},
hoverPopup: {
tagName: 'PopupContent',
},
hoverCompose: ({ popup, token }) => [
popup,
{
type: 'element',
tagName: 'PopupTrigger',
properties: {},
children: [token],
},
],
popupDocs: {
class: 'prose-sm prose-gray dark:prose-dark twoslash-popup-docs',
},
popupTypes: {
tagName: 'span',
class: 'mint-twoslash-popover-pre',
children: (v) => {
if (v.length === 1 && v[0]?.type === 'element' && v[0]?.tagName === 'pre') return v;
return [
{
type: 'element',
tagName: 'code',
properties: {
class: 'twoslash-popup-code shiki',
},
children: v,
},
];
},
},
popupDocsTags: {
class: 'prose-sm prose-gray dark:prose-dark twoslash-popup-docs twoslash-popup-docs-tags',
},
nodesHighlight: {
class: 'highlighted-word twoslash-highlighted',
},
},
}),
langs: ['ts', 'typescript', 'js', 'javascript', 'tsx', 'jsx'],
explicitTrigger: true,
twoslashOptions: {
compilerOptions: twoslashCompilerOptions,
},
};
}
/** https://github.com/fuma-nama/fumadocs/blob/2862a10c2d78b52c0a3f479ad21b255cc0031fc9/packages/twoslash/src/index.ts#L121-L150 */
function renderMarkdown(this: ShikiTransformerContextCommon, md: string): ElementContent[] {
const mdast = fromMarkdown(
md.replace(/{@link (?<link>[^}]*)}/g, '$1'), // replace jsdoc links
{ mdastExtensions: [gfmFromMarkdown()] }
);
return (
toHast(mdast, {
handlers: {
code: (state, node: Code) => {
if (node.lang) {
return this.codeToHast(node.value, {
...this.options,
transformers: [],
meta: {
__raw: node.meta ?? undefined,
},
lang: node.lang,
}).children[0] as Element;
}
return defaultHandlers.code(state, node);
},
},
}) as Element
).children;
}
/** https://github.com/fuma-nama/fumadocs/blob/2862a10c2d78b52c0a3f479ad21b255cc0031fc9/packages/twoslash/src/index.ts#L152-L168 */
function renderMarkdownInline(
this: ShikiTransformerContextCommon,
md: string,
context?: string
): ElementContent[] {
const text = context === 'tag:param' ? md.replace(/^(?<link>[\w$-]+)/, '`$1` ') : md;
const children = renderMarkdown.call(this, text);
if (children.length === 1 && children[0]?.type === 'element' && children[0].tagName === 'p')
return children[0].children;
return children;
}
export function parseLineComment(line: string): { word: string; href: string } | undefined {
line = line.trim();
if (!line.startsWith('//')) return;
line = line.replace(/^[\/\s]+/, '').trim();
if (!line.startsWith('@link ') && !line.startsWith('@link:')) return;
line = line.replace('@link:', '@link ');
const parts = line.split('@link ')[1];
if (!parts) return;
const words = parts.split(' ').filter(Boolean);
if (words.length === 1 && words[0]) {
let word = words[0];
if (word.endsWith(':')) word = word.slice(0, -1);
const lowercaseWord = word.toLowerCase();
const href = word.startsWith('#') ? lowercaseWord : `#${encodeURIComponent(lowercaseWord)}`;
return { word, href };
} else if (words.length === 2 && words[0] && words[1]) {
let word = words[0];
if (word.endsWith(':')) word = word.slice(0, -1);
const href = words[1];
if (!href.startsWith('#') && !href.startsWith('https://')) return;
return { word, href };
}
return;
}
type Url = `https://${string}`;
function checkIsExternalLink(href: string | undefined): href is Url {
let isExternalLink = false;
try {
if (href && URL.canParse(href)) isExternalLink = true;
} catch {}
return isExternalLink;
}
================================================
FILE: packages/mdx/src/plugins/rehype/utils.ts
================================================
import type { Element } from 'hast';
import { type ShikiLang } from './shiki-constants.js';
export function classNameOrEmptyArray(element: Element): string[] {
const className = element.properties.className;
if (Array.isArray(className) && className.every((el) => typeof el === 'string')) return className;
return [];
}
export function getLanguage(
node: Element,
aliases: Record<string, ShikiLang>
): ShikiLang | undefined {
const className = classNameOrEmptyArray(node);
for (const classListItem of className) {
if (classListItem.startsWith('language-')) {
const lang = classListItem.slice(9).toLowerCase();
if (lang) return aliases[lang] ?? (lang as ShikiLang);
}
}
return undefined;
}
================================================
FILE: packages/mdx/src/server/index.ts
================================================
import { serialize as baseSerialize } from 'next-mdx-remote-client/serialize';
import rehypeKatex from 'rehype-katex';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import remarkSmartypants from 'remark-smartypants';
import { rehypeSyntaxHighlighting, RehypeSyntaxHighlightingOptions } from '../plugins/index.js';
import type { SerializeOptions } from '../types/index.js';
export const serialize = async ({
source,
mdxOptions,
scope,
parseFrontmatter = true,
syntaxHighlightingOptions,
}: {
source: string;
mdxOptions?: SerializeOptions['mdxOptions'];
scope?: SerializeOptions['scope'];
parseFrontmatter?: SerializeOptions['parseFrontmatter'];
syntaxHighlightingOptions?: RehypeSyntaxHighlightingOptions;
}) => {
try {
return await baseSerialize({
source,
options: {
mdxOptions: {
...mdxOptions,
remarkPlugins: [
remarkGfm,
remarkSmartypants,
remarkMath,
...(mdxOptions?.remarkPlugins || []),
],
rehypePlugins: [
rehypeKatex,
[rehypeSyntaxHighlighting, syntaxHighlightingOptions],
...(mdxOptions?.rehypePlugins || []),
],
format: mdxOptions?.format || 'mdx',
},
scope,
parseFrontmatter,
},
});
} catch (error) {
console.error(`Error occurred while serializing MDX: ${error}`);
throw error;
}
};
================================================
FILE: packages/mdx/src/types/index.ts
================================================
import type { SerializeOptions, SerializeResult } from 'next-mdx-remote-client/serialize';
type SerializeSuccess = SerializeResult & { compiledSource: string };
export type { SerializeOptions, SerializeResult, SerializeSuccess };
================================================
FILE: packages/mdx/src/ui/index.ts
================================================
export * from './popup.js';
================================================
FILE: packages/mdx/src/ui/popup.tsx
================================================
'use client';
// copied from fuma's approach for custom popup
// https://github.com/fuma-nama/fumadocs/blob/dev/packages/twoslash/src/ui/popup.tsx
import { Popover, PopoverContent, PopoverPortal, PopoverTrigger } from '@radix-ui/react-popover';
import {
type ComponentPropsWithoutRef,
type ComponentRef,
createContext,
forwardRef,
type ReactNode,
useContext,
useMemo,
useRef,
useState,
} from 'react';
interface PopupContextObject {
open: boolean;
setOpen: (open: boolean) => void;
handleOpen: (e: React.PointerEvent) => void;
handleClose: (e: React.PointerEvent) => void;
}
const PopupContext = createContext<PopupContextObject | undefined>(undefined);
function Popup({ delay = 300, children }: { delay?: number; children: ReactNode }) {
const [open, setOpen] = useState(false);
const openTimeoutRef = useRef<number | undefined>(undefined);
const closeTimeoutRef = useRef<number | undefined>(undefined);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopupContext.Provider
value={useMemo(
() => ({
open,
setOpen,
handleOpen(e) {
if (e.pointerType === 'touch') return;
if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current);
openTimeoutRef.current = window.setTimeout(() => {
setOpen(true);
}, delay);
},
handleClose(e) {
if (e.pointerType === 'touch') return;
if (openTimeoutRef.current) clearTimeout(openTimeoutRef.current);
closeTimeoutRef.current = window.setTimeout(() => {
setOpen(false);
}, delay);
},
}),
[delay, open]
)}
>
{children}
</PopupContext.Provider>
</Popover>
);
}
const PopupTrigger = forwardRef<
ComponentRef<typeof PopoverTrigger>,
ComponentPropsWithoutRef<typeof PopoverTrigger> & { href?: string; target?: string; rel?: string }
>(({ children, href, target, rel, ...props }, ref) => {
const ctx = useContext(PopupContext);
if (!ctx) throw new Error('Missing Popup Context');
let element;
if (href) {
element = (
<a href={href} rel={rel} target={target}>
<span className="twoslash-hover">{children}</span>
</a>
);
} else {
element = <span className="twoslash-hover">{children}</span>;
}
return (
<PopoverTrigger
ref={ref}
onPointerEnter={ctx.handleOpen}
onPointerLeave={ctx.handleClose}
asChild
{...props}
>
{element}
</PopoverTrigger>
);
});
PopupTrigger.displayName = 'PopupTrigger';
const PopupContent = forwardRef<
ComponentRef<typeof PopoverContent>,
ComponentPropsWithoutRef<typeof PopoverContent>
>(({ className, side = 'bottom', align = 'center', sideOffset = 4, ...props }, ref) => {
const ctx = useContext(PopupContext);
if (!ctx) throw new Error('Missing Popup Context');
return (
<PopoverPortal>
<PopoverContent
ref={ref}
side={side}
align={align}
sideOffset={sideOffset}
className={'mint-twoslash-popover ' + className}
onPointerEnter={ctx.handleOpen}
onPointerLeave={ctx.handleClose}
onOpenAutoFocus={(e) => {
e.preventDefault();
}}
onCloseAutoFocus={(e) => {
e.preventDefault();
}}
{...props}
/>
</PopoverPortal>
);
});
PopupContent.displayName = 'PopupContent';
export { Popup, PopupTrigger, PopupContent };
================================================
FILE: packages/mdx/tsconfig.build.json
================================================
{
"extends": "./tsconfig.json",
"include": ["src/**/*.ts", "src/**/*.tsx"]
}
================================================
FILE: packages/mdx/tsconfig.json
================================================
{
"extends": "@mintlify/ts-config",
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "react-jsx",
"target": "ES2021",
"outDir": "dist",
"declaration": true,
"module": "Node16"
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules", "dist"]
}
================================================
FILE: readme.md
================================================
<div align="center">
<a href="https://mintlify.com">
<img
src="https://res.cloudinary.com/mintlify/image/upload/v1665385627/logo-rounded_zuk7q1.svg"
alt="Mintlify Logo"
height="64"
/>
</a>
<br />
<p>
<h3>
<b>
Mint
</b>
</h3>
</p>
<p>
<b>
Open source docs builder that's beautiful, fast, and easy to work with.
</b>
</p>
<p>
 [](https://twitter.com/intent/tweet?url=&text=Check%20out%20%40mintlify)
</p>
</div>
# Mintlify's markdown parser
**@mintlify/mdx** is a thin layer on top of [next-mdx-remote-client](https://github.com/ipikuka/next-mdx-remote-client) that provides a better developer experience for Next.js users by adding support for syntax highlighting.
## Installation
```bash
# using npm
npm i @mintlify/mdx
# using yarn
yarn add @mintlify/mdx
# using pnpm
pnpm add @mintlify/mdx
```
## Examples
### Next.js pages router
[You can check the example app here](https://github.com/mintlify/mdx/tree/main/examples/pages-router).
1. Call the `serialize` function inside `getStaticProps` and return the `mdxSource` object.
```tsx
export const getStaticProps = (async () => {
const mdxSource = await serialize({
source: '## Markdown H2',
});
if ('error' in mdxSource) {
// handle error case
}
return { props: { mdxSource } };
}) satisfies GetStaticProps<{
mdxSource: SerializeSuccess;
}>;
```
2. Pass the `mdxSource` object as props inside the `MDXComponent`.
```tsx
export default function Page({ mdxSource }: InferGetStaticPropsType<typeof getStaticProps>) {
return <MDXClient {...mdxSource} />;
}
```
### Next.js app router
[You can check the example app here](https://github.com/mintlify/mdx/tree/main/examples/app-router).
1. Use the `MDXRemote` component directly inside your async React Server Component.
```tsx
import { MDXRemote } from '@mintlify/mdx';
export default async function Home() {
const source: `---
title: Title
---
## Markdown H2
`;
return (
<article className="prose mx-auto py-8">
<MDXRemote source={source} parseFrontmatter />
</article>
);
}
```
## APIs
Similar to [next-mdx-remote-client](https://github.com/ipikuka/next-mdx-remote-client), this package exports the following APIs:
- `serialize` - a function that compiles MDX source to SerializeResult.
- `MDXClient` - a component that renders SerializeSuccess on the client.
- `MDXRemote` - a component that both serializes and renders the source - should be used inside async React Server Component.
### serialize
```tsx
import { serialize } from '@mintlify/mdx';
const mdxSource = await serialize({
source: '## Markdown H2',
mdxOptions: {
remarkPlugins: [
// Remark plugins
],
rehypePlugins: [
// Rehype plugins
],
},
});
```
### MDXClient
```tsx
'use client';
import { MDXClient } from '@mintlify/mdx';
<MDXClient
components={
{
// Your custom components
}
}
{...mdxSource}
/>;
```
### MDXRemote
```tsx
import { MDXRemote } from '@mintlify/mdx';
<MDXRemote
source="## Markdown H2"
mdxOptions={{
remarkPlugins: [
// Remark plugins
],
rehypePlugins: [
// Rehype plugins
],
}}
components={
{
// Your custom components
}
}
/>;
```
<div align="center">
<p>
<sub>
Built with ❤︎ by
<a href="https://mintlify.com">
Mintlify
</a>
</sub>
</p>
</div>
gitextract_yzwoer2e/ ├── .eslintrc.cjs ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .yarnrc.yml ├── examples/ │ ├── app-router/ │ │ ├── .eslintrc.json │ │ ├── .gitignore │ │ ├── app/ │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── examples/ │ │ │ └── highlight-example.mdx │ │ ├── next.config.js │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── readme.md │ │ ├── tailwind.config.ts │ │ └── tsconfig.json │ └── pages-router/ │ ├── .eslintrc.json │ ├── .gitignore │ ├── eslint.config.mjs │ ├── examples/ │ │ └── highlight-example.mdx │ ├── next.config.js │ ├── package.json │ ├── pages/ │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ └── index.tsx │ ├── postcss.config.js │ ├── readme.md │ ├── styles/ │ │ └── globals.css │ ├── tailwind.config.ts │ └── tsconfig.json ├── package.json ├── packages/ │ └── mdx/ │ ├── package.json │ ├── src/ │ │ ├── client/ │ │ │ ├── default.tsx │ │ │ └── rsc.tsx │ │ ├── index.ts │ │ ├── plugins/ │ │ │ ├── index.ts │ │ │ └── rehype/ │ │ │ ├── index.ts │ │ │ ├── rehypeSyntaxHighlighting.ts │ │ │ ├── shiki/ │ │ │ │ └── custom-language.ts │ │ │ ├── shiki-constants.ts │ │ │ ├── twoslash/ │ │ │ │ └── config.ts │ │ │ └── utils.ts │ │ ├── server/ │ │ │ └── index.ts │ │ ├── types/ │ │ │ └── index.ts │ │ └── ui/ │ │ ├── index.ts │ │ └── popup.tsx │ ├── tsconfig.build.json │ └── tsconfig.json └── readme.md
SYMBOL INDEX (41 symbols across 15 files)
FILE: examples/app-router/app/layout.tsx
function RootLayout (line 10) | function RootLayout({ children }: { children: React.ReactNode }) {
FILE: examples/app-router/app/loading.tsx
function Loading (line 1) | function Loading() {
FILE: examples/app-router/app/page.tsx
function Home (line 4) | async function Home() {
FILE: examples/pages-router/pages/_app.tsx
function App (line 5) | function App({ Component, pageProps }: AppProps) {
FILE: examples/pages-router/pages/_document.tsx
function Document (line 3) | function Document() {
FILE: examples/pages-router/pages/index.tsx
function Home (line 20) | function Home({ mdxSource }: InferGetStaticPropsType<typeof getStaticPro...
FILE: packages/mdx/src/client/default.tsx
function MDXClient (line 5) | function MDXClient(props: MDXClientProps) {
FILE: packages/mdx/src/client/rsc.tsx
function MDXRemote (line 11) | async function MDXRemote({
FILE: packages/mdx/src/plugins/rehype/rehypeSyntaxHighlighting.ts
type RehypeSyntaxHighlightingOptions (line 27) | type RehypeSyntaxHighlightingOptions = {
function getHighlighter (line 37) | async function getHighlighter(): Promise<Highlighter> {
function traverseNode (line 133) | function traverseNode({
FILE: packages/mdx/src/plugins/rehype/shiki-constants.ts
constant LINE_HIGHLIGHT_CLASS_NAME (line 11) | const LINE_HIGHLIGHT_CLASS_NAME = 'line-highlight';
constant LINE_FOCUS_CLASS_NAME (line 12) | const LINE_FOCUS_CLASS_NAME = 'line-focus';
constant LINE_DIFF_ADD_CLASS_NAME (line 13) | const LINE_DIFF_ADD_CLASS_NAME = 'line-diff line-add';
constant LINE_DIFF_REMOVE_CLASS_NAME (line 14) | const LINE_DIFF_REMOVE_CLASS_NAME = 'line-diff line-remove';
type ShikiLang (line 16) | type ShikiLang = BundledLanguage | 'ansi' | 'text';
type ShikiTheme (line 17) | type ShikiTheme = (typeof SHIKI_THEMES)[number];
constant SHIKI_CSS_THEME (line 19) | const SHIKI_CSS_THEME = createCssVariablesTheme({
constant DEFAULT_LANG (line 71) | const DEFAULT_LANG = 'text' as const;
constant DEFAULT_DARK_THEME (line 72) | const DEFAULT_DARK_THEME: ShikiTheme = 'dark-plus' as const;
constant DEFAULT_LIGHT_THEME (line 73) | const DEFAULT_LIGHT_THEME: ShikiTheme = 'github-light-default' as const;
constant DEFAULT_THEMES (line 74) | const DEFAULT_THEMES: [ShikiTheme, ShikiTheme, ThemeRegistration] = [
constant DEFAULT_LANG_ALIASES (line 87) | const DEFAULT_LANG_ALIASES: Record<string, ShikiLang> = {
constant UNIQUE_LANGS (line 403) | const UNIQUE_LANGS = Array.from(new Set(Object.values(DEFAULT_LANG_ALIAS...
constant SHIKI_THEMES (line 405) | const SHIKI_THEMES = [
constant DEFAULT_LANGS (line 470) | const DEFAULT_LANGS = [
constant SHIKI_TRANSFORMERS (line 507) | const SHIKI_TRANSFORMERS: ShikiTransformer[] = [
FILE: packages/mdx/src/plugins/rehype/shiki/custom-language.ts
type TextMateGrammarType (line 88) | type TextMateGrammarType = typeof TextMateGrammar.infer;
FILE: packages/mdx/src/plugins/rehype/twoslash/config.ts
function onTwoslashError (line 15) | function onTwoslashError(err: unknown, code: string, lang: string) {
function onShikiError (line 19) | function onShikiError(err: unknown, code: string, lang: string) {
function getTwoslashOptions (line 23) | function getTwoslashOptions(
function renderMarkdown (line 118) | function renderMarkdown(this: ShikiTransformerContextCommon, md: string)...
function renderMarkdownInline (line 146) | function renderMarkdownInline(
function parseLineComment (line 159) | function parseLineComment(line: string): { word: string; href: string } ...
type Url (line 188) | type Url = `https://${string}`;
function checkIsExternalLink (line 189) | function checkIsExternalLink(href: string | undefined): href is Url {
FILE: packages/mdx/src/plugins/rehype/utils.ts
function classNameOrEmptyArray (line 5) | function classNameOrEmptyArray(element: Element): string[] {
function getLanguage (line 11) | function getLanguage(
FILE: packages/mdx/src/types/index.ts
type SerializeSuccess (line 3) | type SerializeSuccess = SerializeResult & { compiledSource: string };
FILE: packages/mdx/src/ui/popup.tsx
type PopupContextObject (line 18) | interface PopupContextObject {
function Popup (line 28) | function Popup({ delay = 300, children }: { delay?: number; children: Re...
Condensed preview — 51 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (83K chars).
[
{
"path": ".eslintrc.cjs",
"chars": 329,
"preview": "module.exports = {\n extends: ['@mintlify/eslint-config-typescript'],\n parserOptions: {\n tsconfigRootDir: __dirname,"
},
{
"path": ".gitignore",
"chars": 206,
"preview": "# NPM\nnode_modules/\n.eslintcache\nyarn-error.log\n\n# Output\ndist/\n\n# Misc\n.DS_Store\n\n# TypeScript\n*.tsbuildinfo\n\n# yarn\n.p"
},
{
"path": ".prettierignore",
"chars": 26,
"preview": "/dist\n/node_modules\n\n.next"
},
{
"path": ".prettierrc",
"chars": 38,
"preview": "\"@mintlify/prettier-config/config.js\"\n"
},
{
"path": ".yarnrc.yml",
"chars": 24,
"preview": "nodeLinker: node-modules"
},
{
"path": "examples/app-router/.eslintrc.json",
"chars": 40,
"preview": "{\n \"extends\": \"next/core-web-vitals\"\n}\n"
},
{
"path": "examples/app-router/.gitignore",
"chars": 391,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "examples/app-router/app/globals.css",
"chars": 7741,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n :root {\n --primary-light: 85 215 153;\n }"
},
{
"path": "examples/app-router/app/layout.tsx",
"chars": 346,
"preview": "import type { Metadata } from 'next';\n\nimport '@/app/globals.css';\n\nexport const metadata: Metadata = {\n title: 'Create"
},
{
"path": "examples/app-router/app/loading.tsx",
"chars": 64,
"preview": "export default function Loading() {\n return <>Loading...</>;\n}\n"
},
{
"path": "examples/app-router/app/page.tsx",
"chars": 347,
"preview": "import { MDXRemote } from '@mintlify/mdx/rsc';\nimport { promises as fs } from 'fs';\n\nexport default async function Home("
},
{
"path": "examples/app-router/examples/highlight-example.mdx",
"chars": 3418,
"preview": "---\ntitle: 'Line Highlighting'\ndescription: 'Highlights specific lines and/or line ranges'\n---\n\nThis MDX file demonstrat"
},
{
"path": "examples/app-router/next.config.js",
"chars": 419,
"preview": "const path = require('path');\n\n/** @type {import('next').NextConfig} */\nconst nextConfig = {\n serverExternalPackages: ["
},
{
"path": "examples/app-router/package.json",
"chars": 687,
"preview": "{\n \"name\": \"app-router\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"next dev\",\n \"build\": \"n"
},
{
"path": "examples/app-router/postcss.config.js",
"chars": 83,
"preview": "module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n"
},
{
"path": "examples/app-router/readme.md",
"chars": 1246,
"preview": "## Getting Started\n\nThis is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://githu"
},
{
"path": "examples/app-router/tailwind.config.ts",
"chars": 522,
"preview": "import type { Config } from 'tailwindcss';\n\nconst config: Config = {\n content: [\n './pages/**/*.{js,ts,jsx,tsx,mdx}'"
},
{
"path": "examples/app-router/tsconfig.json",
"chars": 595,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es5\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"sk"
},
{
"path": "examples/pages-router/.eslintrc.json",
"chars": 40,
"preview": "{\n \"extends\": \"next/core-web-vitals\"\n}\n"
},
{
"path": "examples/pages-router/.gitignore",
"chars": 391,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "examples/pages-router/eslint.config.mjs",
"chars": 355,
"preview": "import { defineConfig } from \"eslint/config\";\nimport nextCoreWebVitals from \"eslint-config-next/core-web-vitals\";\nimport"
},
{
"path": "examples/pages-router/examples/highlight-example.mdx",
"chars": 3418,
"preview": "---\ntitle: 'Line Highlighting'\ndescription: 'Highlights specific lines and/or line ranges'\n---\n\nThis MDX file demonstrat"
},
{
"path": "examples/pages-router/next.config.js",
"chars": 419,
"preview": "const path = require('path');\n\n/** @type {import('next').NextConfig} */\nconst nextConfig = {\n serverExternalPackages: ["
},
{
"path": "examples/pages-router/package.json",
"chars": 775,
"preview": "{\n \"name\": \"pages-router\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"next dev\",\n \"build\": "
},
{
"path": "examples/pages-router/pages/_app.tsx",
"chars": 177,
"preview": "import { AppProps } from 'next/app';\n\nimport '@/styles/globals.css';\n\nexport default function App({ Component, pageProps"
},
{
"path": "examples/pages-router/pages/_document.tsx",
"chars": 233,
"preview": "import { Html, Head, Main, NextScript } from 'next/document';\n\nexport default function Document() {\n return (\n <Html"
},
{
"path": "examples/pages-router/pages/index.tsx",
"chars": 899,
"preview": "import { MDXClient } from '@mintlify/mdx/client';\nimport { serialize } from '@mintlify/mdx/server';\nimport type { Serial"
},
{
"path": "examples/pages-router/postcss.config.js",
"chars": 83,
"preview": "module.exports = {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n"
},
{
"path": "examples/pages-router/readme.md",
"chars": 1493,
"preview": "## Getting Started\n\nThis is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://githu"
},
{
"path": "examples/pages-router/styles/globals.css",
"chars": 7741,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n :root {\n --primary-light: 85 215 153;\n }"
},
{
"path": "examples/pages-router/tailwind.config.ts",
"chars": 522,
"preview": "import type { Config } from 'tailwindcss';\n\nconst config: Config = {\n content: [\n './pages/**/*.{js,ts,jsx,tsx,mdx}'"
},
{
"path": "examples/pages-router/tsconfig.json",
"chars": 509,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es5\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"sk"
},
{
"path": "package.json",
"chars": 222,
"preview": "{\n \"name\": \"mdx\",\n \"private\": true,\n \"scripts\": {\n \"build\": \"yarn workspaces foreach --topological-dev -Av run bui"
},
{
"path": "packages/mdx/package.json",
"chars": 3200,
"preview": "{\n \"name\": \"@mintlify/mdx\",\n \"version\": \"4.0.0\",\n \"description\": \"Markdown parser from Mintlify\",\n \"main\": \"./dist/i"
},
{
"path": "packages/mdx/src/client/default.tsx",
"chars": 388,
"preview": "import { MDXClient as BaseMDXClient, MDXClientProps } from 'next-mdx-remote-client/csr';\n\nimport { Popup, PopupContent, "
},
{
"path": "packages/mdx/src/client/rsc.tsx",
"chars": 1584,
"preview": "import { MDXRemote as BaseMDXRemote, MDXComponents } from 'next-mdx-remote-client/rsc';\nimport { SerializeOptions } from"
},
{
"path": "packages/mdx/src/index.ts",
"chars": 0,
"preview": ""
},
{
"path": "packages/mdx/src/plugins/index.ts",
"chars": 35,
"preview": "export * from './rehype/index.js';\n"
},
{
"path": "packages/mdx/src/plugins/rehype/index.ts",
"chars": 47,
"preview": "export * from './rehypeSyntaxHighlighting.js';\n"
},
{
"path": "packages/mdx/src/plugins/rehype/rehypeSyntaxHighlighting.ts",
"chars": 6561,
"preview": "import { transformerTwoslash } from '@shikijs/twoslash';\nimport { type } from 'arktype';\nimport type { Element, Root } f"
},
{
"path": "packages/mdx/src/plugins/rehype/shiki/custom-language.ts",
"chars": 2192,
"preview": "import { scope } from 'arktype';\n\n// Types come from the LanguageRegistration type in Shiki: node_modules/@shikijs/types"
},
{
"path": "packages/mdx/src/plugins/rehype/shiki-constants.ts",
"chars": 11329,
"preview": "import {\n transformerNotationHighlight,\n transformerNotationFocus,\n transformerMetaHighlight,\n transformerNotationDi"
},
{
"path": "packages/mdx/src/plugins/rehype/twoslash/config.ts",
"chars": 6566,
"preview": "import { rendererRich, type TransformerTwoslashOptions } from '@shikijs/twoslash';\nimport type { Element, ElementContent"
},
{
"path": "packages/mdx/src/plugins/rehype/utils.ts",
"chars": 732,
"preview": "import type { Element } from 'hast';\n\nimport { type ShikiLang } from './shiki-constants.js';\n\nexport function classNameO"
},
{
"path": "packages/mdx/src/server/index.ts",
"chars": 1462,
"preview": "import { serialize as baseSerialize } from 'next-mdx-remote-client/serialize';\nimport rehypeKatex from 'rehype-katex';\ni"
},
{
"path": "packages/mdx/src/types/index.ts",
"chars": 232,
"preview": "import type { SerializeOptions, SerializeResult } from 'next-mdx-remote-client/serialize';\n\ntype SerializeSuccess = Seri"
},
{
"path": "packages/mdx/src/ui/index.ts",
"chars": 28,
"preview": "export * from './popup.js';\n"
},
{
"path": "packages/mdx/src/ui/popup.tsx",
"chars": 3563,
"preview": "'use client';\n\n// copied from fuma's approach for custom popup\n// https://github.com/fuma-nama/fumadocs/blob/dev/package"
},
{
"path": "packages/mdx/tsconfig.build.json",
"chars": 81,
"preview": "{\n \"extends\": \"./tsconfig.json\",\n \"include\": [\"src/**/*.ts\", \"src/**/*.tsx\"]\n}\n"
},
{
"path": "packages/mdx/tsconfig.json",
"chars": 308,
"preview": "{\n \"extends\": \"@mintlify/ts-config\",\n \"compilerOptions\": {\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"jsx\": \"r"
},
{
"path": "readme.md",
"chars": 3725,
"preview": "<div align=\"center\">\n <a href=\"https://mintlify.com\">\n <img\n src=\"https://res.cloudinary.com/mintlify/image/upl"
}
]
About this extraction
This page contains the full source code of the mintlify/mdx GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 51 files (74.1 KB), approximately 23.9k tokens, and a symbol index with 41 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.