Skip to content

Commit c526ebf

Browse files
committed
feat: upgrade to React 19 and improve README documentation
1 parent f213a5f commit c526ebf

File tree

7 files changed

+283
-133
lines changed

7 files changed

+283
-133
lines changed

README.md

Lines changed: 113 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,136 @@
11
# react-render-to-markdown
22

3+
[![npm version](https://img.shields.io/npm/v/react-render-to-markdown.svg)](https://www.npmjs.com/package/react-render-to-markdown)
4+
[![license](https://img.shields.io/npm/l/react-render-to-markdown.svg)](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+
318
```tsx
419
import { renderToMarkdownString } from 'react-render-to-markdown';
520

6-
const markdown = renderToMarkdownString(<h1>Hello, World!</h1>);
21+
const markdown = await renderToMarkdownString(<h1>Hello, World!</h1>);
722
console.log(markdown); // # Hello, World!
823
```
924

10-
## Installation
25+
## Usage
1126

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'
1439
```
1540

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>` | `![alt](src)` |
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+
16119
## Requirements
17120

18121
```json
19122
{
20-
"react": "^18.2.0",
21-
"react-reconciler": "^0.29.0"
123+
"react": ">=19.0.0",
124+
"react-reconciler": "^0.33.0"
22125
}
23126
```
24127

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+
25130
## Used By
26131

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.
28133

29134
## License
30135

31-
MIT License.
136+
[MIT](./LICENSE)

package.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-render-to-markdown",
3-
"version": "18.3.1",
3+
"version": "19.0.0",
44
"description": "render React components to Markdown strings for SSG-MD",
55
"repository": {
66
"type": "git",
@@ -28,23 +28,22 @@
2828
"pre-commit": "npm run lint:write"
2929
},
3030
"devDependencies": {
31-
"react": "^18.2.0",
32-
"react-reconciler": "^0.29.0",
31+
"react": "^19",
3332
"vitest": "^3.2.4",
3433
"@biomejs/biome": "^1.9.4",
3534
"@rslib/core": "0.15.1",
3635
"rsbuild-plugin-publint": "^0.3.3",
3736
"typescript": "^5.9.3",
3837
"simple-git-hooks": "^2.13.1",
3938
"@types/node": "^24.8.1",
40-
"@types/react": "^18.3.12",
41-
"@types/react-reconciler": "^0.28.0"
39+
"@types/react": "^19",
40+
"@types/react-reconciler": "^0.33.0"
4241
},
4342
"dependencies": {
44-
"react-reconciler": "^0.29.0"
43+
"react-reconciler": "0.33.0"
4544
},
4645
"peerDependencies": {
47-
"react": ">=18.2.0"
46+
"react": ">=19"
4847
},
4948
"keywords": ["react", "markdown", "renderer", "react-reconciler"],
5049
"author": "SoonIter <sooniter@gmail.com>",

pnpm-lock.yaml

Lines changed: 27 additions & 51 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
onlyBuiltDependencies:
2+
- '@biomejs/biome'
3+
- core-js
4+
- esbuild
5+
- simple-git-hooks

0 commit comments

Comments
 (0)