|
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 | +The major version of `react-render-to-markdown` follows the React version. Install the one that matches your project: |
| 13 | + |
| 14 | +```bash |
| 15 | +# React 19 |
| 16 | +npm install react-render-to-markdown@19 |
| 17 | + |
| 18 | +# React 18 |
| 19 | +npm install react-render-to-markdown@18 |
| 20 | +``` |
| 21 | + |
| 22 | +## Quick Start |
| 23 | + |
3 | 24 | ```tsx |
4 | 25 | import { renderToMarkdownString } from 'react-render-to-markdown'; |
5 | 26 |
|
6 | | -const markdown = renderToMarkdownString(<h1>Hello, World!</h1>); |
| 27 | +const markdown = await renderToMarkdownString(<h1>Hello, World!</h1>); |
7 | 28 | console.log(markdown); // # Hello, World! |
8 | 29 | ``` |
9 | 30 |
|
10 | | -## Installation |
| 31 | +## Usage |
11 | 32 |
|
12 | | -```bash |
13 | | -npm install react-render-to-markdown |
| 33 | +### Basic HTML Elements |
| 34 | + |
| 35 | +```tsx |
| 36 | +import { renderToMarkdownString } from 'react-render-to-markdown'; |
| 37 | + |
| 38 | +await renderToMarkdownString( |
| 39 | + <div> |
| 40 | + <strong>foo</strong> |
| 41 | + <span>bar</span> |
| 42 | + </div>, |
| 43 | +); |
| 44 | +// Output: '**foo**bar' |
14 | 45 | ``` |
15 | 46 |
|
| 47 | +### React Components & Hooks |
| 48 | + |
| 49 | +Synchronous hooks (`useState`, `useMemo`, `useRef`, `useContext`, etc.) work as expected. Client-side effects (`useEffect`, `useLayoutEffect`) are automatically suppressed: |
| 50 | + |
| 51 | +```tsx |
| 52 | +import { createContext, useContext, useMemo, useState } from 'react'; |
| 53 | +import { renderToMarkdownString } from 'react-render-to-markdown'; |
| 54 | + |
| 55 | +const ThemeContext = createContext('light'); |
| 56 | + |
| 57 | +const Article = () => { |
| 58 | + const [count] = useState(42); |
| 59 | + const theme = useContext(ThemeContext); |
| 60 | + const doubled = useMemo(() => count * 2, [count]); |
| 61 | + |
| 62 | + return ( |
| 63 | + <> |
| 64 | + <h1>Hello World</h1> |
| 65 | + <p>Count: {count}, Doubled: {doubled}, Theme: {theme}</p> |
| 66 | + </> |
| 67 | + ); |
| 68 | +}; |
| 69 | + |
| 70 | +await renderToMarkdownString( |
| 71 | + <ThemeContext.Provider value="dark"> |
| 72 | + <Article /> |
| 73 | + </ThemeContext.Provider>, |
| 74 | +); |
| 75 | +// Output: |
| 76 | +// # Hello World |
| 77 | +// |
| 78 | +// Count: 42, Doubled: 84, Theme: dark |
| 79 | +``` |
| 80 | + |
| 81 | +### Code Blocks |
| 82 | + |
| 83 | +Fenced code blocks with language and title support: |
| 84 | + |
| 85 | +```tsx |
| 86 | +await renderToMarkdownString( |
| 87 | + <pre data-lang="ts" data-title="rspress.config.ts"> |
| 88 | + <code>{'const a = 1;\n'}</code> |
| 89 | + </pre>, |
| 90 | +); |
| 91 | +// Output: |
| 92 | +// ```ts title=rspress.config.ts |
| 93 | +// const a = 1; |
| 94 | +// ``` |
| 95 | +``` |
| 96 | + |
| 97 | +For languages that may contain triple backticks (like `markdown`, `mdx`, `md`), four backticks (``````) are automatically used as delimiters. |
| 98 | + |
| 99 | +## Supported Elements |
| 100 | + |
| 101 | +| HTML Element | Markdown Output | |
| 102 | +| --- | --- | |
| 103 | +| `<h1>` – `<h6>` | `#` – `######` headings | |
| 104 | +| `<p>` | Paragraph with trailing newlines | |
| 105 | +| `<strong>`, `<b>` | `**bold**` | |
| 106 | +| `<em>`, `<i>` | `*italic*` | |
| 107 | +| `<code>` | `` `inline code` `` | |
| 108 | +| `<pre>` + `<code>` | Fenced code block (` ``` `) | |
| 109 | +| `<a href="">` | `[text](url)` | |
| 110 | +| `<img>` | `` | |
| 111 | +| `<ul>`, `<ol>`, `<li>` | Unordered / ordered lists | |
| 112 | +| `<blockquote>` | `> blockquote` | |
| 113 | +| `<br>` | Line break | |
| 114 | +| `<hr>` | `---` horizontal rule | |
| 115 | +| `<style>` | Ignored | |
| 116 | +| `<table>`, `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>` | GFM table | |
| 117 | + |
| 118 | +Any unrecognized elements (e.g. `<div>`, `<span>`, `<section>`) render their children as-is, acting as transparent wrappers. |
| 119 | + |
| 120 | +## How It Works |
| 121 | + |
| 122 | +1. **Custom React Reconciler** — Uses `react-reconciler` to build a lightweight tree of `MarkdownNode` objects from your React element tree. |
| 123 | +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. |
| 124 | +3. **Tree-to-Markdown Serialization** — The `MarkdownNode` tree is serialized to a Markdown string via a recursive `toMarkdown` function. |
| 125 | + |
16 | 126 | ## Requirements |
17 | 127 |
|
18 | 128 | ```json |
19 | 129 | { |
20 | | - "react": "^18.2.0", |
| 130 | + "react": ">=18.2.0", |
21 | 131 | "react-reconciler": "^0.29.0" |
22 | 132 | } |
23 | 133 | ``` |
24 | 134 |
|
| 135 | +> **Note:** React 18.2 or above is required. The effect-interception mechanism relies on React 18's internal hooks dispatcher (`__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current`). |
| 136 | +
|
25 | 137 | ## Used By |
26 | 138 |
|
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. |
| 139 | +- [**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 | 140 |
|
29 | 141 | ## License |
30 | 142 |
|
31 | | -MIT License. |
| 143 | +[MIT](./LICENSE) |
0 commit comments