markdown-to-jsx 9.1.2 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,39 +25,28 @@ Some special features of the library:
25
25
  - [Entry Points](#entry-points)
26
26
  - [Main](#main)
27
27
  - [React](#react)
28
+ - [React Native](#react-native)
28
29
  - [HTML](#html)
29
30
  - [Markdown](#markdown)
30
31
  - [Library Options](#library-options)
31
- - [options.forceBlock](#optionsforceblock)
32
- - [options.forceInline](#optionsforceinline)
33
- - [options.wrapper](#optionswrapper)
34
- - [Other useful recipes](#other-useful-recipes)
35
- - [options.wrapperProps](#optionswrapperprops)
32
+ - [All Options](#all-options)
33
+ - [options.createElement](#optionscreateelement)
36
34
  - [options.forceWrapper](#optionsforcewrapper)
37
- - [options.overrides - Void particular banned tags](#optionsoverrides---void-particular-banned-tags)
38
- - [options.overrides - Override Any HTML Tag's Representation](#optionsoverrides---override-any-html-tags-representation)
39
- - [options.overrides - Rendering Arbitrary React Components](#optionsoverrides---rendering-arbitrary-react-components)
40
- - [options.createElement - Custom React.createElement behavior](#optionscreateelement---custom-reactcreateelement-behavior)
41
- - [options.enforceAtxHeadings](#optionsenforceatxheadings)
35
+ - [options.overrides](#optionsoverrides)
42
36
  - [options.renderRule](#optionsrenderrule)
43
37
  - [options.sanitizer](#optionssanitizer)
44
38
  - [options.slugify](#optionsslugify)
45
- - [options.disableAutoLink](#optionsdisableautolink)
46
- - [options.preserveFrontmatter](#optionspreservefrontmatter)
47
- - [options.disableParsingRawHTML](#optionsdisableparsingrawhtml)
48
- - [options.tagfilter](#optionstagfilter)
39
+ - [options.wrapper](#optionswrapper)
40
+ - [Other useful recipes](#other-useful-recipes)
41
+ - [options.wrapperProps](#optionswrapperprops)
49
42
  - [Syntax highlighting](#syntax-highlighting)
50
43
  - [Handling shortcodes](#handling-shortcodes)
51
- - [Getting the smallest possible bundle size](#getting-the-smallest-possible-bundle-size)
52
44
  - [Usage with Preact](#usage-with-preact)
53
45
  - [AST Anatomy](#ast-anatomy)
54
46
  - [Node Types](#node-types)
55
47
  - [Example AST Structure](#example-ast-structure)
56
48
  - [Type Checking](#type-checking)
57
49
  - [Gotchas](#gotchas)
58
- - [Passing props to stringified React components](#passing-props-to-stringified-react-components)
59
- - [Significant indentation inside arbitrary HTML](#significant-indentation-inside-arbitrary-html)
60
- - [Code blocks](#code-blocks)
61
50
  - [Changelog](#changelog)
62
51
  - [Donate](#donate)
63
52
 
@@ -72,37 +61,25 @@ Some special features of the library:
72
61
  - **`ast` option removed**: The `ast: true` option on `compiler()` has been removed. Use the new `parser()` function instead to access the AST directly.
73
62
 
74
63
  ```typescript
75
- // Before (v8)
76
- import { compiler } from 'markdown-to-jsx'
77
- const ast = compiler('# Hello world', { ast: true })
78
-
79
- // After (v9)
80
- import { parser } from 'markdown-to-jsx'
81
- const ast = parser('# Hello world')
64
+ /** v8 */ compiler('# Hello world', { ast: true })
65
+ /** v9 */ parser('# Hello world')
82
66
  ```
83
67
 
84
68
  - **`namedCodesToUnicode` option removed**: The `namedCodesToUnicode` option has been removed. All named HTML entities are now supported by default via the full entity list, so custom entity mappings are no longer needed.
85
69
 
86
70
  ```typescript
87
- // Before (v8)
88
- import { compiler } from 'markdown-to-jsx'
89
- compiler('≤ symbol', { namedCodesToUnicode: { le: '\u2264' } })
90
-
91
- // After (v9)
92
- import { compiler } from 'markdown-to-jsx'
93
- compiler('≤ symbol') // All entities supported automatically
71
+ /** v8 */ compiler('≤ symbol', { namedCodesToUnicode: { le: '\u2264' } })
72
+ /** v9 */ compiler('≤ symbol')
94
73
  ```
95
74
 
96
75
  - **`tagfilter` enabled by default**: Dangerous HTML tags (`script`, `iframe`, `style`, `title`, `textarea`, `xmp`, `noembed`, `noframes`, `plaintext`) are now escaped by default in both HTML string output and React JSX output. Previously these tags were rendered as JSX elements in React output.
97
76
 
98
77
  ```typescript
99
- // Before (v8) - tags rendered as JSX elements
100
- compiler('<script>alert("xss")</script>') // Rendered as <script> element
78
+ /** v8 */ tags rendered as JSX elements
79
+ /** v9 */ tags escaped by default
80
+ compiler('<script>alert("xss")</script>') // <span>&lt;script&gt;</span>
101
81
 
102
- // After (v9) - tags escaped by default
103
- compiler('<script>alert("xss")</script>') // Renders as <span>&lt;script&gt;</span>
104
-
105
- // To restore old behavior:
82
+ /** Restore old behavior */
106
83
  compiler('<script>alert("xss")</script>', { tagfilter: false })
107
84
  ```
108
85
 
@@ -128,33 +105,22 @@ import { compiler, astToMarkdown, parser } from 'markdown-to-jsx/markdown'
128
105
  1. **Replace `compiler(..., { ast: true })` with `parser()`**:
129
106
 
130
107
  ```typescript
131
- // Before
132
- import { compiler } from 'markdown-to-jsx'
133
- const ast = compiler(markdown, { ast: true })
134
-
135
- // After
136
- import { parser } from 'markdown-to-jsx'
137
- const ast = parser(markdown)
108
+ /** v8 */ compiler(markdown, { ast: true })
109
+ /** v9 */ parser(markdown)
138
110
  ```
139
111
 
140
112
  2. **Migrate React imports to `/react` entry point** (optional but recommended):
141
113
 
142
114
  ```typescript
143
- // Before
144
- import Markdown, { compiler } from 'markdown-to-jsx'
145
-
146
- // After (recommended)
147
- import Markdown, { compiler } from 'markdown-to-jsx/react'
115
+ /** Legacy */ import from 'markdown-to-jsx'
116
+ /** Recommended */ import from 'markdown-to-jsx/react'
148
117
  ```
149
118
 
150
119
  3. **Remove `namedCodesToUnicode` option**: All named HTML entities are now supported automatically, so you can remove any custom entity mappings.
151
120
 
152
121
  ```typescript
153
- // Before
154
- compiler('&le; symbol', { namedCodesToUnicode: { le: '\u2264' } })
155
-
156
- // After
157
- compiler('&le; symbol') // Works automatically
122
+ /** v8 */ compiler('&le; symbol', { namedCodesToUnicode: { le: '\u2264' } })
123
+ /** v9 */ compiler('&le; symbol')
158
124
  ```
159
125
 
160
126
  **Note:** The main entry point (`markdown-to-jsx`) continues to work for backward compatibility, but React code there is deprecated and will be removed in a future major release. Consider migrating to `markdown-to-jsx/react` for React-specific usage.
@@ -169,21 +135,15 @@ compiler('&le; symbol') // Works automatically
169
135
  - Type `ParserResult` renamed to `ASTNode` - If you were using `MarkdownToJSX.ParserResult` in your code, update to `MarkdownToJSX.ASTNode`
170
136
 
171
137
  ```typescript
172
- // Before
173
- const nodes: MarkdownToJSX.ParserResult[] = parse(markdown)
174
-
175
- // After
176
- const nodes: MarkdownToJSX.ASTNode[] = parse(markdown)
138
+ /** v7 */ MarkdownToJSX.ParserResult[]
139
+ /** v8+ */ MarkdownToJSX.ASTNode[]
177
140
  ```
178
141
 
179
142
  - Multiple `RuleType` enums consolidated into `RuleType.textFormatted` - If you were checking for `RuleType.textBolded`, `RuleType.textEmphasized`, `RuleType.textMarked`, or `RuleType.textStrikethroughed`, update to check for `RuleType.textFormatted` and inspect the node's boolean flags:
180
143
 
181
144
  ```typescript
182
- // Before
183
- if (node.type === RuleType.textBolded) { ... }
184
-
185
- // After
186
- if (node.type === RuleType.textFormatted && node.bold) { ... }
145
+ /** v7 */ RuleType.textBolded
146
+ /** v8+ */ RuleType.textFormatted && node.bold
187
147
  ```
188
148
 
189
149
  </details>
@@ -239,20 +199,79 @@ For React-specific usage, import from the `/react` entry point:
239
199
  ```tsx
240
200
  import Markdown, { compiler, parser, astToJSX } from 'markdown-to-jsx/react'
241
201
 
242
- // Use compiler for markdown → JSX
243
202
  const jsxElement = compiler('# Hello world')
244
203
 
245
- const markdown = `# Hello world`
246
-
247
204
  function App() {
248
- return <Markdown children={markdown} />
205
+ return <Markdown children="# Hello world" />
249
206
  }
250
207
 
251
- // Or use parser + astToJSX for total control
208
+ /** Or use parser + astToJSX */
252
209
  const ast = parser('# Hello world')
253
210
  const jsxElement2 = astToJSX(ast)
254
211
  ```
255
212
 
213
+ #### React Native
214
+
215
+ For React Native usage, import from the `/native` entry point:
216
+
217
+ ```tsx
218
+ import Markdown, { compiler, parser, astToNative } from 'markdown-to-jsx/native'
219
+ import { View, Text, StyleSheet, Linking } from 'react-native'
220
+
221
+ const nativeElement = compiler('# Hello world', {
222
+ styles: {
223
+ heading1: { fontSize: 32, fontWeight: 'bold' },
224
+ paragraph: { marginVertical: 8 },
225
+ link: { color: 'blue', textDecorationLine: 'underline' },
226
+ },
227
+ onLinkPress: url => {
228
+ Linking.openURL(url)
229
+ },
230
+ })
231
+
232
+ const markdown = `# Hello world
233
+
234
+ This is a [link](https://example.com) with **bold** and *italic* text.
235
+ `
236
+
237
+ function App() {
238
+ return (
239
+ <View>
240
+ <Markdown
241
+ children={markdown}
242
+ options={{
243
+ styles: StyleSheet.create({
244
+ heading1: { fontSize: 32, fontWeight: 'bold' },
245
+ paragraph: { marginVertical: 8 },
246
+ link: { color: 'blue', textDecorationLine: 'underline' },
247
+ }),
248
+ onLinkPress: url => {
249
+ Linking.openURL(url)
250
+ },
251
+ }}
252
+ />
253
+ </View>
254
+ )
255
+ }
256
+ ```
257
+
258
+ **React Native-specific options:**
259
+
260
+ - `onLinkPress?: (url: string, title?: string) => void` - Custom handler for link presses (defaults to `Linking.openURL`)
261
+ - `onLinkLongPress?: (url: string, title?: string) => void` - Handler for link long presses
262
+ - `styles?: Partial<Record<NativeStyleKey, StyleProp<ViewStyle | TextStyle | ImageStyle>>>` - Style overrides for each element type
263
+ - `wrapperProps?: ViewProps | TextProps` - Props for the wrapper component (defaults to `View` for block, `Text` for inline)
264
+
265
+ **HTML Tag Mapping:**
266
+ HTML tags are automatically mapped to React Native components:
267
+
268
+ - `<img>` → `Image` component
269
+ - Block elements (`<div>`, `<section>`, `<article>`, `<blockquote>`, `<ul>`, `<ol>`, `<li>`, `<table>`, etc.) → `View` component
270
+ - Inline elements (`<span>`, `<strong>`, `<em>`, `<a>`, etc.) → `Text` component
271
+ - Type 1 blocks (`<pre>`, `<script>`, `<style>`, `<textarea>`) → `View` component
272
+
273
+ **Note:** Links are underlined by default for better accessibility and discoverability. You can override this via the `styles.link` option.
274
+
256
275
  #### HTML
257
276
 
258
277
  For HTML string output (server-side rendering), import from the `/html` entry point:
@@ -260,11 +279,9 @@ For HTML string output (server-side rendering), import from the `/html` entry po
260
279
  ```tsx
261
280
  import { compiler, html, parser } from 'markdown-to-jsx/html'
262
281
 
263
- // Convenience function that combines parsing and HTML rendering
264
282
  const htmlString = compiler('# Hello world')
265
- // Returns: '<h1>Hello world</h1>'
266
283
 
267
- // Or use parser + html separately for more control
284
+ /** Or use parser + html */
268
285
  const ast = parser('# Hello world')
269
286
  const htmlString2 = html(ast)
270
287
  ```
@@ -276,253 +293,58 @@ For markdown-to-markdown compilation (normalization and formatting), import from
276
293
  ```typescript
277
294
  import { compiler, astToMarkdown, parser } from 'markdown-to-jsx/markdown'
278
295
 
279
- // Convenience function that parses and recompiles markdown
280
296
  const normalizedMarkdown = compiler('# Hello world\n\nExtra spaces!')
281
- // Returns: '# Hello world\n\nExtra spaces!\n'
282
297
 
283
- // Or work with AST directly
298
+ /** Or work with AST */
284
299
  const ast = parser('# Hello world')
285
300
  const normalizedMarkdown2 = astToMarkdown(ast)
286
- // Returns: '# Hello world\n'
287
301
  ```
288
302
 
289
303
  ### Library Options
290
304
 
291
- #### options.forceBlock
292
-
293
- By default, the compiler will try to make an intelligent guess about the content passed and wrap it in a `<div>`, `<p>`, or `<span>` as needed to satisfy the "inline"-ness of the markdown. For instance, this string would be considered "inline":
294
-
295
- ```md
296
- Hello. _Beautiful_ day isn't it?
297
- ```
298
-
299
- But this string would be considered "block" due to the existence of a header tag, which is a block-level HTML element:
300
-
301
- ```md
302
- # Whaddup?
303
- ```
304
-
305
- However, if you really want all input strings to be treated as "block" layout, simply pass `options.forceBlock = true` like this:
306
-
307
- ```tsx
308
- <Markdown options={{ forceBlock: true }}>Hello there old chap!</Markdown>
309
-
310
- // or
311
-
312
- compiler('Hello there old chap!', { forceBlock: true })
313
-
314
- // renders
315
- <p>Hello there old chap!</p>
316
- ```
317
-
318
- #### options.forceInline
319
-
320
- The inverse is also available by passing `options.forceInline = true`:
321
-
322
- ```tsx
323
- <Markdown options={{ forceInline: true }}># You got it babe!</Markdown>
324
-
325
- // or
326
- compiler('# You got it babe!', { forceInline: true })
327
-
328
- // renders
329
- <span># You got it babe!</span>
330
- ```
331
-
332
- #### options.wrapper
333
-
334
- When there are multiple children to be rendered, the compiler will wrap the output in a `div` by default. You can override this default by setting the `wrapper` option to either a string (React Element) or a component.
335
-
336
- ```tsx
337
- const str = '# Heck Yes\n\nThis is great!'
338
-
339
- <Markdown options={{ wrapper: 'article' }}>
340
- {str}
341
- </Markdown>;
342
-
343
- // or
344
-
345
- compiler(str, { wrapper: 'article' });
346
-
347
- // renders
348
-
349
- <article>
350
- <h1>Heck Yes</h1>
351
- <p>This is great!</p>
352
- </article>
353
- ```
354
-
355
- ##### Other useful recipes
356
-
357
- To get an array of children back without a wrapper, set `wrapper` to `null`. This is particularly useful when using `compiler(…)` directly.
358
-
359
- ```tsx
360
- compiler('One\n\nTwo\n\nThree', { wrapper: null })
361
-
362
- // returns
363
- ;[<p>One</p>, <p>Two</p>, <p>Three</p>]
364
- ```
365
-
366
- To render children at the same DOM level as `<Markdown>` with no HTML wrapper, set `wrapper` to `React.Fragment`. This will still wrap your children in a React node for the purposes of rendering, but the wrapper element won't show up in the DOM.
367
-
368
- #### options.wrapperProps
369
-
370
- Props to apply to the wrapper element when `wrapper` is used.
371
-
372
- ```tsx
373
- <Markdown options={{
374
- wrapper: 'article',
375
- wrapperProps: { className: 'post', 'data-testid': 'markdown-content' }
376
- }}>
377
- # Hello World
378
- </Markdown>
379
-
380
- // renders
381
- <article class="post" data-testid="markdown-content">
382
- <h1>Hello World</h1>
383
- </article>
384
- ```
385
-
386
- #### options.forceWrapper
387
-
388
- By default, the compiler does not wrap the rendered contents if there is only a single child. You can change this by setting `forceWrapper` to `true`. If the child is inline, it will not necessarily be wrapped in a `span`.
389
-
390
- ```tsx
391
- // Using `forceWrapper` with a single, inline child…
392
- <Markdown options={{ wrapper: 'aside', forceWrapper: true }}>
393
- Mumble, mumble…
394
- </Markdown>
395
-
396
- // renders
397
-
398
- <aside>Mumble, mumble…</aside>
399
- ```
400
-
401
- #### options.overrides - Void particular banned tags
402
-
403
- Pass the `options.overrides` prop to the compiler or `<Markdown>` component with an implementation that return `null` for tags you wish to exclude from the rendered output. This provides complete removal of tags from the output.
404
-
405
- **Note**: The `tagfilter` option provides default escaping of dangerous tags (`script`, `iframe`, `style`, `title`, `textarea`, `xmp`, `noembed`, `noframes`, `plaintext`). Use `overrides` when you need to:
406
-
407
- - Remove additional tags not covered by `tagfilter` (like `object`)
408
- - Have more control over tag removal vs. escaping
409
- - Disable `tagfilter` but still want to remove specific tags
410
-
411
- For example, to void the `iframe` tag:
412
-
413
- ```tsx
414
- import Markdown from 'markdown-to-jsx'
415
- import React from 'react'
416
- import { render } from 'react-dom'
417
-
418
- render(
419
- <Markdown options={{ overrides: { iframe: () => null } }}>
420
- <iframe src="https://potentially-malicious-web-page.com/"></iframe>
421
- </Markdown>,
422
- document.body
423
- )
424
-
425
- // renders: ""
426
- ```
427
-
428
- The library does not void any tags by default (except through `tagfilter` escaping), allowing you to choose the appropriate security approach for your use case.
429
-
430
- #### options.overrides - Override Any HTML Tag's Representation
305
+ #### All Options
306
+
307
+ | Option | Type | Default | Description |
308
+ | ----------------------- | ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
309
+ | `createElement` | `function` | - | Custom React.createElement behavior (React/React Native only). See [createElement](#optionscreateelement) for details. |
310
+ | `disableAutoLink` | `boolean` | `false` | Disable automatic conversion of bare URLs to anchor tags. |
311
+ | `disableParsingRawHTML` | `boolean` | `false` | Disable parsing of raw HTML into JSX. |
312
+ | `enforceAtxHeadings` | `boolean` | `false` | Require space between `#` and header text (GFM spec compliance). |
313
+ | `forceBlock` | `boolean` | `false` | Force all content to be treated as block-level. |
314
+ | `forceInline` | `boolean` | `false` | Force all content to be treated as inline. |
315
+ | `forceWrapper` | `boolean` | `false` | Force wrapper even with single child (React/React Native only). See [forceWrapper](#optionsforcewrapper) for details. |
316
+ | `overrides` | `object` | - | Override HTML tag rendering. See [overrides](#optionsoverrides) for details. |
317
+ | `preserveFrontmatter` | `boolean` | `false` | Include frontmatter in rendered output (as `<pre>` for HTML/JSX, included in markdown). Behavior varies by compiler type. |
318
+ | `renderRule` | `function` | - | Custom rendering for AST rules. See [renderRule](#optionsrenderrule) for details. |
319
+ | `sanitizer` | `function` | built-in | Custom URL sanitizer function. See [sanitizer](#optionssanitizer) for details. |
320
+ | `slugify` | `function` | built-in | Custom slug generation for heading IDs. See [slugify](#optionsslugify) for details. |
321
+ | `tagfilter` | `boolean` | `true` | Escape dangerous HTML tags (`script`, `iframe`, `style`, etc.) to prevent XSS. |
322
+ | `wrapper` | `string \| component \| null` | `'div'` | Wrapper element for multiple children (React/React Native only). See [wrapper](#optionswrapper) for details. |
323
+ | `wrapperProps` | `object` | - | Props for wrapper element (React/React Native only). See [wrapperProps](#optionswrapperprops) for details. |
324
+
325
+ #### options.createElement
431
326
 
432
- Pass the `options.overrides` prop to the compiler or `<Markdown>` component to seamlessly revise the rendered representation of any HTML tag. You can choose to change the component itself, add/change props, or both.
433
-
434
- ```tsx
435
- import Markdown from 'markdown-to-jsx'
436
- import React from 'react'
437
- import { render } from 'react-dom'
438
-
439
- // surprise, it's a div instead!
440
- const MyParagraph = ({ children, ...props }) => <div {...props}>{children}</div>
441
-
442
- render(
443
- <Markdown
444
- options={{
445
- overrides: {
446
- h1: {
447
- component: MyParagraph,
448
- props: {
449
- className: 'foo',
450
- },
451
- },
452
- },
453
- }}
454
- >
455
- # Hello world!
456
- </Markdown>,
457
- document.body
458
- )
459
-
460
- /*
461
- renders:
462
-
463
- <div class="foo">
464
- Hello World
465
- </div>
466
- */
467
- ```
468
-
469
- If you only wish to provide a component override, a simplified syntax is available:
470
-
471
- ```js
472
- {
473
- overrides: {
474
- h1: MyParagraph,
475
- },
476
- }
477
- ```
478
-
479
- Depending on the type of element, there are some props that must be preserved to ensure the markdown is converted as intended. They are:
480
-
481
- - `a`: `title`, `href`
482
- - `img`: `title`, `alt`, `src`
483
- - `input[type="checkbox"]`: `checked`, `readonly` (specifically, the one rendered by a GFM task list)
484
- - `ol`: `start`
485
- - `td`: `style`
486
- - `th`: `style`
487
-
488
- Any conflicts between passed `props` and the specific properties above will be resolved in favor of `markdown-to-jsx`'s code.
489
-
490
- Some element mappings are a bit different from other libraries, in particular:
491
-
492
- - `span`: Used for inline text.
493
- - `code`: Used for inline code.
494
- - `pre > code`: Code blocks are a `code` element with a `pre` as its direct ancestor.
495
-
496
- #### options.overrides - Rendering Arbitrary React Components
497
-
498
- One of the most interesting use cases enabled by the HTML syntax processing in `markdown-to-jsx` is the ability to use any kind of element, even ones that aren't real HTML tags like React component classes.
499
-
500
- By adding an override for the components you plan to use in markdown documents, it's possible to dynamically render almost anything. One possible scenario could be writing documentation:
327
+ Sometimes, you might want to override the `React.createElement` default behavior to hook into the rendering process before the JSX gets rendered. This might be useful to add extra children or modify some props based on runtime conditions. The function mirrors the `React.createElement` function, so the params are [`type, [props], [...children]`](https://reactjs.org/docs/react-api.html#createelement):
501
328
 
502
- ```tsx
329
+ ```javascript
503
330
  import Markdown from 'markdown-to-jsx'
504
331
  import React from 'react'
505
332
  import { render } from 'react-dom'
506
333
 
507
- import DatePicker from './date-picker'
508
-
509
334
  const md = `
510
- # DatePicker
511
-
512
- The DatePicker works by supplying a date to bias towards,
513
- as well as a default timezone.
514
-
515
- <DatePicker biasTowardDateTime="2017-12-05T07:39:36.091Z" timezone="UTC+5" />
335
+ # Hello world
516
336
  `
517
337
 
518
338
  render(
519
339
  <Markdown
520
340
  children={md}
521
341
  options={{
522
- overrides: {
523
- DatePicker: {
524
- component: DatePicker,
525
- },
342
+ createElement(type, props, children) {
343
+ return (
344
+ <div className="parent">
345
+ {React.createElement(type, props, children)}
346
+ </div>
347
+ )
526
348
  },
527
349
  }}
528
350
  />,
@@ -530,129 +352,61 @@ render(
530
352
  )
531
353
  ```
532
354
 
533
- `markdown-to-jsx` also handles JSX interpolation syntax, but in a minimal way to not introduce a potential attack vector. Interpolations are sent to the component as their raw string, which the consumer can then `eval()` or process as desired to their security needs.
355
+ #### options.forceWrapper
534
356
 
535
- In the following case, `DatePicker` could simply run `parseInt()` on the passed `startTime` for example:
357
+ By default, the compiler does not wrap the rendered contents if there is only a single child. You can change this by setting `forceWrapper` to `true`. If the child is inline, it will not necessarily be wrapped in a `span`.
536
358
 
537
359
  ```tsx
538
- import Markdown from 'markdown-to-jsx'
539
- import React from 'react'
540
- import { render } from 'react-dom'
541
-
542
- import DatePicker from './date-picker'
543
-
544
- const md = `
545
- # DatePicker
546
-
547
- The DatePicker works by supplying a date to bias towards,
548
- as well as a default timezone.
360
+ // Using `forceWrapper` with a single, inline child…
361
+ <Markdown options={{ wrapper: 'aside', forceWrapper: true }}>
362
+ Mumble, mumble…
363
+ </Markdown>
549
364
 
550
- <DatePicker
551
- biasTowardDateTime="2017-12-05T07:39:36.091Z"
552
- timezone="UTC+5"
553
- startTime={1514579720511}
554
- />
555
- `
365
+ // renders
556
366
 
557
- render(
558
- <Markdown
559
- children={md}
560
- options={{
561
- overrides: {
562
- DatePicker: {
563
- component: DatePicker,
564
- },
565
- },
566
- }}
567
- />,
568
- document.body
569
- )
367
+ <aside>Mumble, mumble…</aside>
570
368
  ```
571
369
 
572
- Another possibility is to use something like [recompose's `withProps()` HOC](https://github.com/acdlite/recompose/blob/main/docs/API.md#withprops) to create various pregenerated scenarios and then reference them by name in the markdown:
370
+ #### options.overrides
573
371
 
574
- ```tsx
575
- import Markdown from 'markdown-to-jsx'
576
- import React from 'react'
577
- import { render } from 'react-dom'
578
- import withProps from 'recompose/withProps'
372
+ Override HTML tag rendering or render custom React components. Three use cases:
579
373
 
580
- import DatePicker from './date-picker'
581
-
582
- const DecemberDatePicker = withProps({
583
- range: {
584
- start: new Date('2017-12-01'),
585
- end: new Date('2017-12-31'),
586
- },
587
- timezone: 'UTC+5',
588
- })(DatePicker)
374
+ **1. Remove tags:** Return `null` to completely remove tags (beyond `tagfilter` escaping):
589
375
 
590
- const md = `
591
- # DatePicker
592
-
593
- The DatePicker works by supplying a date to bias towards,
594
- as well as a default timezone.
376
+ ```tsx
377
+ <Markdown options={{ overrides: { iframe: () => null } }}>
378
+ <iframe src="..."></iframe>
379
+ </Markdown>
380
+ ```
595
381
 
596
- <DatePicker
597
- biasTowardDateTime="2017-12-05T07:39:36.091Z"
598
- timezone="UTC+5"
599
- startTime={1514579720511}
600
- />
382
+ **2. Override HTML tags:** Change component, props, or both:
601
383
 
602
- Here's an example of a DatePicker pre-set to only the month of December:
384
+ ```tsx
385
+ const MyParagraph = ({ children, ...props }) => <div {...props}>{children}</div>
603
386
 
604
- <DecemberDatePicker />
605
- `
387
+ <Markdown options={{ overrides: { h1: { component: MyParagraph, props: { className: 'foo' } } } }}>
388
+ # Hello
389
+ </Markdown>
606
390
 
607
- render(
608
- <Markdown
609
- children={md}
610
- options={{
611
- overrides: {
612
- DatePicker,
613
- DecemberDatePicker,
614
- },
615
- }}
616
- />,
617
- document.body
618
- )
391
+ /** Simplified */ { overrides: { h1: MyParagraph } }
619
392
  ```
620
393
 
621
- #### options.createElement - Custom React.createElement behavior
394
+ **3. Render React components:** Use custom components in markdown:
622
395
 
623
- Sometimes, you might want to override the `React.createElement` default behavior to hook into the rendering process before the JSX gets rendered. This might be useful to add extra children or modify some props based on runtime conditions. The function mirrors the `React.createElement` function, so the params are [`type, [props], [...children]`](https://reactjs.org/docs/react-api.html#createelement):
624
-
625
- ```javascript
626
- import Markdown from 'markdown-to-jsx'
627
- import React from 'react'
628
- import { render } from 'react-dom'
396
+ ```tsx
397
+ import DatePicker from './date-picker'
629
398
 
630
- const md = `
631
- # Hello world
632
- `
399
+ const md = `<DatePicker timezone="UTC+5" startTime={1514579720511} />`
633
400
 
634
- render(
635
- <Markdown
636
- children={md}
637
- options={{
638
- createElement(type, props, children) {
639
- return (
640
- <div className="parent">
641
- {React.createElement(type, props, children)}
642
- </div>
643
- )
644
- },
645
- }}
646
- />,
647
- document.body
648
- )
401
+ <Markdown options={{ overrides: { DatePicker } }}>{md}</Markdown>
649
402
  ```
650
403
 
651
- #### options.enforceAtxHeadings
652
-
653
- Forces the compiler to have space between hash sign `#` and the header text which is explicitly stated in the most of the [markdown specs](https://github.github.com/gfm/#atx-heading).
404
+ **Important notes:**
654
405
 
655
- > The opening sequence of `#` characters must be followed by a space or by the end of line.
406
+ - Props are passed as strings; parse them in your component (e.g., `JSON.parse(columns)`)
407
+ - JSX interpolations (`{value}`) are passed as raw strings
408
+ - Some props are preserved: `a` (`href`, `title`), `img` (`src`, `alt`, `title`), `input[type="checkbox"]` (`checked`, `readonly`), `ol` (`start`), `td`/`th` (`style`)
409
+ - Element mappings: `span` for inline text, `code` for inline code, `pre > code` for code blocks
656
410
 
657
411
  #### options.renderRule
658
412
 
@@ -702,7 +456,7 @@ This can be overridden and replaced with a custom sanitizer if desired via `opti
702
456
  // or
703
457
 
704
458
  compiler('[foo](javascript:alert("foo"))', {
705
- sanitizer: (value, tag, attribute) => value,
459
+ sanitizer: value => value,
706
460
  })
707
461
  ```
708
462
 
@@ -711,135 +465,51 @@ compiler('[foo](javascript:alert("foo"))', {
711
465
  By default, a [lightweight deburring function](https://github.com/quantizor/markdown-to-jsx/blob/bc2f57412332dc670f066320c0f38d0252e0f057/index.js#L261-L275) is used to generate an HTML id from headings. You can override this by passing a function to `options.slugify`. This is helpful when you are using non-alphanumeric characters (e.g. Chinese or Japanese characters) in headings. For example:
712
466
 
713
467
  ```tsx
714
- <Markdown options={{ slugify: str => str }}># 中文</Markdown>
715
-
716
- // or
717
-
468
+ ;<Markdown options={{ slugify: str => str }}># 中文</Markdown>
718
469
  compiler('# 中文', { slugify: str => str })
719
-
720
- // renders:
721
- <h1 id="中文">中文</h1>
722
470
  ```
723
471
 
724
472
  The original function is available as a library export called `slugify`.
725
473
 
726
- #### options.disableAutoLink
474
+ #### options.wrapper
727
475
 
728
- By default, bare URLs in the markdown document will be converted into an anchor tag. This behavior can be disabled if desired.
476
+ When there are multiple children to be rendered, the compiler will wrap the output in a `div` by default. You can override this default by setting the `wrapper` option to either a string (React Element) or a component.
729
477
 
730
478
  ```tsx
731
- <Markdown options={{ disableAutoLink: true }}>
732
- The URL https://quantizor.dev will not be rendered as an anchor tag.
733
- </Markdown>
734
-
735
- // or
736
-
737
- compiler(
738
- 'The URL https://quantizor.dev will not be rendered as an anchor tag.',
739
- { disableAutoLink: true }
740
- )
479
+ const str = '# Heck Yes\n\nThis is great!'
741
480
 
742
- // renders:
481
+ <Markdown options={{ wrapper: 'article' }}>{str}</Markdown>
743
482
 
744
- <span>
745
- The URL https://quantizor.dev will not be rendered as an anchor tag.
746
- </span>
483
+ compiler(str, { wrapper: 'article' })
747
484
  ```
748
485
 
749
- #### options.preserveFrontmatter
750
-
751
- By default, YAML frontmatter at the beginning of markdown documents is parsed but not rendered in the output. Set this option to `true` to include the frontmatter in the rendered output. For HTML/JSX output, frontmatter is rendered as a `<pre>` element. For markdown-to-markdown compilation, frontmatter is included in the output markdown.
752
-
753
- | Compiler Type | Default Behavior | When `preserveFrontmatter: true` | When `preserveFrontmatter: false` |
754
- | ------------------------ | --------------------------- | -------------------------------- | --------------------------------- |
755
- | **React/HTML** | ❌ Don't render frontmatter | ✅ Render as `<pre>` element | ❌ Don't render frontmatter |
756
- | **Markdown-to-Markdown** | ✅ Preserve frontmatter | ✅ Preserve frontmatter | ❌ Exclude frontmatter |
757
-
758
- ```tsx
759
- <Markdown options={{ preserveFrontmatter: true }}>
760
- {`---
761
- title: My Document
762
- author: John Doe
763
- ---
764
-
765
- # Content
766
-
767
- This is the main content.`
768
- }
769
- </Markdown>
770
-
771
- // renders:
772
-
773
- <div>
774
- <pre>---
775
- title: My Document
776
- author: John Doe
777
- ---</pre>
778
- <h1>Content</h1>
779
- <p>This is the main content.</p>
780
- </div>
781
- ```
486
+ ##### Other useful recipes
782
487
 
783
- For markdown-to-markdown compilation:
488
+ To get an array of children back without a wrapper, set `wrapper` to `null`. This is particularly useful when using `compiler(…)` directly.
784
489
 
785
490
  ```tsx
786
- import { compiler } from 'markdown-to-jsx/markdown'
787
-
788
- const markdown = `---
789
- title: My Document
790
- author: John Doe
791
- ---
792
-
793
- # Content`
794
-
795
- // With preserveFrontmatter: true (default)
796
- compiler(markdown, { preserveFrontmatter: true })
797
- // returns: "---\ntitle: My Document\nauthor: John Doe\n---\n\n# Content"
798
-
799
- // With preserveFrontmatter: false
800
- compiler(markdown, { preserveFrontmatter: false })
801
- // returns: "# Content"
491
+ compiler('One\n\nTwo\n\nThree', { wrapper: null })[
492
+ /** Returns */ ((<p>One</p>), (<p>Two</p>), (<p>Three</p>))
493
+ ]
802
494
  ```
803
495
 
804
- #### options.disableParsingRawHTML
805
-
806
- By default, raw HTML is parsed to JSX. This behavior can be disabled if desired.
807
-
808
- ```tsx
809
- <Markdown options={{ disableParsingRawHTML: true }}>
810
- This text has <span>html</span> in it but it won't be rendered
811
- </Markdown>;
812
-
813
- // or
814
-
815
- compiler('This text has <span>html</span> in it but it won't be rendered', { disableParsingRawHTML: true });
816
-
817
- // renders:
818
-
819
- <span>This text has &lt;span&gt;html&lt;/span&gt; in it but it won't be rendered</span>
820
- ```
496
+ To render children at the same DOM level as `<Markdown>` with no HTML wrapper, set `wrapper` to `React.Fragment`. This will still wrap your children in a React node for the purposes of rendering, but the wrapper element won't show up in the DOM.
821
497
 
822
- #### options.tagfilter
498
+ #### options.wrapperProps
823
499
 
824
- By default, dangerous HTML tags are filtered and escaped to prevent XSS attacks. This applies to both HTML string output and React JSX output. The following tags are filtered: `script`, `iframe`, `style`, `title`, `textarea`, `xmp`, `noembed`, `noframes`, `plaintext`.
500
+ Props to apply to the wrapper element when `wrapper` is used.
825
501
 
826
502
  ```tsx
827
- // Tags are escaped by default (GFM-compliant)
828
- compiler('<script>alert("xss")</script>')
829
- // HTML output: '<span>&lt;script&gt;</span>'
830
- // React output: <span>&lt;script&gt;</span>
831
-
832
- // Disable tag filtering:
833
- compiler('<script>alert("xss")</script>', { tagfilter: false })
834
- // HTML output: '<script></script>'
835
- // React output: <script></script>
503
+ <Markdown
504
+ options={{
505
+ wrapper: 'article',
506
+ wrapperProps: { className: 'post', 'data-testid': 'markdown-content' },
507
+ }}
508
+ >
509
+ # Hello World
510
+ </Markdown>
836
511
  ```
837
512
 
838
- **Note**: Even when `tagfilter` is disabled, other security measures remain active:
839
-
840
- - URL sanitization preventing `javascript:` and `vbscript:` schemes in `href` and `src` attributes
841
- - Protection against `data:` URLs (except safe `data:image/*` MIME types)
842
-
843
513
  ### Syntax highlighting
844
514
 
845
515
  When using [fenced code blocks](https://www.markdownguide.org/extended-syntax/#syntax-highlighting) with language annotation, that language will be added to the `<code>` element as `class="lang-${language}"`. For best results, you can use `options.overrides` to provide an appropriate syntax highlighting integration like this one using `highlight.js`:
@@ -876,7 +546,7 @@ function App() {
876
546
  }
877
547
 
878
548
  function SyntaxHighlightedCode(props) {
879
- const ref = (React.useRef < HTMLElement) | (null > null)
549
+ const ref = React.useRef<HTMLElement | null>(null)
880
550
 
881
551
  React.useEffect(() => {
882
552
  if (ref.current && props.className?.includes('lang-') && window.hljs) {
@@ -933,24 +603,10 @@ function Example() {
933
603
  </Markdown>
934
604
  )
935
605
  }
936
-
937
- // renders
938
- // <span>On a beautiful summer day, all I want to do is <span>🙂</span>.</span>
939
606
  ```
940
607
 
941
608
  When you use `options.renderRule`, any React-renderable JSX may be returned including images and GIFs. Ensure you benchmark your solution as the `text` rule is one of the hottest paths in the system!
942
609
 
943
- ### Getting the smallest possible bundle size
944
-
945
- Many development conveniences are placed behind `process.env.NODE_ENV !== "production"` conditionals. When bundling your app, it's a good idea to replace these code snippets such that a minifier (like uglify) can sweep them away and leave a smaller overall bundle.
946
-
947
- Here are instructions for some of the popular bundlers:
948
-
949
- - [webpack](https://webpack.js.org/guides/production/#specify-the-environment)
950
- - [browserify plugin](https://github.com/hughsk/envify)
951
- - [parcel](https://parceljs.org/production.html)
952
- - [fuse-box](http://fuse-box.org/plugins/replace-plugin#notes)
953
-
954
610
  ### Usage with Preact
955
611
 
956
612
  Everything will work just fine! Simply [Alias `react` to `preact/compat`](https://preactjs.com/guide/v10/switching-to-preact#setting-up-compat) like you probably already are doing.
@@ -1122,92 +778,29 @@ if (node.type === RuleType.heading) {
1122
778
 
1123
779
  ### Gotchas
1124
780
 
1125
- #### Passing props to stringified React components
1126
-
1127
- Using the [`options.overrides`](#optionsoverrides---rendering-arbitrary-react-components) functionality to render React components, props are passed into the component in stringifed form. It is up to you to parse the string to make use of the data.
781
+ **Props are stringified:** When using `overrides` with React components, props are passed as strings. Parse them in your component:
1128
782
 
1129
783
  ```tsx
1130
- const Table: React.FC<
1131
- JSX.IntrinsicElements['table'] & {
1132
- columns: string
1133
- dataSource: string
1134
- }
1135
- > = ({ columns, dataSource, ...props }) => {
784
+ const Table = ({ columns, dataSource, ...props }) => {
1136
785
  const parsedColumns = JSON.parse(columns)
1137
786
  const parsedData = JSON.parse(dataSource)
1138
-
1139
- return (
1140
- <div {...props}>
1141
- <h1>Columns</h1>
1142
- {parsedColumns.map(column => (
1143
- <span key={column.key}>{column.title}</span>
1144
- ))}
1145
-
1146
- <h2>Data</h2>
1147
- {parsedData.map(datum => (
1148
- <span key={datum.key}>{datum.Month}</span>
1149
- ))}
1150
- </div>
1151
- )
787
+ // ... use parsed data
1152
788
  }
1153
-
1154
- /**
1155
- * Example HTML in markdown:
1156
- *
1157
- * <Table
1158
- * columns={[{ title: 'Month', dataIndex: 'Month', key: 'Month' }]}
1159
- * dataSource={[
1160
- * {
1161
- * Month: '2024-09-01',
1162
- * 'Forecasted Revenue': '$3,137,678.85',
1163
- * 'Forecasted Expenses': '$2,036,660.28',
1164
- * key: 0,
1165
- * },
1166
- * ]}
1167
- * />
1168
- */
1169
789
  ```
1170
790
 
1171
- #### Significant indentation inside arbitrary HTML
791
+ **HTML indentation:** Leading whitespace in HTML blocks is auto-trimmed based on the first line's indentation to avoid markdown syntax conflicts.
1172
792
 
1173
- People usually write HTML like this:
1174
-
1175
- ```html
1176
- <div>Hey, how are you?</div>
1177
- ```
1178
-
1179
- Note the leading spaces before the inner content. This sort of thing unfortunately clashes with existing markdown syntaxes since 4 spaces === a code block and other similar collisions.
1180
-
1181
- To get around this, `markdown-to-jsx` left-trims approximately as much whitespace as the first line inside the HTML block. So for example:
1182
-
1183
- ```html
1184
- <div># Hello How are you?</div>
1185
- ```
1186
-
1187
- The two leading spaces in front of "# Hello" would be left-trimmed from all lines inside the HTML block. In the event that there are varying amounts of indentation, only the amount of the first line is trimmed.
1188
-
1189
- > NOTE! These syntaxes work just fine when you aren't writing arbitrary HTML wrappers inside your markdown. This is very much an edge case of an edge case. 🙃
1190
-
1191
- #### Code blocks
1192
-
1193
- ⛔️
1194
-
1195
- ```md
1196
- <div>
1197
- var some = code();
1198
- </div>
1199
- ```
1200
-
1201
-
793
+ **Code in HTML:** Don't put code directly in HTML divs. Use fenced code blocks instead:
1202
794
 
1203
795
  ````md
1204
796
  <div>
1205
797
  ```js
1206
- var some = code();
1207
- ```
1208
- </div>
798
+ var code = here();
1209
799
  ````
1210
800
 
801
+ </div>
802
+ ```
803
+
1211
804
  ## Changelog
1212
805
 
1213
806
  See [Github Releases](https://github.com/quantizor/markdown-to-jsx/releases).