33 | ```
34 |
35 | ## 9.1.0 - 2025-02-20
36 |
37 | * [`6ce120e`](https://github.com/remarkjs/react-markdown/commit/6ce120e)
38 | Add support for async plugins
39 |
40 | ## 9.0.3 - 2025-01-06
41 |
42 | (same as 9.0.2 but now with d.ts files)
43 |
44 | ## 9.0.2 - 2025-01-06
45 |
46 | * [`b151a90`](https://github.com/remarkjs/react-markdown/commit/b151a90)
47 | Fix types for React 19
48 | * [`6962af7`](https://github.com/remarkjs/react-markdown/commit/6962af7)
49 | Add declaration maps
50 | * [`aa5933b`](https://github.com/remarkjs/react-markdown/commit/aa5933b)
51 | Refactor to use `@import` to import types
52 |
53 | ## 9.0.1 - 2023-11-13
54 |
55 | * [`d8e3787`](https://github.com/remarkjs/react-markdown/commit/d8e3787)
56 | Fix double encoding in new url transform
57 |
58 | ## 9.0.0 - 2023-09-27
59 |
60 | * [`b67d714`](https://github.com/remarkjs/react-markdown/commit/b67d714)
61 | Change to require Node.js 16\
62 | **migrate**: update too
63 | * [`ec2b134`](https://github.com/remarkjs/react-markdown/commit/ec2b134)
64 | Change to require React 18\
65 | **migrate**: update too
66 | * [`bf5824f`](https://github.com/remarkjs/react-markdown/commit/bf5824f)
67 | Change to use `exports`\
68 | **migrate**: don’t use private APIs
69 | * [`c383a45`](https://github.com/remarkjs/react-markdown/commit/c383a45)
70 | Update `@types/hast`, utilities, plugins, etc\
71 | **migrate**: update too
72 | * [`eca5e6b`](https://github.com/remarkjs/react-markdown/commit/eca5e6b)
73 | [`08ead9e`](https://github.com/remarkjs/react-markdown/commit/08ead9e)
74 | Replace `transformImageUri`, `transformLinkUri` w/ `urlTransform`\
75 | **migrate**: see “Add `urlTransform`” below
76 | * [`de29396`](https://github.com/remarkjs/react-markdown/commit/de29396)
77 | Remove `linkTarget` option\
78 | **migrate**: see “Remove `linkTarget`” below
79 | * [`4346276`](https://github.com/remarkjs/react-markdown/commit/4346276)
80 | Remove support for passing custom props to components\
81 | **migrate**: see “Remove `includeElementIndex`”, “Remove `rawSourcePos`”,
82 | “Remove `sourcePos`”, “Remove extra props passed to certain components”
83 | below
84 | * [`c0dfbd6`](https://github.com/remarkjs/react-markdown/commit/c0dfbd6)
85 | Remove UMD bundle from package\
86 | **migrate**: use `esm.sh` or a CDN or so
87 | * [`e12b5e9`](https://github.com/remarkjs/react-markdown/commit/e12b5e9)
88 | Remove `prop-types`\
89 | **migrate**: use TypeScript
90 | * [`4eb7aa0`](https://github.com/remarkjs/react-markdown/commit/4eb7aa0)
91 | Change to throw errors for removed props\
92 | **migrate**: don’t pass options that don’t do things
93 | * [`8aabf74`](https://github.com/remarkjs/react-markdown/commit/8aabf74)
94 | Change to improve error messages\
95 | **migrate**: expect better messages
96 |
97 | ### Add `urlTransform`
98 |
99 | The `transformImageUri` and `transformLinkUri` were removed.
100 | Having two functions is a bit much, particularly because there are more URLs
101 | you might want to change (or which might be unsafe so *we* make them safe).
102 | And their name and APIs were a bit weird.
103 | You can use the new `urlTransform` prop instead to change all your URLs.
104 |
105 | ### Remove `linkTarget`
106 |
107 | The `linkTarget` option was removed; you should likely not set targets.
108 | If you want to, use
109 | [`rehype-external-links`](https://github.com/rehypejs/rehype-external-links).
110 |
111 | ### Remove `includeElementIndex`
112 |
113 | The `includeElementIndex` option was removed, so `index` is never passed to
114 | components.
115 | Write a plugin to pass `index`:
116 |
117 |
118 | Show example of plugin
119 |
120 | ```js
121 | import {visit} from 'unist-util-visit'
122 |
123 | function rehypePluginAddingIndex() {
124 | /**
125 | * @param {import('hast').Root} tree
126 | * @returns {undefined}
127 | */
128 | return function (tree) {
129 | visit(tree, function (node, index) {
130 | if (node.type === 'element' && typeof index === 'number') {
131 | node.properties.index = index
132 | }
133 | })
134 | }
135 | }
136 | ```
137 |
138 |
139 |
140 | ### Remove `rawSourcePos`
141 |
142 | The `rawSourcePos` option was removed, so `sourcePos` is never passed to
143 | components.
144 | All components are passed `node`, so you can get `node.position` from them.
145 |
146 | ### Remove `sourcePos`
147 |
148 | The `sourcePos` option was removed, so `data-sourcepos` is never passed to
149 | elements.
150 | Write a plugin to pass `index`:
151 |
152 |
153 | Show example of plugin
154 |
155 | ```js
156 | import {stringifyPosition} from 'unist-util-stringify-position'
157 | import {visit} from 'unist-util-visit'
158 |
159 | function rehypePluginAddingIndex() {
160 | /**
161 | * @param {import('hast').Root} tree
162 | * @returns {undefined}
163 | */
164 | return function (tree) {
165 | visit(tree, function (node) {
166 | if (node.type === 'element') {
167 | node.properties.dataSourcepos = stringifyPosition(node.position)
168 | }
169 | })
170 | }
171 | }
172 | ```
173 |
174 |
175 |
176 | ### Remove extra props passed to certain components
177 |
178 | When overwriting components, these props are no longer passed:
179 |
180 | * `inline` on `code`
181 | — create a plugin or use `pre` for the block
182 | * `level` on `h1`, `h2`, `h3`, `h4`, `h5`, `h6`
183 | — check `node.tagName` instead
184 | * `checked` on `li`
185 | — check `task-list-item` class or check `props.children`
186 | * `index` on `li`
187 | — create a plugin
188 | * `ordered` on `li`
189 | — create a plugin or check the parent
190 | * `depth` on `ol`, `ul`
191 | — create a plugin
192 | * `ordered` on `ol`, `ul`
193 | — check `node.tagName` instead
194 | * `isHeader` on `td`, `th`
195 | — check `node.tagName` instead
196 | * `isHeader` on `tr`
197 | — create a plugin or check children
198 |
199 | ## 8.0.7 - 2023-04-12
200 |
201 | * [`c289176`](https://github.com/remarkjs/react-markdown/commit/c289176)
202 | Fix performance for keys
203 | by [**@wooorm**](https://github.com/wooorm)
204 | in [#738](https://github.com/remarkjs/react-markdown/pull/738)
205 | * [`9034dbd`](https://github.com/remarkjs/react-markdown/commit/9034dbd)
206 | Fix types in syntax highlight example
207 | by [**@dlqqq**](https://github.com/dlqqq)
208 | in [#736](https://github.com/remarkjs/react-markdown/pull/736)
209 |
210 | **Full Changelog**:
211 |
212 | ## 8.0.6 - 2023-03-20
213 |
214 | * [`33ab015`](https://github.com/remarkjs/react-markdown/commit/33ab015)
215 | Update to TS 5\
216 | by [**@Methuselah96**](https://github.com/Methuselah96)
217 | in [#734](https://github.com/remarkjs/react-markdown/issues/734)
218 |
219 | ## 8.0.5 - 2023-01-17
220 |
221 | * [`d640d40`](https://github.com/remarkjs/react-markdown/commit/d640d40)
222 | Update to use `node16` module resolution in `tsconfig.json`\
223 | by [**@ChristianMurphy**](https://github.com/ChristianMurphy)
224 | in [#723](https://github.com/remarkjs/react-markdown/pull/723)
225 | * [`402fea3`](https://github.com/remarkjs/react-markdown/commit/402fea3)
226 | Fix typo in `plugins` deprecation message\
227 | by [**@marc2332**](https://github.com/marc2332)
228 | in [#719](https://github.com/remarkjs/react-markdown/pull/719)
229 | * [`4f98f73`](https://github.com/remarkjs/react-markdown/commit/4f98f73)
230 | Remove deprecated and unneeded `defaultProps`\
231 | by [**@Lepozepo**](https://github.com/Lepozepo)
232 | in [#718](https://github.com/remarkjs/react-markdown/pull/718)
233 |
234 | ## 8.0.4 - 2022-12-01
235 |
236 | * [`9b20440`](https://github.com/remarkjs/react-markdown/commit/9b20440)
237 | Fix type of `td`, `th` props\
238 | by [**@lucasassisrosa**](https://github.com/lucasassisrosa)
239 | in [#714](https://github.com/remarkjs/react-markdown/pull/714)
240 | * [`cfe075b`](https://github.com/remarkjs/react-markdown/commit/cfe075b)
241 | Add clarification of `alt` on `img` in docs\
242 | by [**@cballenar**](https://github.com/cballenar)
243 | in [#692](https://github.com/remarkjs/react-markdown/pull/692)
244 |
245 | ## 8.0.3 - 2022-04-20
246 |
247 | * [`a2fb833`](https://github.com/remarkjs/react-markdown/commit/a2fb833)
248 | Fix prop types of plugins\
249 | by [**@starpit**](https://github.com/starpit)
250 | in [#683](https://github.com/remarkjs/react-markdown/pull/683)
251 |
252 | ## 8.0.2 - 2022-03-31
253 |
254 | * [`2712227`](https://github.com/remarkjs/react-markdown/commit/2712227)
255 | Update `react-is`
256 | * [`704c3c6`](https://github.com/remarkjs/react-markdown/commit/704c3c6)
257 | Fix TypeScript bug by adding workaround\
258 | by [**@Methuselah96**](https://github.com/Methuselah96)
259 | in [#676](https://github.com/remarkjs/react-markdown/pull/676)
260 |
261 | ## 8.0.1 - 2022-03-14
262 |
263 | * [`c23ecf6`](https://github.com/remarkjs/react-markdown/commit/c23ecf6)
264 | Add missing dependency for types\
265 | by [**@Methuselah96**](https://github.com/Methuselah96)
266 | in [#675](https://github.com/remarkjs/react-markdown/pull/675)
267 |
268 | ## 8.0.0 - 2022-01-17
269 |
270 |
271 |
272 | * [`cd845c9`](https://github.com/remarkjs/react-markdown/commit/cd845c9)
273 | Remove deprecated `plugins` option\
274 | (**migrate by renaming it to `remarkPlugins`**)
275 | * [`36e4916`](https://github.com/remarkjs/react-markdown/commit/36e4916)
276 | Update [`remark-rehype`](https://github.com/remarkjs/remark-rehype),
277 | add support for passing it options\
278 | by [**@peolic**](https://github.com/peolic)
279 | in [#669](https://github.com/remarkjs/react-markdown/pull/669)\
280 | (**migrate by removing `remark-footnotes` and updating `remark-gfm` if you
281 | were using them, otherwise you shouldn’t notice this**)
282 |
283 | ## 7.1.2 - 2022-01-02
284 |
285 | * [`656a4fa`](https://github.com/remarkjs/react-markdown/commit/656a4fa)
286 | Fix `ref` in types\
287 | by [**@ChristianMurphy**](https://github.com/ChristianMurphy)
288 | in [#668](https://github.com/remarkjs/react-markdown/pull/668)
289 |
290 | ## 7.1.1 - 2021-11-29
291 |
292 | * [`4185f06`](https://github.com/remarkjs/react-markdown/commit/4185f06)
293 | Add improved docs\
294 | by [**@wooorm**](https://github.com/wooorm)
295 | in [#657](https://github.com/remarkjs/react-markdown/pull/657)
296 |
297 | ## 7.1.0 - 2021-10-21
298 |
299 | * [`7b8a829`](https://github.com/remarkjs/react-markdown/commit/7b8a829)
300 | Add support for `SpecialComponents` to be any `ComponentType`\
301 | by [**@Methuselah96**](https://github.com/Methuselah96)
302 | in [#640](https://github.com/remarkjs/react-markdown/pull/640)
303 | * [`a7c26fc`](https://github.com/remarkjs/react-markdown/commit/a7c26fc)
304 | Remove warning on whitespace in tables
305 |
306 | ## 7.0.1 - 2021-08-26
307 |
308 | * [`ec387c2`](https://github.com/remarkjs/react-markdown/commit/ec387c2)
309 | Add improved type for `linkTarget` as string
310 | * [`5af6bc7`](https://github.com/remarkjs/react-markdown/commit/5af6bc7)
311 | Fix to correctly compile intrinsic types
312 |
313 | ## 7.0.0 - 2021-08-13
314 |
315 | Welcome to version 7.
316 | This a major release and therefore contains breaking changes.
317 |
318 | ### Breaking changes
319 |
320 | * [`01b11fe`](https://github.com/remarkjs/react-markdown/commit/01b11fe)
321 | [`c613efd`](https://github.com/remarkjs/react-markdown/commit/c613efd)
322 | [`a1e1c3f`](https://github.com/remarkjs/react-markdown/commit/a1e1c3f)
323 | [`aeee9ac`](https://github.com/remarkjs/react-markdown/commit/aeee9ac)
324 | Use ESM
325 | (please [read this](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c))
326 | * [`3dffd6a`](https://github.com/remarkjs/react-markdown/commit/3dffd6a)
327 | Update dependencies
328 | (upgrade all your plugins and this should go fine)
329 |
330 | ### Internals
331 |
332 | * [`8b5481c`](https://github.com/remarkjs/react-markdown/commit/8b5481c)
333 | [`fb1b512`](https://github.com/remarkjs/react-markdown/commit/fb1b512)
334 | [`144af79`](https://github.com/remarkjs/react-markdown/commit/144af79)
335 | Replace `jest` with `uvu`
336 | * [`8c572df`](https://github.com/remarkjs/react-markdown/commit/8c572df)
337 | Replace `rollup` with `esbuild`
338 | * [`8737eac`](https://github.com/remarkjs/react-markdown/commit/8737eac)
339 | [`28d4c75`](https://github.com/remarkjs/react-markdown/commit/28d4c75)
340 | [`b2dd046`](https://github.com/remarkjs/react-markdown/commit/b2dd046)
341 | Refactor code-style
342 |
343 | ## 6.0.3 - 2021-07-30
344 |
345 | * [`13367ed`](https://github.com/remarkjs/react-markdown/commit/13367ed)
346 | Fix types to include each element w/ its properties
347 | * [`0a1931a`](https://github.com/remarkjs/react-markdown/commit/0a1931a)
348 | Fix to add min version of `property-information`
349 |
350 | ## 6.0.2 - 2021-05-06
351 |
352 | * [`cefc02d`](https://github.com/remarkjs/react-markdown/commit/cefc02d)
353 | Add string type for `className`s
354 | * [`6355e45`](https://github.com/remarkjs/react-markdown/commit/6355e45)
355 | Fix to pass `vfile` to plugins
356 | * [`5cf6e1b`](https://github.com/remarkjs/react-markdown/commit/5cf6e1b)
357 | Fix to add warning when non-strings are given as `children`
358 |
359 | ## 6.0.1 - 2021-04-23
360 |
361 | * [`2e956be`](https://github.com/remarkjs/react-markdown/commit/2e956be)
362 | Fix whitespace in table elements
363 | * [`d36048a`](https://github.com/remarkjs/react-markdown/commit/d36048a)
364 | Add architecture section to readme
365 |
366 | ## 6.0.0 - 2021-04-15
367 |
368 | Welcome to version 6.
369 | This a major release and therefore contains breaking changes.
370 |
371 | ### Change `renderers` to `components`
372 |
373 | `react-markdown` used to let you define components for *markdown* constructs
374 | (`link`, `delete`, `break`, etc).
375 | This proved complex as users didn’t know about those names or markdown
376 | peculiarities (such as that there are fully formed links *and* link references).
377 |
378 | See [**GH-549**](https://github.com/remarkjs/react-markdown/issues/549) for more
379 | on why this changed.
380 | See [**Appendix B: Components** in
381 | `readme.md`](https://github.com/remarkjs/react-markdown#appendix-b-components)
382 | for more on components.
383 |
384 |
385 | Show example of needed change
386 |
387 | Before (**broken**):
388 |
389 | ```js
390 |
394 | }}
395 | >{`***`}
396 | ```
397 |
398 | Now (**fixed**):
399 |
400 | ```js
401 |
405 | }}
406 | >{`***`}
407 | ```
408 |
409 |
410 |
411 |
412 | Show conversion table
413 |
414 | | Type (`renderers`) | Tag names (`components`) |
415 | | ----------------------------------- | --------------------------------------- |
416 | | `blockquote` | `blockquote` |
417 | | `break` | `br` |
418 | | `code`, `inlineCode` | `code`, `pre`**\*** |
419 | | `definition` | **†** |
420 | | `delete` | `del`**‡** |
421 | | `emphasis` | `em` |
422 | | `heading` | `h1`, `h2`, `h3`, `h4`, `h5`, `h6`**§** |
423 | | `html`, `parsedHtml`, `virtualHtml` | **‖** |
424 | | `image`, `imageReference` | `img`**†** |
425 | | `link`, `linkReference` | `a`**†** |
426 | | `list` | `ol`, `ul`**¶** |
427 | | `listItem` | `li` |
428 | | `paragraph` | `p` |
429 | | `root` | **\*\*** |
430 | | `strong` | `strong` |
431 | | `table` | `table`**‡** |
432 | | `tableHead` | `thead`**‡** |
433 | | `tableBody` | `tbody`**‡** |
434 | | `tableRow` | `tr`**‡** |
435 | | `tableCell` | `td`, `th`**‡** |
436 | | `text` | |
437 | | `thematicBreak` | `hr` |
438 |
439 | * **\*** It’s possible to differentiate between code based on the `inline`
440 | prop.
441 | Block code is also wrapped in a `pre`
442 | * **†** Resource (`[text](url)`) and reference (`[text][id]`) style links and
443 | images (and their definitions) are now resolved and treated the same
444 | * **‡** Available when using
445 | [`remark-gfm`](https://github.com/remarkjs/remark-gfm)
446 | * **§** It’s possible to differentiate between heading based on the `level`
447 | prop
448 | * **‖** When using `rehype-raw` (see below), components for those elements
449 | can also be used (for example, `abbr` for
450 | `HTML`)
451 | * **¶** It’s possible to differentiate between lists based on the `ordered`
452 | prop
453 | * **\*\*** Wrap `ReactMarkdown` in a component instead
454 |
455 |
456 |
457 | ### Add `rehypePlugins`
458 |
459 | We’ve added another plugin system:
460 | [**rehype**](https://github.com/rehypejs/rehype).
461 | It’s similar to remark (what we’re using for markdown) but for HTML.
462 |
463 | There are many rehype plugins.
464 | Some examples are
465 | [`@mapbox/rehype-prism`](https://github.com/mapbox/rehype-prism)
466 | (syntax highlighting with Prism),
467 | [`rehype-katex`](https://github.com/remarkjs/remark-math/tree/HEAD/packages/rehype-katex)
468 | (rendering math with KaTeX), or
469 | [`rehype-autolink-headings`](https://github.com/rehypejs/rehype-autolink-headings)
470 | (adding links to headings).
471 |
472 | See [List of plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md)
473 | for more plugins.
474 |
475 |
476 | Show example of feature
477 |
478 | ```js
479 | import rehypeHighlight from 'rehype-highlight'
480 |
481 | {`~~~js
482 | console.log(1)
483 | ~~~`}
484 | ```
485 |
486 |
487 |
488 | ### Remove buggy HTML in markdown parser
489 |
490 | In a lot of cases, you should not use HTML in markdown: it’s most always unsafe.
491 | And it defeats much of the purpose of this project (not relying on
492 | `dangerouslySetInnerHTML`).
493 |
494 | `react-markdown` used to have an opt-in HTML parser with a bunch of bugs.
495 | As we now support rehype plugins, we can defer that work to a rehype plugin.
496 | To support HTML in markdown with `react-markdown`, use
497 | [`rehype-raw`](https://github.com/rehypejs/rehype-raw).
498 | The `astPlugins` and `allowDangerousHtml` (previously called `escapeHtml`) props
499 | are no longer needed and were removed.
500 |
501 | When using `rehype-raw`, you should probably use
502 | [`rehype-sanitize`](https://github.com/rehypejs/rehype-sanitize)
503 | too.
504 |
505 |
506 | Show example of needed change
507 |
508 | Before (**broken**):
509 |
510 | ```js
511 | import MarkdownWithHtml from 'react-markdown/with-html'
512 |
513 | {`# Hello, world!`}
514 | ```
515 |
516 | Now (**fixed**):
517 |
518 | ```js
519 | import Markdown from 'react-markdown'
520 | import rehypeRaw from 'rehype-raw'
521 | import rehypeSanitize from 'rehype-sanitize'
522 |
523 | {`# Hello, world!`}
524 | ```
525 |
526 |
527 |
528 | ### Change `source` to `children`
529 |
530 | Instead of passing a `source` pass `children` instead:
531 |
532 |
533 | Show example of needed change
534 |
535 | Before (**broken**):
536 |
537 | ```js
538 |
539 | ```
540 |
541 | Now (**fixed**):
542 |
543 | ```js
544 | {`some
545 | markdown`}
546 | ```
547 |
548 | Or (**also fixed**):
549 |
550 | ```js
551 |
553 | ```
554 |
555 |
556 |
557 | ### Replace `allowNode`, `allowedTypes`, and `disallowedTypes`
558 |
559 | Similar to the `renderers` to `components` change, the filtering options
560 | also changed from being based on markdown names towards being based on HTML
561 | names: `allowNode` to `allowElement`, `allowedTypes` to `allowedElements`, and
562 | `disallowedTypes` to `disallowedElements`.
563 |
564 |
565 | Show example of needed change
566 |
567 | Before (**broken**):
568 |
569 | ```js
570 | {``}
574 | ```
575 |
576 | Now (**fixed**):
577 |
578 | ```js
579 | {``}
583 | ```
584 |
585 | ***
586 |
587 | Before (**broken**):
588 |
589 | ```js
590 | node.type !== 'heading' || node.depth !== 1}
593 | >{`# main heading`}
594 | ```
595 |
596 | Now (**fixed**):
597 |
598 | ```js
599 | element.tagName !== 'h1'}
602 | >{`# main heading`}
603 | ```
604 |
605 |
606 |
607 | ### Change `includeNodeIndex` to `includeElementIndex`
608 |
609 | Similar to the `renderers` to `components` change, this option to pass more info
610 | to components also changed from being based on markdown to being based on HTML.
611 |
612 |
613 | Show example of needed change
614 |
615 | Before (**broken**):
616 |
617 | ```js
618 |
622 | }}
623 | >{`Some text`}
624 | ```
625 |
626 | Now (**fixed**):
627 |
628 | ```js
629 |
633 | }}
634 | >{`Some text`}
635 | ```
636 |
637 |
638 |
639 | ### Change signature of `transformLinkUri`, `linkTarget`
640 |
641 | The second parameter of these functions (to rewrite `href` on `a` or to define
642 | `target` on `a`) are now [hast](https://github.com/syntax-tree/hast) (HTML AST)
643 | instead of [mdast](https://github.com/syntax-tree/mdast) (markdown AST).
644 |
645 | ### Change signature of `transformImageUri`
646 |
647 | The second parameter of this function was always `undefined` and the fourth was
648 | the `alt` (`string`) on the image.
649 | The second parameter is now that `alt`.
650 |
651 | ### Remove support for React 15, IE11
652 |
653 | We now use ES2015 (such as `Object.assign`) and removed certain hacks to work
654 | with React 15 and older.
655 |
656 | ## 5.0.3 - 2020-10-23
657 |
658 | * [`bb0bdde`](https://github.com/remarkjs/react-markdown/commit/bb0bdde)
659 | Unlock peer dependency on React to allow v17
660 | * [`24e42bd`](https://github.com/remarkjs/react-markdown/commit/24e42bd)
661 | Fix exception on missing element from `html-to-react`
662 | * [`3d363e9`](https://github.com/remarkjs/react-markdown/commit/3d363e9)
663 | Fix umd browser build
664 |
665 | ## 5.0.2 - 2020-10-23
666 |
667 | * [`4dadaba`](https://github.com/remarkjs/react-markdown/commit/4dadaba)
668 | Fix to allow combining `allowedTypes`, `unwrapDisallowed` in types
669 |
670 | ## 5.0.1 - 2020-10-21
671 |
672 | * [`c3dc5ee`](https://github.com/remarkjs/react-markdown/commit/c3dc5ee)
673 | Fix to not crash on empty text nodes
674 |
675 | ## 5.0.0 - 2020-10-19
676 |
677 | ### BREAKING
678 |
679 | #### Maintained by [unified](https://unifiedjs.com)
680 |
681 | This project is now maintained by the unified collective, which also houses the
682 | underlying tools used in `react-markdown`: hundreds of projects for working with
683 | markdown and markup related things (including MDX).
684 | We have cleaned the project: updated dependencies, improved
685 | docs/tests/coverage/types, cleaned the issue tracker, and fixed a couple of
686 | bugs, but otherwise *much should be the same*.
687 |
688 | #### Upgrade `remark-parse`
689 |
690 | The parser used in `react-markdown` has been upgraded to the latest version.
691 | It is now 100% CommonMark compliant: that means it works the same as in other
692 | places, such as Discourse, Reddit, Stack Overflow, and GitHub.
693 | Note that GitHub does extend CommonMark: to match how Markdown works on GitHub,
694 | use the [`remark-gfm`](https://github.com/remarkjs/remark-gfm) plugin.
695 |
696 | * [`remark-parse@9.0.0`](https://github.com/remarkjs/remark/releases/tag/remark-parse%409.0.0)
697 | * [`remark-parse@8.0.0`](https://github.com/remarkjs/remark/releases/tag/remark-parse%408.0.0)
698 | * [`remark-parse@7.0.0`](https://github.com/remarkjs/remark/releases/tag/remark-parse%407.0.0)
699 | * [`remark-parse@6.0.0`](https://github.com/remarkjs/remark/releases/tag/remark-parse%406.0.0)
700 |
701 | #### New serializer property: `node`
702 |
703 | A new `node` prop is passed to all non-tag/non-fragment renderers.
704 | This contains the raw [mdast](https://github.com/syntax-tree/mdast) AST node,
705 | which opens up a number of interesting possibilities.
706 | The breaking change is for renderers which blindly spread their props to an
707 | underlying component/tag.
708 | For instance:
709 |
710 | ```js
711 | }} … />
712 | ```
713 |
714 | Should now be written as:
715 |
716 | ```js
717 | }} … />
718 | ```
719 |
720 | #### List/list item `tight` property replaced by `spread`
721 |
722 | Previously, the `tight` property would hint as to whether or not list items
723 | should be wrapped in paragraphs.
724 | This logic has now been replaced by a new `spread` property, which behaves
725 | slightly differently.
726 | [Read more](https://github.com/remarkjs/remark/pull/364).
727 |
728 | ## 4.3.1 - 2020-01-05
729 |
730 | ### Fixes
731 |
732 | * (Typings) Fix incorrect typescript definitions (Peng Guanwen)
733 |
734 | ## 4.3.0 - 2020-01-02
735 |
736 | ### Fixes
737 |
738 | * (Typings) Add typings for `react-markdown/html-parser` (Peng Guanwen)
739 |
740 | ## 4.2.2 - 2019-09-03
741 |
742 | ### Fixes
743 |
744 | * (Typings) Inline `RemarkParseOptions` for now (Espen Hovlandsdal)
745 |
746 | ## 4.2.1 - 2019-09-01
747 |
748 | ### Fixes
749 |
750 | * (Typings) Fix incorrect import - `RemarkParseOptions` (Jakub Chrzanowski)
751 |
752 | ## 4.2.0 - 2019-09-01
753 |
754 | ### Added
755 |
756 | * Add support for plugins that use AST transformations (Frankie Ali)
757 |
758 | ### Fixes
759 |
760 | * (Typings) Add `parserOptions` to type definitions (Ted Piotrowski)
761 | * Allow renderer to be any React element type (Nathan Bierema)
762 |
763 | ## 4.1.0 - 2019-06-24
764 |
765 | ### Added
766 |
767 | * Add prop `parserOptions` to specify options for remark-parse (Kelvin Chan)
768 |
769 | ## 4.0.9 - 2019-06-22
770 |
771 | ### Fixes
772 |
773 | * (Typings) Make transformLinkUri & transformImageUri actually nullable
774 | (Florentin Luca Rieger)
775 |
776 | ## 4.0.8 - 2019-04-14
777 |
778 | ### Fixes
779 |
780 | * Fix HTML parsing of elements with a single child vs. multiple children
781 | (Nicolas Venegas)
782 |
783 | ## 4.0.7 - 2019-04-14
784 |
785 | ### Fixes
786 |
787 | * Fix matching of replaced non-void elements in HTML parser plugin (Nicolas
788 | Venegas)
789 | * Fix HTML parsing of multiple void elements (Nicolas Venegas)
790 | * Fix void element children invariant violation (Nicolas Venegas)
791 |
792 | ## 4.0.6 - 2019-01-04
793 |
794 | ### Fixes
795 |
796 | * Mitigate regex ddos by upgrading html-to-react (Christoph Werner)
797 | * Update typings to allow arbitrary node types (Jesse Pinho)
798 | * Readme: Add note about only parsing plugins working (Vincent Tunru)
799 |
800 | ## 4.0.4 - 2018-11-30
801 |
802 | ### Changed
803 |
804 | * Upgrade dependencies (Espen Hovlandsdal)
805 |
806 | ## 4.0.3 - 2018-10-11
807 |
808 | ### Fixes
809 |
810 | * Output paragraph element for last item in loose list (Jeremy Moseley)
811 |
812 | ## 4.0.2 - 2018-10-05
813 |
814 | ### Fixes
815 |
816 | * Fix text rendering in React versions lower than or equal to 15 (Espen
817 | Hovlandsdal)
818 |
819 | ## 4.0.1 - 2018-10-03
820 |
821 | ### Fixes
822 |
823 | * \[TypeScript] Fix TypeScript index signature for renderers (Linus Unnebäck)
824 |
825 | ## 4.0.0 - 2018-10-03
826 |
827 | ### BREAKING
828 |
829 | * `text` is now a first-class node + renderer
830 | — if you are using `allowedNodes`, it needs to be included in this list.
831 | Since it is now a React component, it will be passed an object of props
832 | instead of the old approach where a string was passed.
833 | `children` will contain the actual text string.
834 | * On React >= 16.2, if no `className` prop is provided, a fragment will be
835 | used instead of a div.
836 | To always render a div, pass `'div'` as the `root` renderer.
837 | * On React >= 16.2, escaped HTML will no longer be rendered with div/span
838 | containers
839 | * The UMD bundle now exports the component as `window.ReactMarkdown` instead
840 | of `window.reactMarkdown`
841 |
842 | ### Added
843 |
844 | * HTML parser plugin for full HTML compatibility (Espen Hovlandsdal)
845 |
846 | ### Fixes
847 |
848 | * URI transformer allows uppercase http/https URLs (Liam Kennedy)
849 | * \[TypeScript] Strongly type the keys of `renderers` (Linus Unnebäck)
850 |
851 | ## 3.6.0 - 2018-09-05
852 |
853 | ### Added
854 |
855 | * Add support for passing index info to renderers (Beau Roberts)
856 |
857 | ## 3.5.0 - 2018-09-03
858 |
859 | ### Added
860 |
861 | * Allow specifying `target` attribute for links (Marshall Smith)
862 |
863 | ## 3.4.1 - 2018-07-25
864 |
865 | ### Fixes
866 |
867 | * Bump dependency for mdast-add-list-metadata as it was using ES6 features
868 | (Espen Hovlandsdal)
869 |
870 | ## 3.4.0 - 2018-07-25
871 |
872 | ### Added
873 |
874 | * Add more metadata props to list and listItem (André Staltz)
875 | * list: `depth`
876 | * listItem: `ordered`, `index`
877 |
878 | ### Fixes
879 |
880 | * Make `source` property optional in typescript definition (gRoberts84)
881 |
882 | ## 3.3.4 - 2018-06-19
883 |
884 | ### Fixes
885 |
886 | * Fix bug where rendering empty link references (`[][]`) would fail (Dennis S)
887 |
888 | ## 3.3.3 - 2018-06-14
889 |
890 | ### Fixes
891 |
892 | * Fix bug where unwrapping certain disallowed nodes would fail (Petr Gazarov)
893 |
894 | ## 3.3.2 - 2018-05-07
895 |
896 | ### Changes
897 |
898 | * Add `rawSourcePos` property for passing structured source position info to
899 | renderers (Espen Hovlandsdal)
900 |
901 | ## 3.3.1 - 2018-05-07
902 |
903 | ### Changes
904 |
905 | * Pass properties of unknown nodes directly to renderer (Jesse Pinho)
906 | * Update TypeScript definition and prop types (ClassicDarkChocolate)
907 |
908 | ## 3.3.0 - 2018-03-06
909 |
910 | ### Added
911 |
912 | * Add support for fragment renderers (Benjamim Sonntag)
913 |
914 | ## 3.2.2 - 2018-02-26
915 |
916 | ### Fixes
917 |
918 | * Fix language escaping in code blocks (Espen Hovlandsdal)
919 |
920 | ## 3.2.1 - 2018-02-21
921 |
922 | ### Fixes
923 |
924 | * Pass the React key into an overridden text renderer (vanchagreen)
925 |
926 | ## 3.2.0 - 2018-02-12
927 |
928 | ### Added
929 |
930 | * Allow overriding text renderer (Thibaud Courtoison)
931 |
932 | ## 3.1.5 - 2018-02-03
933 |
934 | ### Fixes
935 |
936 | * Only use first language from code block (Espen Hovlandsdal)
937 |
938 | ## 3.1.4 - 2017-12-30
939 |
940 | ### Fixes
941 |
942 | * Enable transformImageUri for image references (evoye)
943 |
944 | ## 3.1.3 - 2017-12-16
945 |
946 | ### Fixes
947 |
948 | * Exclude babel config from npm package (Espen Hovlandsdal)
949 |
950 | ## 3.1.2 - 2017-12-16
951 |
952 | ### Fixes
953 |
954 | * Fixed partial table exception (Alexander Wong)
955 |
956 | ## 3.1.1 - 2017-12-11
957 |
958 | ### Fixes
959 |
960 | * Add readOnly property to checkboxes (Phil Rajchgot)
961 |
962 | ## 3.1.0 - 2017-11-30
963 |
964 | ### Added
965 |
966 | * Support for checkbox lists (Espen Hovlandsdal)
967 |
968 | ### Fixes
969 |
970 | * Better typings (Igor Kamyshev)
971 |
972 | ## 3.0.1 - 2017-11-21
973 |
974 | ### Added
975 |
976 | * *Experimental* support for plugins (Espen Hovlandsdal)
977 |
978 | ### Changes
979 |
980 | * Provide more arguments to `transformLinkUri`/`transformImageUri` (children,
981 | title, alt) (mudrz)
982 |
983 | ## 3.0.0 - 2017-11-20
984 |
985 | ### Notes
986 |
987 | * **FULL REWRITE**.
988 | Changed parser from CommonMark to Markdown.
989 | Big, breaking changes.
990 | See *BREAKING* below.
991 |
992 | ### Added
993 |
994 | * Table support!
995 | * New types: `table`, `tableHead`, `tableBody`, `tableRow`, `tableCell`
996 | * New type: `delete` (`~~foo~~`)
997 | * New type: `imageReference`
998 | * New type: `linkReference`
999 | * New type: `definition`
1000 | * Hacky, but basic support for React-native rendering of attributeless HTML
1001 | nodes (``, ``, etc)
1002 |
1003 | ### BREAKING
1004 |
1005 | * Container props removed (`containerTagName`, `containerProps`), override
1006 | `root` renderer instead
1007 | * `softBreak` option removed.
1008 | New solution will be added at some point in the future.
1009 | * `escapeHtml` is now TRUE by default
1010 | * `HtmlInline`/`HtmlBlock` are now named `html` (use `isBlock` prop to check\
1011 | if inline or block)
1012 | * Renderer names are camelcased and in certain cases, renamed.
1013 | For instance:
1014 | * `Emph` => `emphasis`
1015 | * `Item` => `listItem`
1016 | * `Code` => `inlineCode`
1017 | * `CodeBlock` => `code`
1018 | * `linebreak`/`hardbreak` => `break`
1019 | * All renderers: `literal` prop is now called `value`\* List renderer: `type`
1020 | prop is now a boolean named `ordered` (`Bullet` => `false`, `Ordered` =>
1021 | `true`)
1022 | * `walker` prop removed.
1023 | Code depending on this will have to be rewritten to use the `astPlugins`
1024 | prop, which functions differently.
1025 | * `allowNode` has new arguments (node, index, parent)
1026 | — node has different props, see renderer props
1027 | * `childBefore` and `childAfter` props removed.
1028 | Use `root` renderer instead.
1029 | * `parserOptions` removed (new parser, so the old options doesn’t make sense
1030 | anymore)
1031 |
1032 | ## 2.5.1 - 2017-11-11
1033 |
1034 | ### Changes
1035 |
1036 | * Fix ` ` not having a node key (Alex Zaworski)
1037 |
1038 | ## 2.5.0 - 2017-04-10
1039 |
1040 | ### Changes
1041 |
1042 | * Fix deprecations for React v15.5 (Renée Kooi)
1043 |
1044 | ## 2.4.6 - 2017-03-14
1045 |
1046 | ### Changes
1047 |
1048 | * Fix too strict TypeScript definition (Rasmus Eneman)
1049 | * Update JSON-loader info in readme to match webpack 2 (Robin Wieruch)
1050 |
1051 | ### Added
1052 |
1053 | * Add ability to pass options to the CommonMark parser (Evan Hensleigh)
1054 |
1055 | ## 2.4.4 - 2017-01-16
1056 |
1057 | ### Changes
1058 |
1059 | * Fixed TypeScript definitions (Kohei Asai)
1060 |
1061 | ## 2.4.3 - 2017-01-12
1062 |
1063 | ### Added
1064 |
1065 | * Added TypeScript definitions (Ibragimov Ruslan)
1066 |
1067 | ## 2.4.2 - 2016-07-09
1068 |
1069 | ### Added
1070 |
1071 | * Added UMD-build (`umd/react-markdown.js`) (Espen Hovlandsdal)
1072 |
1073 | ## 2.4.1 - 2016-07-09
1074 |
1075 | ### Changes
1076 |
1077 | * Update `commonmark-react-renderer`, fixing a bug with missing nodes
1078 | (Espen Hovlandsdal)
1079 |
1080 | ## 2.4.0 - 2016-07-09
1081 |
1082 | ### Changes
1083 |
1084 | * Plain DOM-node renderers are now given only their respective props.
1085 | Fixes warnings when using React >= 15.2 (Espen Hovlandsdal)
1086 |
1087 | ### Added
1088 |
1089 | * New `transformImageUri` option allows you to transform URIs for images
1090 | (Petri Lehtinen)
1091 |
1092 | ## 2.3.0 - 2016-06-06
1093 |
1094 | ## Added
1095 |
1096 | * The `walker` instance is now passed to the `walker` callback function
1097 | (Riku Rouvila)
1098 |
1099 | ## 2.2.0 - 2016-04-20
1100 |
1101 | * Add `childBefore`/`childAfter` options (Thomas Lindstrøm)
1102 |
1103 | ## 2.1.1 - 2016-03-25
1104 |
1105 | * Add `containerProps` option (Thomas Lindstrøm)
1106 |
1107 | ## 2.1.0 - 2016-03-12
1108 |
1109 | ### Changes
1110 |
1111 | * Join sibling text nodes into one text node (Espen Hovlandsdal)
1112 |
1113 | ## 2.0.1 - 2016-02-21
1114 |
1115 | ### Changed
1116 |
1117 | * Update `commonmark-react-renderer` dependency to latest version to add keys
1118 | to all elements and simplify custom renderers
1119 |
1120 | ## 2.0.0 - 2016-02-21
1121 |
1122 | ### Changed
1123 |
1124 | * **Breaking change**: The renderer now requires Node 0.14 or higher.
1125 | This is because the renderer uses stateless components internally.
1126 | * **Breaking change**: `allowNode` now receives different properties in the
1127 | options argument.
1128 | See `README.md` for more details.
1129 | * **Breaking change**: CommonMark has changed some type names.
1130 | `Html` is now `HtmlInline`, `Header` is now `Heading` and `HorizontalRule`
1131 | is now `ThematicBreak`.
1132 | This affects the `allowedTypes` and `disallowedTypes` options.
1133 | * **Breaking change**: A bug in the `allowedTypes`/`disallowedTypes` and
1134 | `allowNode` options made them only applicable to certain types.
1135 | In this version, all types are filtered, as expected.
1136 | * **Breaking change**: Link URIs are now filtered through an XSS-filter by
1137 | default, prefixing “dangerous” protocols such as `javascript:` with `x-`
1138 | (eg: `javascript:alert('foo')` turns into `x-javascript:alert('foo')`).
1139 | This can be overridden with the `transformLinkUri`-option.
1140 | Pass `null` to disable the feature or a custom function to replace the
1141 | built-in behaviour.
1142 |
1143 | ### Added
1144 |
1145 | * New `renderers` option allows you to customize which React component should
1146 | be used for rendering given types.
1147 | See `README.md` for more details.
1148 | (Espen Hovlandsdal / Guillaume Plique)
1149 | * New `unwrapDisallowed` option allows you to select if the contents of a
1150 | disallowed node should be “unwrapped” (placed into the disallowed node
1151 | position).
1152 | For instance, setting this option to true and disallowing a link would still
1153 | render the text of the link, instead of the whole link node and all it’s
1154 | children disappearing.
1155 | (Espen Hovlandsdal)
1156 | * New `transformLinkUri` option allows you to transform URIs in links.
1157 | By default, an XSS-filter is used, but you could also use this for use cases
1158 | like transforming absolute to relative URLs, or similar.
1159 | (Espen Hovlandsdal)
1160 |
1161 | ## 1.2.4 - 2016-01-28
1162 |
1163 | ### Changed
1164 |
1165 | * Rolled back dependencies because of breaking changes
1166 |
1167 | ## 1.2.3 - 2016-01-24
1168 |
1169 | ### Changed
1170 |
1171 | * Updated dependencies for both `commonmark` and `commonmark-react-parser` to
1172 | work around an embarrassing oversight on my part.
1173 |
1174 | ## 1.2.2 - 2016-01-08
1175 |
1176 | ### Changed
1177 |
1178 | * Reverted change from 1.2.1 that uses the dist version.
1179 | Instead, documentation is added that specified the need for `json-loader` to
1180 | be enabled when using webpack.
1181 |
1182 | ## 1.2.1 - 2015-12-29
1183 |
1184 | ### Fixed
1185 |
1186 | * Use pre-built (dist version) of commonmark renderer in order to work around
1187 | JSON-loader dependency.
1188 |
1189 | ## 1.2.0 - 2015-12-16
1190 |
1191 | ### Added
1192 |
1193 | * Added new `allowNode`-property.
1194 | See README for details.
1195 |
1196 | ## 1.1.4 - 2015-12-14
1197 |
1198 | ### Fixed
1199 |
1200 | * Set correct `libraryTarget` to make UMD builds work as expected
1201 |
1202 | ## 1.1.3 - 2015-12-14
1203 |
1204 | ### Fixed
1205 |
1206 | * Update babel dependencies and run prepublish only as actual prepublish, not
1207 | install
1208 |
1209 | ## 1.1.1 - 2015-11-28
1210 |
1211 | ### Fixed
1212 |
1213 | * Fixed issue with React external name in global environment (`react` vs `React`)
1214 |
1215 | ## 1.1.0 - 2015-11-22
1216 |
1217 | ### Changed
1218 |
1219 | * Add ability to allow/disallow specific node types (`allowedTypes`/`disallowedTypes`)
1220 |
1221 | ## 1.0.5 - 2015-10-22
1222 |
1223 | ### Changed
1224 |
1225 | * Moved React from dependency to peer dependency.
1226 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @typedef {import('./lib/index.js').AllowElement} AllowElement
3 | * @typedef {import('./lib/index.js').Components} Components
4 | * @typedef {import('./lib/index.js').ExtraProps} ExtraProps
5 | * @typedef {import('./lib/index.js').HooksOptions} HooksOptions
6 | * @typedef {import('./lib/index.js').Options} Options
7 | * @typedef {import('./lib/index.js').UrlTransform} UrlTransform
8 | */
9 |
10 | export {
11 | MarkdownAsync,
12 | MarkdownHooks,
13 | Markdown as default,
14 | defaultUrlTransform
15 | } from './lib/index.js'
16 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @import {Element, Nodes, Parents, Root} from 'hast'
3 | * @import {Root as MdastRoot} from 'mdast'
4 | * @import {ComponentType, JSX, ReactElement, ReactNode} from 'react'
5 | * @import {Options as RemarkRehypeOptions} from 'remark-rehype'
6 | * @import {BuildVisitor} from 'unist-util-visit'
7 | * @import {PluggableList, Processor} from 'unified'
8 | */
9 |
10 | /**
11 | * @callback AllowElement
12 | * Filter elements.
13 | * @param {Readonly} element
14 | * Element to check.
15 | * @param {number} index
16 | * Index of `element` in `parent`.
17 | * @param {Readonly | undefined} parent
18 | * Parent of `element`.
19 | * @returns {boolean | null | undefined}
20 | * Whether to allow `element` (default: `false`).
21 | */
22 |
23 | /**
24 | * @typedef ExtraProps
25 | * Extra fields we pass.
26 | * @property {Element | undefined} [node]
27 | * passed when `passNode` is on.
28 | */
29 |
30 | /**
31 | * @typedef {{
32 | * [Key in keyof JSX.IntrinsicElements]?: ComponentType | keyof JSX.IntrinsicElements
33 | * }} Components
34 | * Map tag names to components.
35 | */
36 |
37 | /**
38 | * @typedef Deprecation
39 | * Deprecation.
40 | * @property {string} from
41 | * Old field.
42 | * @property {string} id
43 | * ID in readme.
44 | * @property {keyof Options} [to]
45 | * New field.
46 | */
47 |
48 | /**
49 | * @typedef Options
50 | * Configuration.
51 | * @property {AllowElement | null | undefined} [allowElement]
52 | * Filter elements (optional);
53 | * `allowedElements` / `disallowedElements` is used first.
54 | * @property {ReadonlyArray | null | undefined} [allowedElements]
55 | * Tag names to allow (default: all tag names);
56 | * cannot combine w/ `disallowedElements`.
57 | * @property {string | null | undefined} [children]
58 | * Markdown.
59 | * @property {Components | null | undefined} [components]
60 | * Map tag names to components.
61 | * @property {ReadonlyArray | null | undefined} [disallowedElements]
62 | * Tag names to disallow (default: `[]`);
63 | * cannot combine w/ `allowedElements`.
64 | * @property {PluggableList | null | undefined} [rehypePlugins]
65 | * List of rehype plugins to use.
66 | * @property {PluggableList | null | undefined} [remarkPlugins]
67 | * List of remark plugins to use.
68 | * @property {Readonly | null | undefined} [remarkRehypeOptions]
69 | * Options to pass through to `remark-rehype`.
70 | * @property {boolean | null | undefined} [skipHtml=false]
71 | * Ignore HTML in markdown completely (default: `false`).
72 | * @property {boolean | null | undefined} [unwrapDisallowed=false]
73 | * Extract (unwrap) what’s in disallowed elements (default: `false`);
74 | * normally when say `strong` is not allowed, it and it’s children are dropped,
75 | * with `unwrapDisallowed` the element itself is replaced by its children.
76 | * @property {UrlTransform | null | undefined} [urlTransform]
77 | * Change URLs (default: `defaultUrlTransform`)
78 | */
79 |
80 | /**
81 | * @typedef HooksOptionsOnly
82 | * Configuration specifically for {@linkcode MarkdownHooks}.
83 | * @property {ReactNode | null | undefined} [fallback]
84 | * Content to render while the processor processing the markdown (optional).
85 | */
86 |
87 | /**
88 | * @typedef {Options & HooksOptionsOnly} HooksOptions
89 | * Configuration for {@linkcode MarkdownHooks};
90 | * extends the regular {@linkcode Options} with a `fallback` prop.
91 | */
92 |
93 | /**
94 | * @callback UrlTransform
95 | * Transform all URLs.
96 | * @param {string} url
97 | * URL.
98 | * @param {string} key
99 | * Property name (example: `'href'`).
100 | * @param {Readonly} node
101 | * Node.
102 | * @returns {string | null | undefined}
103 | * Transformed URL (optional).
104 | */
105 |
106 | import {unreachable} from 'devlop'
107 | import {toJsxRuntime} from 'hast-util-to-jsx-runtime'
108 | import {urlAttributes} from 'html-url-attributes'
109 | import {Fragment, jsx, jsxs} from 'react/jsx-runtime'
110 | import {useEffect, useMemo, useState} from 'react'
111 | import remarkParse from 'remark-parse'
112 | import remarkRehype from 'remark-rehype'
113 | import {unified} from 'unified'
114 | import {visit} from 'unist-util-visit'
115 | import {VFile} from 'vfile'
116 |
117 | const changelog =
118 | 'https://github.com/remarkjs/react-markdown/blob/main/changelog.md'
119 |
120 | /** @type {PluggableList} */
121 | const emptyPlugins = []
122 | /** @type {Readonly} */
123 | const emptyRemarkRehypeOptions = {allowDangerousHtml: true}
124 | const safeProtocol = /^(https?|ircs?|mailto|xmpp)$/i
125 |
126 | // Mutable because we `delete` any time it’s used and a message is sent.
127 | /** @type {ReadonlyArray>} */
128 | const deprecations = [
129 | {from: 'astPlugins', id: 'remove-buggy-html-in-markdown-parser'},
130 | {from: 'allowDangerousHtml', id: 'remove-buggy-html-in-markdown-parser'},
131 | {
132 | from: 'allowNode',
133 | id: 'replace-allownode-allowedtypes-and-disallowedtypes',
134 | to: 'allowElement'
135 | },
136 | {
137 | from: 'allowedTypes',
138 | id: 'replace-allownode-allowedtypes-and-disallowedtypes',
139 | to: 'allowedElements'
140 | },
141 | {from: 'className', id: 'remove-classname'},
142 | {
143 | from: 'disallowedTypes',
144 | id: 'replace-allownode-allowedtypes-and-disallowedtypes',
145 | to: 'disallowedElements'
146 | },
147 | {from: 'escapeHtml', id: 'remove-buggy-html-in-markdown-parser'},
148 | {from: 'includeElementIndex', id: '#remove-includeelementindex'},
149 | {
150 | from: 'includeNodeIndex',
151 | id: 'change-includenodeindex-to-includeelementindex'
152 | },
153 | {from: 'linkTarget', id: 'remove-linktarget'},
154 | {from: 'plugins', id: 'change-plugins-to-remarkplugins', to: 'remarkPlugins'},
155 | {from: 'rawSourcePos', id: '#remove-rawsourcepos'},
156 | {from: 'renderers', id: 'change-renderers-to-components', to: 'components'},
157 | {from: 'source', id: 'change-source-to-children', to: 'children'},
158 | {from: 'sourcePos', id: '#remove-sourcepos'},
159 | {from: 'transformImageUri', id: '#add-urltransform', to: 'urlTransform'},
160 | {from: 'transformLinkUri', id: '#add-urltransform', to: 'urlTransform'}
161 | ]
162 |
163 | /**
164 | * Component to render markdown.
165 | *
166 | * This is a synchronous component.
167 | * When using async plugins,
168 | * see {@linkcode MarkdownAsync} or {@linkcode MarkdownHooks}.
169 | *
170 | * @param {Readonly} options
171 | * Props.
172 | * @returns {ReactElement}
173 | * React element.
174 | */
175 | export function Markdown(options) {
176 | const processor = createProcessor(options)
177 | const file = createFile(options)
178 | return post(processor.runSync(processor.parse(file), file), options)
179 | }
180 |
181 | /**
182 | * Component to render markdown with support for async plugins
183 | * through async/await.
184 | *
185 | * Components returning promises are supported on the server.
186 | * For async support on the client,
187 | * see {@linkcode MarkdownHooks}.
188 | *
189 | * @param {Readonly} options
190 | * Props.
191 | * @returns {Promise}
192 | * Promise to a React element.
193 | */
194 | export async function MarkdownAsync(options) {
195 | const processor = createProcessor(options)
196 | const file = createFile(options)
197 | const tree = await processor.run(processor.parse(file), file)
198 | return post(tree, options)
199 | }
200 |
201 | /**
202 | * Component to render markdown with support for async plugins through hooks.
203 | *
204 | * This uses `useEffect` and `useState` hooks.
205 | * Hooks run on the client and do not immediately render something.
206 | * For async support on the server,
207 | * see {@linkcode MarkdownAsync}.
208 | *
209 | * @param {Readonly} options
210 | * Props.
211 | * @returns {ReactNode}
212 | * React node.
213 | */
214 | export function MarkdownHooks(options) {
215 | const processor = useMemo(
216 | function () {
217 | return createProcessor(options)
218 | },
219 | [options.rehypePlugins, options.remarkPlugins, options.remarkRehypeOptions]
220 | )
221 | const [error, setError] = useState(
222 | /** @type {Error | undefined} */ (undefined)
223 | )
224 | const [tree, setTree] = useState(/** @type {Root | undefined} */ (undefined))
225 |
226 | useEffect(
227 | function () {
228 | let cancelled = false
229 | const file = createFile(options)
230 |
231 | processor.run(processor.parse(file), file, function (error, tree) {
232 | if (!cancelled) {
233 | setError(error)
234 | setTree(tree)
235 | }
236 | })
237 |
238 | /**
239 | * @returns {undefined}
240 | * Nothing.
241 | */
242 | return function () {
243 | cancelled = true
244 | }
245 | },
246 | [options.children, processor]
247 | )
248 |
249 | if (error) throw error
250 |
251 | return tree ? post(tree, options) : options.fallback
252 | }
253 |
254 | /**
255 | * Set up the `unified` processor.
256 | *
257 | * @param {Readonly} options
258 | * Props.
259 | * @returns {Processor}
260 | * Result.
261 | */
262 | function createProcessor(options) {
263 | const rehypePlugins = options.rehypePlugins || emptyPlugins
264 | const remarkPlugins = options.remarkPlugins || emptyPlugins
265 | const remarkRehypeOptions = options.remarkRehypeOptions
266 | ? {...options.remarkRehypeOptions, ...emptyRemarkRehypeOptions}
267 | : emptyRemarkRehypeOptions
268 |
269 | const processor = unified()
270 | .use(remarkParse)
271 | .use(remarkPlugins)
272 | .use(remarkRehype, remarkRehypeOptions)
273 | .use(rehypePlugins)
274 |
275 | return processor
276 | }
277 |
278 | /**
279 | * Set up the virtual file.
280 | *
281 | * @param {Readonly} options
282 | * Props.
283 | * @returns {VFile}
284 | * Result.
285 | */
286 | function createFile(options) {
287 | const children = options.children || ''
288 | const file = new VFile()
289 |
290 | if (typeof children === 'string') {
291 | file.value = children
292 | } else {
293 | unreachable(
294 | 'Unexpected value `' +
295 | children +
296 | '` for `children` prop, expected `string`'
297 | )
298 | }
299 |
300 | return file
301 | }
302 |
303 | /**
304 | * Process the result from unified some more.
305 | *
306 | * @param {Nodes} tree
307 | * Tree.
308 | * @param {Readonly} options
309 | * Props.
310 | * @returns {ReactElement}
311 | * React element.
312 | */
313 | function post(tree, options) {
314 | const allowedElements = options.allowedElements
315 | const allowElement = options.allowElement
316 | const components = options.components
317 | const disallowedElements = options.disallowedElements
318 | const skipHtml = options.skipHtml
319 | const unwrapDisallowed = options.unwrapDisallowed
320 | const urlTransform = options.urlTransform || defaultUrlTransform
321 |
322 | for (const deprecation of deprecations) {
323 | if (Object.hasOwn(options, deprecation.from)) {
324 | unreachable(
325 | 'Unexpected `' +
326 | deprecation.from +
327 | '` prop, ' +
328 | (deprecation.to
329 | ? 'use `' + deprecation.to + '` instead'
330 | : 'remove it') +
331 | ' (see <' +
332 | changelog +
333 | '#' +
334 | deprecation.id +
335 | '> for more info)'
336 | )
337 | }
338 | }
339 |
340 | if (allowedElements && disallowedElements) {
341 | unreachable(
342 | 'Unexpected combined `allowedElements` and `disallowedElements`, expected one or the other'
343 | )
344 | }
345 |
346 | visit(tree, transform)
347 |
348 | return toJsxRuntime(tree, {
349 | Fragment,
350 | components,
351 | ignoreInvalidStyle: true,
352 | jsx,
353 | jsxs,
354 | passKeys: true,
355 | passNode: true
356 | })
357 |
358 | /** @type {BuildVisitor} */
359 | function transform(node, index, parent) {
360 | if (node.type === 'raw' && parent && typeof index === 'number') {
361 | if (skipHtml) {
362 | parent.children.splice(index, 1)
363 | } else {
364 | parent.children[index] = {type: 'text', value: node.value}
365 | }
366 |
367 | return index
368 | }
369 |
370 | if (node.type === 'element') {
371 | /** @type {string} */
372 | let key
373 |
374 | for (key in urlAttributes) {
375 | if (
376 | Object.hasOwn(urlAttributes, key) &&
377 | Object.hasOwn(node.properties, key)
378 | ) {
379 | const value = node.properties[key]
380 | const test = urlAttributes[key]
381 | if (test === null || test.includes(node.tagName)) {
382 | node.properties[key] = urlTransform(String(value || ''), key, node)
383 | }
384 | }
385 | }
386 | }
387 |
388 | if (node.type === 'element') {
389 | let remove = allowedElements
390 | ? !allowedElements.includes(node.tagName)
391 | : disallowedElements
392 | ? disallowedElements.includes(node.tagName)
393 | : false
394 |
395 | if (!remove && allowElement && typeof index === 'number') {
396 | remove = !allowElement(node, index, parent)
397 | }
398 |
399 | if (remove && parent && typeof index === 'number') {
400 | if (unwrapDisallowed && node.children) {
401 | parent.children.splice(index, 1, ...node.children)
402 | } else {
403 | parent.children.splice(index, 1)
404 | }
405 |
406 | return index
407 | }
408 | }
409 | }
410 | }
411 |
412 | /**
413 | * Make a URL safe.
414 | *
415 | * This follows how GitHub works.
416 | * It allows the protocols `http`, `https`, `irc`, `ircs`, `mailto`, and `xmpp`,
417 | * and URLs relative to the current protocol (such as `/something`).
418 | *
419 | * @satisfies {UrlTransform}
420 | * @param {string} value
421 | * URL.
422 | * @returns {string}
423 | * Safe URL.
424 | */
425 | export function defaultUrlTransform(value) {
426 | // Same as:
427 | //
428 | // But without the `encode` part.
429 | const colon = value.indexOf(':')
430 | const questionMark = value.indexOf('?')
431 | const numberSign = value.indexOf('#')
432 | const slash = value.indexOf('/')
433 |
434 | if (
435 | // If there is no protocol, it’s relative.
436 | colon === -1 ||
437 | // If the first colon is after a `?`, `#`, or `/`, it’s not a protocol.
438 | (slash !== -1 && colon > slash) ||
439 | (questionMark !== -1 && colon > questionMark) ||
440 | (numberSign !== -1 && colon > numberSign) ||
441 | // It is a protocol, it should be allowed.
442 | safeProtocol.test(value.slice(0, colon))
443 | ) {
444 | return value
445 | }
446 |
447 | return ''
448 | }
449 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Espen Hovlandsdal
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Espen Hovlandsdal ",
3 | "bugs": "https://github.com/remarkjs/react-markdown/issues",
4 | "contributors": [
5 | "Alexander Wallin ",
6 | "Alexander Wong ",
7 | "André Staltz ",
8 | "Angus MacIsaac ",
9 | "Beau Roberts ",
10 | "Charlie Chen ",
11 | "Christian Murphy ",
12 | "Christoph Werner ",
13 | "Danny ",
14 | "Dennis S ",
15 | "Espen Hovlandsdal ",
16 | "Evan Hensleigh ",
17 | "Fabian Irsara ",
18 | "Florentin Luca Rieger ",
19 | "Frank ",
20 | "Igor Kamyshev ",
21 | "Jack Williams ",
22 | "Jakub Chrzanowski ",
23 | "Jeremy Moseley ",
24 | "Jesse Pinho ",
25 | "Kelvin Chan ",
26 | "Kohei Asai ",
27 | "Linus Unnebäck ",
28 | "Marshall Smith ",
29 | "Nathan Bierema ",
30 | "Nicolas Venegas ",
31 | "Peng Guanwen ",
32 | "Petr Gazarov ",
33 | "Phil Rajchgot ",
34 | "Rasmus Eneman ",
35 | "René Kooi ",
36 | "Riku Rouvila ",
37 | "Robin Wieruch ",
38 | "Rostyslav Melnychuk ",
39 | "Ted Piotrowski ",
40 | "Thibaud Courtoison ",
41 | "Thomas Lindstrøm ",
42 | "Tiago Roldão ",
43 | "Titus Wormer (https://wooorm.com)",
44 | "cerkiewny ",
45 | "evoye ",
46 | "gRoberts84 ",
47 | "mudrz ",
48 | "vanchagreen "
49 | ],
50 | "dependencies": {
51 | "@types/hast": "^3.0.0",
52 | "@types/mdast": "^4.0.0",
53 | "devlop": "^1.0.0",
54 | "hast-util-to-jsx-runtime": "^2.0.0",
55 | "html-url-attributes": "^3.0.0",
56 | "mdast-util-to-hast": "^13.0.0",
57 | "remark-parse": "^11.0.0",
58 | "remark-rehype": "^11.0.0",
59 | "unified": "^11.0.0",
60 | "unist-util-visit": "^5.0.0",
61 | "vfile": "^6.0.0"
62 | },
63 | "description": "React component to render markdown",
64 | "devDependencies": {
65 | "@testing-library/react": "^16.0.0",
66 | "@types/node": "^22.0.0",
67 | "@types/react": "^19.0.0",
68 | "@types/react-dom": "^19.0.0",
69 | "c8": "^10.0.0",
70 | "concat-stream": "^2.0.0",
71 | "esbuild": "^0.25.0",
72 | "eslint-plugin-react": "^7.0.0",
73 | "global-jsdom": "^26.0.0",
74 | "prettier": "^3.0.0",
75 | "react": "^19.0.0",
76 | "react-dom": "^19.0.0",
77 | "rehype-raw": "^7.0.0",
78 | "rehype-starry-night": "^2.0.0",
79 | "remark-cli": "^12.0.0",
80 | "remark-gfm": "^4.0.0",
81 | "remark-preset-wooorm": "^11.0.0",
82 | "remark-toc": "^9.0.0",
83 | "type-coverage": "^2.0.0",
84 | "typescript": "^5.0.0",
85 | "xo": "^0.60.0"
86 | },
87 | "exports": "./index.js",
88 | "files": [
89 | "index.d.ts.map",
90 | "index.d.ts",
91 | "index.js",
92 | "lib/"
93 | ],
94 | "funding": {
95 | "type": "opencollective",
96 | "url": "https://opencollective.com/unified"
97 | },
98 | "keywords": [
99 | "ast",
100 | "commonmark",
101 | "component",
102 | "gfm",
103 | "markdown",
104 | "react",
105 | "react-component",
106 | "remark",
107 | "unified"
108 | ],
109 | "license": "MIT",
110 | "name": "react-markdown",
111 | "peerDependencies": {
112 | "@types/react": ">=18",
113 | "react": ">=18"
114 | },
115 | "prettier": {
116 | "bracketSpacing": false,
117 | "singleQuote": true,
118 | "semi": false,
119 | "tabWidth": 2,
120 | "trailingComma": "none",
121 | "useTabs": false
122 | },
123 | "remarkConfig": {
124 | "plugins": [
125 | "remark-preset-wooorm",
126 | [
127 | "remark-lint-no-html",
128 | false
129 | ]
130 | ]
131 | },
132 | "repository": "remarkjs/react-markdown",
133 | "scripts": {
134 | "build": "tsc --build --clean && tsc --build && type-coverage",
135 | "format": "remark --frail --output --quiet -- . && prettier --log-level warn --write -- . && xo --fix",
136 | "test-api": "node --conditions development --experimental-loader=./script/load-jsx.js --no-warnings test.jsx",
137 | "test-coverage": "c8 --100 --exclude script/ --reporter lcov -- npm run test-api",
138 | "test": "npm run build && npm run format && npm run test-coverage"
139 | },
140 | "sideEffects": false,
141 | "typeCoverage": {
142 | "atLeast": 100,
143 | "strict": true
144 | },
145 | "type": "module",
146 | "version": "10.1.0",
147 | "xo": {
148 | "envs": [
149 | "shared-node-browser"
150 | ],
151 | "extends": "plugin:react/jsx-runtime",
152 | "overrides": [
153 | {
154 | "files": [
155 | "**/*.jsx"
156 | ],
157 | "rules": {
158 | "no-unused-vars": "off"
159 | }
160 | }
161 | ],
162 | "prettier": true,
163 | "rules": {
164 | "complexity": "off",
165 | "n/file-extension-in-import": "off",
166 | "unicorn/prevent-abbreviations": "off"
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
6 |
7 | # react-markdown
8 |
9 | [![Build][badge-build-image]][badge-build-url]
10 | [![Coverage][badge-coverage-image]][badge-coverage-url]
11 | [![Downloads][badge-downloads-image]][badge-downloads-url]
12 | [![Size][badge-size-image]][badge-size-url]
13 |
14 | React component to render markdown.
15 |
16 | ## Feature highlights
17 |
18 | * [x] **[safe][section-security] by default**
19 | (no `dangerouslySetInnerHTML` or XSS attacks)
20 | * [x] **[components][section-components]**
21 | (pass your own component to use instead of `
` for `## hi`)
22 | * [x] **[plugins][section-plugins]**
23 | (many plugins you can pick and choose from)
24 | * [x] **[compliant][section-syntax]**
25 | (100% to CommonMark, 100% to GFM with a plugin)
26 |
27 | ## Contents
28 |
29 | * [What is this?](#what-is-this)
30 | * [When should I use this?](#when-should-i-use-this)
31 | * [Install](#install)
32 | * [Use](#use)
33 | * [API](#api)
34 | * [`Markdown`](#markdown)
35 | * [`MarkdownAsync`](#markdownasync)
36 | * [`MarkdownHooks`](#markdownhooks)
37 | * [`defaultUrlTransform(url)`](#defaulturltransformurl)
38 | * [`AllowElement`](#allowelement)
39 | * [`Components`](#components)
40 | * [`ExtraProps`](#extraprops)
41 | * [`HooksOptions`](#hooksoptions)
42 | * [`Options`](#options)
43 | * [`UrlTransform`](#urltransform)
44 | * [Examples](#examples)
45 | * [Use a plugin](#use-a-plugin)
46 | * [Use a plugin with options](#use-a-plugin-with-options)
47 | * [Use custom components (syntax highlight)](#use-custom-components-syntax-highlight)
48 | * [Use remark and rehype plugins (math)](#use-remark-and-rehype-plugins-math)
49 | * [Plugins](#plugins)
50 | * [Syntax](#syntax)
51 | * [Compatibility](#compatibility)
52 | * [Architecture](#architecture)
53 | * [Appendix A: HTML in markdown](#appendix-a-html-in-markdown)
54 | * [Appendix B: Components](#appendix-b-components)
55 | * [Appendix C: line endings in markdown (and JSX)](#appendix-c-line-endings-in-markdown-and-jsx)
56 | * [Security](#security)
57 | * [Related](#related)
58 | * [Contribute](#contribute)
59 | * [License](#license)
60 |
61 | ## What is this?
62 |
63 | This package is a [React][] component that can be given a string of markdown
64 | that it’ll safely render to React elements.
65 | You can pass plugins to change how markdown is transformed and pass components
66 | that will be used instead of normal HTML elements.
67 |
68 | * to learn markdown, see this [cheatsheet and tutorial][commonmark-help]
69 | * to try out `react-markdown`, see [our demo][github-io-react-markdown]
70 |
71 | ## When should I use this?
72 |
73 | There are other ways to use markdown in React out there so why use this one?
74 | The three main reasons are that they often rely on `dangerouslySetInnerHTML`,
75 | have bugs with how they handle markdown, or don’t let you swap elements for
76 | components.
77 | `react-markdown` builds a virtual DOM, so React only replaces what changed,
78 | from a syntax tree.
79 | That’s supported because we use [unified][github-unified],
80 | specifically [remark][github-remark] for markdown and [rehype][github-rehype]
81 | for HTML,
82 | which are popular tools to transform content with plugins.
83 |
84 | This package focusses on making it easy for beginners to safely use markdown in
85 | React.
86 | When you’re familiar with unified, you can use a modern hooks based alternative
87 | [`react-remark`][github-react-remark] or [`rehype-react`][github-rehype-react]
88 | manually.
89 | If you instead want to use JavaScript and JSX *inside* markdown files, use
90 | [MDX][github-mdx].
91 |
92 | ## Install
93 |
94 | This package is [ESM only][esm].
95 | In Node.js (version 16+), install with [npm][npm-install]:
96 |
97 | ```sh
98 | npm install react-markdown
99 | ```
100 |
101 | In Deno with [`esm.sh`][esmsh]:
102 |
103 | ```js
104 | import Markdown from 'https://esm.sh/react-markdown@10'
105 | ```
106 |
107 | In browsers with [`esm.sh`][esmsh]:
108 |
109 | ```html
110 |
113 | ```
114 |
115 | ## Use
116 |
117 | A basic hello world:
118 |
119 | ```js
120 | import React from 'react'
121 | import {createRoot} from 'react-dom/client'
122 | import Markdown from 'react-markdown'
123 |
124 | const markdown = '# Hi, *Pluto*!'
125 |
126 | createRoot(document.body).render({markdown})
127 | ```
128 |
129 |
130 | Show equivalent JSX
131 |
132 | ```js
133 |
134 | Hi, Pluto!
135 |
136 | ```
137 |
138 |
139 |
140 | Here is an example that shows how to use a plugin
141 | ([`remark-gfm`][github-remark-gfm],
142 | which adds support for footnotes, strikethrough, tables, tasklists and
143 | URLs directly):
144 |
145 | ```js
146 | import React from 'react'
147 | import {createRoot} from 'react-dom/client'
148 | import Markdown from 'react-markdown'
149 | import remarkGfm from 'remark-gfm'
150 |
151 | const markdown = `Just a link: www.nasa.gov.`
152 |
153 | createRoot(document.body).render(
154 | {markdown}
155 | )
156 | ```
157 |
158 |
159 | Show equivalent JSX
160 |
161 | ```js
162 |
165 | ```
166 |
167 |
168 |
169 | ## API
170 |
171 | This package exports the identifiers
172 | [`MarkdownAsync`][api-markdown-async],
173 | [`MarkdownHooks`][api-markdown-hooks],
174 | and
175 | [`defaultUrlTransform`][api-default-url-transform].
176 | The default export is [`Markdown`][api-markdown].
177 |
178 | It also exports the additional [TypeScript][] types
179 | [`AllowElement`][api-allow-element],
180 | [`Components`][api-components],
181 | [`ExtraProps`][api-extra-props],
182 | [`HooksOptions`][api-hooks-options],
183 | [`Options`][api-options],
184 | and
185 | [`UrlTransform`][api-url-transform].
186 |
187 | ### `Markdown`
188 |
189 | Component to render markdown.
190 |
191 | This is a synchronous component.
192 | When using async plugins,
193 | see [`MarkdownAsync`][api-markdown-async] or
194 | [`MarkdownHooks`][api-markdown-hooks].
195 |
196 | ###### Parameters
197 |
198 | * `options` ([`Options`][api-options])
199 | — props
200 |
201 | ###### Returns
202 |
203 | React element (`ReactElement`).
204 |
205 | ### `MarkdownAsync`
206 |
207 | Component to render markdown with support for async plugins
208 | through async/await.
209 |
210 | Components returning promises are supported on the server.
211 | For async support on the client,
212 | see [`MarkdownHooks`][api-markdown-hooks].
213 |
214 | ###### Parameters
215 |
216 | * `options` ([`Options`][api-options])
217 | — props
218 |
219 | ###### Returns
220 |
221 | Promise to a React element (`Promise`).
222 |
223 | ### `MarkdownHooks`
224 |
225 | Component to render markdown with support for async plugins through hooks.
226 |
227 | This uses `useEffect` and `useState` hooks.
228 | Hooks run on the client and do not immediately render something.
229 | For async support on the server,
230 | see [`MarkdownAsync`][api-markdown-async].
231 |
232 | ###### Parameters
233 |
234 | * `options` ([`Options`][api-options])
235 | — props
236 |
237 | ###### Returns
238 |
239 | React node (`ReactNode`).
240 |
241 | ### `defaultUrlTransform(url)`
242 |
243 | Make a URL safe.
244 |
245 | This follows how GitHub works.
246 | It allows the protocols `http`, `https`, `irc`, `ircs`, `mailto`, and `xmpp`,
247 | and URLs relative to the current protocol (such as `/something`).
248 |
249 | ###### Parameters
250 |
251 | * `url` (`string`)
252 | — URL
253 |
254 | ###### Returns
255 |
256 | Safe URL (`string`).
257 |
258 | ### `AllowElement`
259 |
260 | Filter elements (TypeScript type).
261 |
262 | ###### Parameters
263 |
264 | * `node` ([`Element` from `hast`][github-hast-element])
265 | — element to check
266 | * `index` (`number | undefined`)
267 | — index of `element` in `parent`
268 | * `parent` ([`Node` from `hast`][github-hast-nodes])
269 | — parent of `element`
270 |
271 | ###### Returns
272 |
273 | Whether to allow `element` (`boolean`, optional).
274 |
275 | ### `Components`
276 |
277 | Map tag names to components (TypeScript type).
278 |
279 | ###### Type
280 |
281 | ```ts
282 | import type {ExtraProps} from 'react-markdown'
283 | import type {ComponentProps, ElementType} from 'react'
284 |
285 | type Components = {
286 | [Key in Extract]?: ElementType & ExtraProps>
287 | }
288 | ```
289 |
290 | ### `ExtraProps`
291 |
292 | Extra fields we pass to components (TypeScript type).
293 |
294 | ###### Fields
295 |
296 | * `node` ([`Element` from `hast`][github-hast-element], optional)
297 | — original node
298 |
299 | ### `HooksOptions`
300 |
301 | Configuration for [`MarkdownHooks`][api-markdown-hooks] (TypeScript type);
302 | extends the regular [`Options`][api-options] with a `fallback` prop.
303 |
304 | ###### Extends
305 |
306 | [`Options`][api-options].
307 |
308 | ###### Fields
309 |
310 | * `fallback` (`ReactNode`, optional)
311 | — content to render while the processor processing the markdown
312 |
313 | ### `Options`
314 |
315 | Configuration (TypeScript type).
316 |
317 | ###### Fields
318 |
319 | * `allowElement` ([`AllowElement`][api-allow-element], optional)
320 | — filter elements;
321 | `allowedElements` / `disallowedElements` is used first
322 | * `allowedElements` (`Array`, default: all tag names)
323 | — tag names to allow;
324 | cannot combine w/ `disallowedElements`
325 | * `children` (`string`, optional)
326 | — markdown
327 | * `components` ([`Components`][api-components], optional)
328 | — map tag names to components
329 | * `disallowedElements` (`Array`, default: `[]`)
330 | — tag names to disallow;
331 | cannot combine w/ `allowedElements`
332 | * `rehypePlugins` (`Array`, optional)
333 | — list of [rehype plugins][github-rehype-plugins] to use
334 | * `remarkPlugins` (`Array`, optional)
335 | — list of [remark plugins][github-remark-plugins] to use
336 | * `remarkRehypeOptions`
337 | ([`Options` from `remark-rehype`][github-remark-rehype-options],
338 | optional)
339 | — options to pass through to `remark-rehype`
340 | * `skipHtml` (`boolean`, default: `false`)
341 | — ignore HTML in markdown completely
342 | * `unwrapDisallowed` (`boolean`, default: `false`)
343 | — extract (unwrap) what’s in disallowed elements;
344 | normally when say `strong` is not allowed, it and it’s children are dropped,
345 | with `unwrapDisallowed` the element itself is replaced by its children
346 | * `urlTransform` ([`UrlTransform`][api-url-transform], default:
347 | [`defaultUrlTransform`][api-default-url-transform])
348 | — change URLs
349 |
350 | ### `UrlTransform`
351 |
352 | Transform URLs (TypeScript type).
353 |
354 | ###### Parameters
355 |
356 | * `url` (`string`)
357 | — URL
358 | * `key` (`string`, example: `'href'`)
359 | — property name
360 | * `node` ([`Element` from `hast`][github-hast-element])
361 | — element to check
362 |
363 | ###### Returns
364 |
365 | Transformed URL (`string`, optional).
366 |
367 | ## Examples
368 |
369 | ### Use a plugin
370 |
371 | This example shows how to use a remark plugin.
372 | In this case, [`remark-gfm`][github-remark-gfm],
373 | which adds support for strikethrough, tables, tasklists and URLs directly:
374 |
375 | ```js
376 | import React from 'react'
377 | import {createRoot} from 'react-dom/client'
378 | import Markdown from 'react-markdown'
379 | import remarkGfm from 'remark-gfm'
380 |
381 | const markdown = `A paragraph with *emphasis* and **strong importance**.
382 |
383 | > A block quote with ~strikethrough~ and a URL: https://reactjs.org.
384 |
385 | * Lists
386 | * [ ] todo
387 | * [x] done
388 |
389 | A table:
390 |
391 | | a | b |
392 | | - | - |
393 | `
394 |
395 | createRoot(document.body).render(
396 | {markdown}
397 | )
398 | ```
399 |
400 |
401 | Show equivalent JSX
402 |
403 | ```js
404 | <>
405 |
406 | A paragraph with emphasis and strong importance.
407 |
408 |
409 |
410 | A block quote with strikethrough and a URL:{' '}
411 | https://reactjs.org.
412 |
413 |
414 |
415 |
Lists
416 |
417 | todo
418 |
419 |
420 | done
421 |
422 |
423 |
A table:
424 |
425 |
426 |
427 |
a
428 |
b
429 |
430 |
431 |
432 | >
433 | ```
434 |
435 |
436 |
437 | ### Use a plugin with options
438 |
439 | This example shows how to use a plugin and give it options.
440 | To do that, use an array with the plugin at the first place, and the options
441 | second.
442 | [`remark-gfm`][github-remark-gfm] has an option to allow only double tildes for
443 | strikethrough:
444 |
445 | ```js
446 | import React from 'react'
447 | import {createRoot} from 'react-dom/client'
448 | import Markdown from 'react-markdown'
449 | import remarkGfm from 'remark-gfm'
450 |
451 | const markdown = 'This ~is not~ strikethrough, but ~~this is~~!'
452 |
453 | createRoot(document.body).render(
454 |
455 | {markdown}
456 |
457 | )
458 | ```
459 |
460 |
461 | Show equivalent JSX
462 |
463 | ```js
464 |
465 | This ~is not~ strikethrough, but this is!
466 |
467 | ```
468 |
469 |
470 |
471 | ### Use custom components (syntax highlight)
472 |
473 | This example shows how you can overwrite the normal handling of an element by
474 | passing a component.
475 | In this case, we apply syntax highlighting with the seriously super amazing
476 | [`react-syntax-highlighter`][github-react-syntax-highlighter] by
477 | [**@conorhastings**][github-conorhastings]:
478 |
479 |
480 |
481 | ```js
482 | import React from 'react'
483 | import {createRoot} from 'react-dom/client'
484 | import Markdown from 'react-markdown'
485 | import {Prism as SyntaxHighlighter} from 'react-syntax-highlighter'
486 | import {dark} from 'react-syntax-highlighter/dist/esm/styles/prism'
487 |
488 | // Did you know you can use tildes instead of backticks for code in markdown? ✨
489 | const markdown = `Here is some JavaScript code:
490 |
491 | ~~~js
492 | console.log('It works!')
493 | ~~~
494 | `
495 |
496 | createRoot(document.body).render(
497 |
511 | ) : (
512 |
513 | {children}
514 |
515 | )
516 | }
517 | }}
518 | />
519 | )
520 | ```
521 |
522 |
523 | Show equivalent JSX
524 |
525 | ```js
526 | <>
527 |
Here is some JavaScript code:
528 |
529 |
530 |
531 | >
532 | ```
533 |
534 |
535 |
536 | ### Use remark and rehype plugins (math)
537 |
538 | This example shows how a syntax extension
539 | (through [`remark-math`][github-remark-math])
540 | is used to support math in markdown, and a transform plugin
541 | ([`rehype-katex`][github-rehype-katex]) to render that math.
542 |
543 | ```js
544 | import React from 'react'
545 | import {createRoot} from 'react-dom/client'
546 | import Markdown from 'react-markdown'
547 | import rehypeKatex from 'rehype-katex'
548 | import remarkMath from 'remark-math'
549 | import 'katex/dist/katex.min.css' // `rehype-katex` does not import the CSS for you
550 |
551 | const markdown = `The lift coefficient ($C_L$) is a dimensionless coefficient.`
552 |
553 | createRoot(document.body).render(
554 |
555 | {markdown}
556 |
557 | )
558 | ```
559 |
560 |
561 | Show equivalent JSX
562 |
563 | ```js
564 |
576 | ```
577 |
578 |
579 |
580 | ## Plugins
581 |
582 | We use [unified][github-unified],
583 | specifically [remark][github-remark] for markdown and
584 | [rehype][github-rehype] for HTML,
585 | which are tools to transform content with plugins.
586 | Here are three good ways to find plugins:
587 |
588 | * [`awesome-remark`][github-awesome-remark] and
589 | [`awesome-rehype`][github-awesome-rehype]
590 | — selection of the most awesome projects
591 | * [List of remark plugins][github-remark-plugins] and
592 | [list of rehype plugins][github-rehype-plugins]
593 | — list of all plugins
594 | * [`remark-plugin`][github-topic-remark-plugin] and
595 | [`rehype-plugin`][github-topic-rehype-plugin] topics
596 | — any tagged repo on GitHub
597 |
598 | ## Syntax
599 |
600 | `react-markdown` follows CommonMark, which standardizes the differences between
601 | markdown implementations, by default.
602 | Some syntax extensions are supported through plugins.
603 |
604 | We use [`micromark`][github-micromark] under the hood for our parsing.
605 | See its documentation for more information on markdown, CommonMark, and
606 | extensions.
607 |
608 | ## Compatibility
609 |
610 | Projects maintained by the unified collective are compatible with maintained
611 | versions of Node.js.
612 |
613 | When we cut a new major release, we drop support for unmaintained versions of
614 | Node.
615 | This means we try to keep the current release line, `react-markdown@10`,
616 | compatible with Node.js 16.
617 |
618 | They work in all modern browsers (essentially: everything not IE 11).
619 | You can use a bundler (such as esbuild, webpack, or Rollup) to use this package
620 | in your project, and use its options (or plugins) to add support for legacy
621 | browsers.
622 |
623 | ## Architecture
624 |
625 |
636 |
637 | To understand what this project does, it’s important to first understand what
638 | unified does: please read through the [`unifiedjs/unified`][github-unified]
639 | readme
640 | (the part until you hit the API section is required reading).
641 |
642 | `react-markdown` is a unified pipeline — wrapped so that most folks don’t need
643 | to directly interact with unified.
644 | The processor goes through these steps:
645 |
646 | * parse markdown to mdast (markdown syntax tree)
647 | * transform through remark (markdown ecosystem)
648 | * transform mdast to hast (HTML syntax tree)
649 | * transform through rehype (HTML ecosystem)
650 | * render hast to React with components
651 |
652 | ## Appendix A: HTML in markdown
653 |
654 | `react-markdown` typically escapes HTML (or ignores it, with `skipHtml`)
655 | because it is dangerous and defeats the purpose of this library.
656 |
657 | However, if you are in a trusted environment (you trust the markdown), and
658 | can spare the bundle size (±60kb minzipped), then you can use
659 | [`rehype-raw`][github-rehype-raw]:
660 |
661 | ```js
662 | import React from 'react'
663 | import {createRoot} from 'react-dom/client'
664 | import Markdown from 'react-markdown'
665 | import rehypeRaw from 'rehype-raw'
666 |
667 | const markdown = `
668 |
669 | Some *emphasis* and strong!
670 |
671 |