|
1 | 1 | # react-render-to-markdown |
2 | 2 |
|
| 3 | +[](https://www.npmjs.com/package/react-render-to-markdown) |
| 4 | +[](https://github.com/SoonIter/react-render-to-markdown/blob/main/LICENSE) |
| 5 | + |
| 6 | +Render React components to Markdown strings — like `renderToString` in `react-dom`, but outputs **Markdown** instead of HTML. |
| 7 | + |
| 8 | +Built on top of `react-reconciler`, this library creates a custom React renderer that traverses the React element tree and produces well-formatted Markdown. It follows **SSR-like behavior**: `useEffect`, `useLayoutEffect`, and `useInsertionEffect` are suppressed (as no-ops), while `useState`, `useMemo`, `useRef`, `useContext`, and other synchronous hooks work as expected. |
| 9 | + |
| 10 | +## Installation |
| 11 | + |
| 12 | +```bash |
| 13 | +npm install react-render-to-markdown |
| 14 | +``` |
| 15 | + |
| 16 | +## Quick Start |
| 17 | + |
3 | 18 | ```tsx |
4 | 19 | import { renderToMarkdownString } from 'react-render-to-markdown'; |
5 | 20 |
|
6 | | -const markdown = renderToMarkdownString(<h1>Hello, World!</h1>); |
| 21 | +const markdown = await renderToMarkdownString(<h1>Hello, World!</h1>); |
7 | 22 | console.log(markdown); // # Hello, World! |
8 | 23 | ``` |
9 | 24 |
|
10 | | -## Installation |
| 25 | +## Usage |
11 | 26 |
|
12 | | -```bash |
13 | | -npm install react-render-to-markdown |
| 27 | +### Basic HTML Elements |
| 28 | + |
| 29 | +```tsx |
| 30 | +import { renderToMarkdownString } from 'react-render-to-markdown'; |
| 31 | + |
| 32 | +await renderToMarkdownString( |
| 33 | + <div> |
| 34 | + <strong>foo</strong> |
| 35 | + <span>bar</span> |
| 36 | + </div>, |
| 37 | +); |
| 38 | +// Output: '**foo**bar' |
14 | 39 | ``` |
15 | 40 |
|
| 41 | +### React Components & Hooks |
| 42 | + |
| 43 | +Synchronous hooks (`useState`, `useMemo`, `useRef`, `useContext`, etc.) work as expected. Client-side effects (`useEffect`, `useLayoutEffect`) are automatically suppressed: |
| 44 | + |
| 45 | +```tsx |
| 46 | +import { createContext, useContext, useMemo, useState } from 'react'; |
| 47 | +import { renderToMarkdownString } from 'react-render-to-markdown'; |
| 48 | + |
| 49 | +const ThemeContext = createContext('light'); |
| 50 | + |
| 51 | +const Article = () => { |
| 52 | + const [count] = useState(42); |
| 53 | + const theme = useContext(ThemeContext); |
| 54 | + const doubled = useMemo(() => count * 2, [count]); |
| 55 | + |
| 56 | + return ( |
| 57 | + <> |
| 58 | + <h1>Hello World</h1> |
| 59 | + <p>Count: {count}, Doubled: {doubled}, Theme: {theme}</p> |
| 60 | + </> |
| 61 | + ); |
| 62 | +}; |
| 63 | + |
| 64 | +await renderToMarkdownString( |
| 65 | + <ThemeContext.Provider value="dark"> |
| 66 | + <Article /> |
| 67 | + </ThemeContext.Provider>, |
| 68 | +); |
| 69 | +// Output: |
| 70 | +// # Hello World |
| 71 | +// |
| 72 | +// Count: 42, Doubled: 84, Theme: dark |
| 73 | +``` |
| 74 | + |
| 75 | +### Code Blocks |
| 76 | + |
| 77 | +Fenced code blocks with language and title support: |
| 78 | + |
| 79 | +```tsx |
| 80 | +await renderToMarkdownString( |
| 81 | + <pre data-lang="ts" data-title="rspress.config.ts"> |
| 82 | + <code>{'const a = 1;\n'}</code> |
| 83 | + </pre>, |
| 84 | +); |
| 85 | +// Output: |
| 86 | +// ```ts title=rspress.config.ts |
| 87 | +// const a = 1; |
| 88 | +// ``` |
| 89 | +``` |
| 90 | + |
| 91 | +For languages that may contain triple backticks (like `markdown`, `mdx`, `md`), four backticks (``````) are automatically used as delimiters. |
| 92 | + |
| 93 | +## Supported Elements |
| 94 | + |
| 95 | +| HTML Element | Markdown Output | |
| 96 | +| --- | --- | |
| 97 | +| `<h1>` – `<h6>` | `#` – `######` headings | |
| 98 | +| `<p>` | Paragraph with trailing newlines | |
| 99 | +| `<strong>`, `<b>` | `**bold**` | |
| 100 | +| `<em>`, `<i>` | `*italic*` | |
| 101 | +| `<code>` | `` `inline code` `` | |
| 102 | +| `<pre>` + `<code>` | Fenced code block (` ``` `) | |
| 103 | +| `<a href="">` | `[text](url)` | |
| 104 | +| `<img>` | `` | |
| 105 | +| `<ul>`, `<ol>`, `<li>` | Unordered / ordered lists | |
| 106 | +| `<blockquote>` | `> blockquote` | |
| 107 | +| `<br>` | Line break | |
| 108 | +| `<hr>` | `---` horizontal rule | |
| 109 | +| `<table>`, `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>` | GFM table | |
| 110 | + |
| 111 | +Any unrecognized elements (e.g. `<div>`, `<span>`, `<section>`) render their children as-is, acting as transparent wrappers. |
| 112 | + |
| 113 | +## How It Works |
| 114 | + |
| 115 | +1. **Custom React Reconciler** — Uses `react-reconciler` to build a lightweight tree of `MarkdownNode` objects from your React element tree. |
| 116 | +2. **SSR-like Hook Behavior** — Client-side effects (`useEffect`, `useLayoutEffect`, `useInsertionEffect`) are intercepted and turned into no-ops, matching React's Fizz server renderer behavior. This ensures browser-only code (e.g. `document`, `window`) in effects never runs. |
| 117 | +3. **Tree-to-Markdown Serialization** — The `MarkdownNode` tree is serialized to a Markdown string via a recursive `toMarkdown` function. |
| 118 | + |
16 | 119 | ## Requirements |
17 | 120 |
|
18 | 121 | ```json |
19 | 122 | { |
20 | | - "react": "^18.2.0", |
21 | | - "react-reconciler": "^0.29.0" |
| 123 | + "react": ">=19.0.0", |
| 124 | + "react-reconciler": "^0.33.0" |
22 | 125 | } |
23 | 126 | ``` |
24 | 127 |
|
| 128 | +> **Note:** React 19 or above is required. The effect-interception mechanism relies on React 19's internal hooks dispatcher (`__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.H`). |
| 129 | +
|
25 | 130 | ## Used By |
26 | 131 |
|
27 | | -- [Rspress SSG-MD](https://rspress.rs/guide/basic/ssg-md) — Rspress uses this library to power its SSG-MD feature, which renders documentation pages as Markdown files instead of HTML. This enables Generative Engine Optimization (GEO) by generating `llms.txt` and `llms-full.txt` for better Agent accessibility. |
| 132 | +- [**Rspress SSG-MD**](https://rspress.rs/guide/basic/ssg-md) — Rspress uses this library to power its SSG-MD (Static Site Generation to Markdown) feature. SSG-MD renders documentation pages as Markdown files instead of HTML, generating `llms.txt` and `llms-full.txt` for [Generative Engine Optimization (GEO)](https://en.wikipedia.org/wiki/Generative_engine_optimization), enabling better accessibility for AI agents and large language models. |
28 | 133 |
|
29 | 134 | ## License |
30 | 135 |
|
31 | | -MIT License. |
| 136 | +[MIT](./LICENSE) |
0 commit comments