markdown-to-jsx 9.3.5 → 9.4.1
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 +190 -29
- package/dist/html.cjs +102 -102
- package/dist/html.d.cts +30 -2
- package/dist/html.d.ts +30 -2
- package/dist/html.js +102 -102
- package/dist/html.js.map +5 -5
- package/dist/index.cjs +98 -98
- package/dist/index.d.cts +32 -4
- package/dist/index.d.ts +32 -4
- package/dist/index.js +98 -98
- package/dist/index.js.map +5 -5
- package/dist/markdown.cjs +118 -118
- package/dist/markdown.js +118 -118
- package/dist/markdown.js.map +5 -5
- package/dist/native.cjs +102 -102
- package/dist/native.d.cts +36 -3
- package/dist/native.d.ts +36 -3
- package/dist/native.js +102 -102
- package/dist/native.js.map +5 -5
- package/dist/react.cjs +99 -99
- package/dist/react.d.cts +38 -5
- package/dist/react.d.ts +38 -5
- package/dist/react.js +99 -99
- package/dist/react.js.map +5 -5
- package/dist/solid.cjs +102 -102
- package/dist/solid.d.cts +30 -2
- package/dist/solid.d.ts +30 -2
- package/dist/solid.js +102 -102
- package/dist/solid.js.map +5 -5
- package/dist/vue.cjs +91 -91
- package/dist/vue.d.cts +38 -5
- package/dist/vue.d.ts +38 -5
- package/dist/vue.js +91 -91
- package/dist/vue.js.map +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ Some special features of the library:
|
|
|
35
35
|
- [options.createElement](#optionscreateelement)
|
|
36
36
|
- [options.forceWrapper](#optionsforcewrapper)
|
|
37
37
|
- [options.overrides](#optionsoverrides)
|
|
38
|
+
- [options.evalUnserializableExpressions](#optionsevalunserializableexpressions)
|
|
38
39
|
- [options.renderRule](#optionsrenderrule)
|
|
39
40
|
- [options.sanitizer](#optionssanitizer)
|
|
40
41
|
- [options.slugify](#optionsslugify)
|
|
@@ -378,23 +379,24 @@ const normalizedMarkdown2 = astToMarkdown(ast)
|
|
|
378
379
|
|
|
379
380
|
#### All Options
|
|
380
381
|
|
|
381
|
-
| Option
|
|
382
|
-
|
|
|
383
|
-
| `createElement`
|
|
384
|
-
| `disableAutoLink`
|
|
385
|
-
| `disableParsingRawHTML`
|
|
386
|
-
| `enforceAtxHeadings`
|
|
387
|
-
| `
|
|
388
|
-
| `
|
|
389
|
-
| `
|
|
390
|
-
| `
|
|
391
|
-
| `
|
|
392
|
-
| `
|
|
393
|
-
| `
|
|
394
|
-
| `
|
|
395
|
-
| `
|
|
396
|
-
| `
|
|
397
|
-
| `
|
|
382
|
+
| Option | Type | Default | Description |
|
|
383
|
+
| ------------------------------- | ----------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
384
|
+
| `createElement` | `function` | - | Custom createElement behavior (React/React Native/SolidJS/Vue only). See [createElement](#optionscreateelement) for details. |
|
|
385
|
+
| `disableAutoLink` | `boolean` | `false` | Disable automatic conversion of bare URLs to anchor tags. |
|
|
386
|
+
| `disableParsingRawHTML` | `boolean` | `false` | Disable parsing of raw HTML into JSX. |
|
|
387
|
+
| `enforceAtxHeadings` | `boolean` | `false` | Require space between `#` and header text (GFM spec compliance). |
|
|
388
|
+
| `evalUnserializableExpressions` | `boolean` | `false` | ⚠️ Eval unserializable props (DANGEROUS). See [evalUnserializableExpressions](#optionsevalunserializableexpressions) for details. |
|
|
389
|
+
| `forceBlock` | `boolean` | `false` | Force all content to be treated as block-level. |
|
|
390
|
+
| `forceInline` | `boolean` | `false` | Force all content to be treated as inline. |
|
|
391
|
+
| `forceWrapper` | `boolean` | `false` | Force wrapper even with single child (React/React Native/Vue only). See [forceWrapper](#optionsforcewrapper) for details. |
|
|
392
|
+
| `overrides` | `object` | - | Override HTML tag rendering. See [overrides](#optionsoverrides) for details. |
|
|
393
|
+
| `preserveFrontmatter` | `boolean` | `false` | Include frontmatter in rendered output (as `<pre>` for HTML/JSX, included in markdown). Behavior varies by compiler type. |
|
|
394
|
+
| `renderRule` | `function` | - | Custom rendering for AST rules. See [renderRule](#optionsrenderrule) for details. |
|
|
395
|
+
| `sanitizer` | `function` | built-in | Custom URL sanitizer function. See [sanitizer](#optionssanitizer) for details. |
|
|
396
|
+
| `slugify` | `function` | built-in | Custom slug generation for heading IDs. See [slugify](#optionsslugify) for details. |
|
|
397
|
+
| `tagfilter` | `boolean` | `true` | Escape dangerous HTML tags (`script`, `iframe`, `style`, etc.) to prevent XSS. |
|
|
398
|
+
| `wrapper` | `string \| component \| null` | `'div'` | Wrapper element for multiple children (React/React Native/Vue only). See [wrapper](#optionswrapper) for details. |
|
|
399
|
+
| `wrapperProps` | `object` | - | Props for wrapper element (React/React Native/Vue only). See [wrapperProps](#optionswrapperprops) for details. |
|
|
398
400
|
|
|
399
401
|
#### options.createElement
|
|
400
402
|
|
|
@@ -477,14 +479,105 @@ const md = `<DatePicker timezone="UTC+5" startTime={1514579720511} />`
|
|
|
477
479
|
|
|
478
480
|
**Important notes:**
|
|
479
481
|
|
|
480
|
-
-
|
|
481
|
-
-
|
|
482
|
+
- **JSX props are intelligently parsed** (v9.1+):
|
|
483
|
+
- Arrays and objects: `data={[1, 2, 3]}` → parsed as `[1, 2, 3]`
|
|
484
|
+
- Booleans: `enabled={true}` → parsed as `true`
|
|
485
|
+
- Functions: `onClick={() => ...}` → kept as string for security (use [renderRule](#optionsrenderrule) for case-by-case handling, or see [evalUnserializableExpressions](#optionsevalunserializableexpressions))
|
|
486
|
+
- Complex expressions: `value={someVar}` → kept as string
|
|
487
|
+
- The original raw attribute string is available in `node.rawAttrs` when using `parser()`
|
|
482
488
|
- Some props are preserved: `a` (`href`, `title`), `img` (`src`, `alt`, `title`), `input[type="checkbox"]` (`checked`, `readonly`), `ol` (`start`), `td`/`th` (`style`)
|
|
483
489
|
- Element mappings: `span` for inline text, `code` for inline code, `pre > code` for code blocks
|
|
484
490
|
|
|
491
|
+
#### options.evalUnserializableExpressions
|
|
492
|
+
|
|
493
|
+
**⚠️ SECURITY WARNING: STRONGLY DISCOURAGED FOR USER INPUTS**
|
|
494
|
+
|
|
495
|
+
When enabled, attempts to eval expressions in JSX props that cannot be serialized as JSON (functions, variables, complex expressions). This uses `eval()` which can execute arbitrary code.
|
|
496
|
+
|
|
497
|
+
**By default (recommended)**, unserializable expressions are kept as strings for security:
|
|
498
|
+
|
|
499
|
+
```tsx
|
|
500
|
+
import { parser } from 'markdown-to-jsx'
|
|
501
|
+
|
|
502
|
+
const ast = parser('<Button onClick={() => alert("hi")} />')
|
|
503
|
+
// ast[0].attrs.onClick === "() => alert(\"hi\")" (string, safe)
|
|
504
|
+
|
|
505
|
+
// Arrays and objects are automatically parsed (no eval needed):
|
|
506
|
+
const ast2 = parser('<Table data={[1, 2, 3]} />')
|
|
507
|
+
// ast2[0].attrs.data === [1, 2, 3] (parsed via JSON.parse)
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**ONLY enable this option when:**
|
|
511
|
+
|
|
512
|
+
- The markdown source is completely trusted (e.g., your own documentation)
|
|
513
|
+
- You control all JSX components and their props
|
|
514
|
+
- The content is NOT user-generated or user-editable
|
|
515
|
+
|
|
516
|
+
**DO NOT enable this option when:**
|
|
517
|
+
|
|
518
|
+
- Processing user-submitted markdown
|
|
519
|
+
- Rendering untrusted content
|
|
520
|
+
- Building public-facing applications with user content
|
|
521
|
+
|
|
522
|
+
**Example of the danger:**
|
|
523
|
+
|
|
524
|
+
```tsx
|
|
525
|
+
// User-submitted markdown with malicious code
|
|
526
|
+
const userMarkdown = '<Component onClick={() => fetch("/admin/delete-all")} />'
|
|
527
|
+
|
|
528
|
+
// ❌ DANGEROUS - function will be executable
|
|
529
|
+
parser(userMarkdown, { evalUnserializableExpressions: true })
|
|
530
|
+
|
|
531
|
+
// ✅ SAFE - function kept as string
|
|
532
|
+
parser(userMarkdown) // default behavior
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
**Safe alternative: Use renderRule for case-by-case handling:**
|
|
536
|
+
|
|
537
|
+
```tsx
|
|
538
|
+
// Instead of eval'ing arbitrary expressions, handle them selectively in renderRule:
|
|
539
|
+
const handlers = {
|
|
540
|
+
handleClick: () => console.log('clicked'),
|
|
541
|
+
handleSubmit: () => console.log('submitted'),
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
compiler(markdown, {
|
|
545
|
+
renderRule(next, node) {
|
|
546
|
+
if (
|
|
547
|
+
node.type === RuleType.htmlBlock &&
|
|
548
|
+
typeof node.attrs?.onClick === 'string'
|
|
549
|
+
) {
|
|
550
|
+
// Option 1: Named handler lookup (safest)
|
|
551
|
+
const handler = handlers[node.attrs.onClick]
|
|
552
|
+
if (handler) {
|
|
553
|
+
return <button onClick={handler}>{/* ... */}</button>
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Option 2: Selective eval with allowlist (still risky)
|
|
557
|
+
if (
|
|
558
|
+
node.tag === 'TrustedComponent' &&
|
|
559
|
+
node.attrs.onClick.startsWith('() =>')
|
|
560
|
+
) {
|
|
561
|
+
try {
|
|
562
|
+
const fn = eval(`(${node.attrs.onClick})`)
|
|
563
|
+
return <button onClick={fn}>{/* ... */}</button>
|
|
564
|
+
} catch (e) {
|
|
565
|
+
// Handle error
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return next()
|
|
570
|
+
},
|
|
571
|
+
})
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
This approach gives you full control over which expressions are evaluated and under what conditions.
|
|
575
|
+
|
|
485
576
|
#### options.renderRule
|
|
486
577
|
|
|
487
|
-
Supply your own rendering function that can selectively override how _rules_ are rendered (note, this is different than _`options.overrides`_ which operates at the HTML tag level and is more general).
|
|
578
|
+
Supply your own rendering function that can selectively override how _rules_ are rendered (note, this is different than _`options.overrides`_ which operates at the HTML tag level and is more general). The `renderRule` function always executes before any other rendering code, giving you full control over how nodes are rendered, including normally-skipped nodes like `ref`, `footnote`, and `frontmatter`.
|
|
579
|
+
|
|
580
|
+
You can use this functionality to do pretty much anything with an established AST node; here's an example of selectively overriding the "codeBlock" rule to process LaTeX syntax using the `@matejmazur/react-katex` library:
|
|
488
581
|
|
|
489
582
|
````tsx
|
|
490
583
|
import Markdown, { RuleType } from 'markdown-to-jsx'
|
|
@@ -513,17 +606,41 @@ function App() {
|
|
|
513
606
|
}
|
|
514
607
|
````
|
|
515
608
|
|
|
609
|
+
**Accessing parsed HTML content:** For HTML blocks marked as `verbatim` (like `<script>`, `<style>`, `<pre>`), default renderers use `rawText` for CommonMark compliance, but `renderRule` can access the fully parsed AST in `children`:
|
|
610
|
+
|
|
611
|
+
```tsx
|
|
612
|
+
<Markdown
|
|
613
|
+
options={{
|
|
614
|
+
renderRule(next, node, renderChildren) {
|
|
615
|
+
if (node.type === RuleType.htmlBlock && node.tag === 'script') {
|
|
616
|
+
// Access parsed children even for verbatim blocks
|
|
617
|
+
const parsedContent = node.children || []
|
|
618
|
+
// Or use rawText for original content
|
|
619
|
+
const rawContent = node.rawText || ''
|
|
620
|
+
|
|
621
|
+
// Custom rendering logic here
|
|
622
|
+
return <CustomScript content={parsedContent} raw={rawContent} />
|
|
623
|
+
}
|
|
624
|
+
return next()
|
|
625
|
+
},
|
|
626
|
+
}}
|
|
627
|
+
>
|
|
628
|
+
<script>Hello **world**</script>
|
|
629
|
+
</Markdown>
|
|
630
|
+
```
|
|
631
|
+
|
|
516
632
|
#### options.sanitizer
|
|
517
633
|
|
|
518
634
|
By default a lightweight URL sanitizer function is provided to avoid common attack vectors that might be placed into the `href` of an anchor tag, for example. The sanitizer receives the input, the HTML tag being targeted, and the attribute name. The original function is available as a library export called `sanitizer`.
|
|
519
635
|
|
|
520
636
|
This can be overridden and replaced with a custom sanitizer if desired via `options.sanitizer`:
|
|
521
637
|
|
|
638
|
+
<!-- prettier-ignore -->
|
|
522
639
|
```tsx
|
|
523
640
|
// sanitizer in this situation would receive:
|
|
524
641
|
// ('javascript:alert("foo")', 'a', 'href')
|
|
525
642
|
|
|
526
|
-
|
|
643
|
+
<Markdown options={{ sanitizer: (value, tag, attribute) => value }}>
|
|
527
644
|
{`[foo](javascript:alert("foo"))`}
|
|
528
645
|
</Markdown>
|
|
529
646
|
|
|
@@ -538,8 +655,9 @@ compiler('[foo](javascript:alert("foo"))', {
|
|
|
538
655
|
|
|
539
656
|
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:
|
|
540
657
|
|
|
658
|
+
<!-- prettier-ignore -->
|
|
541
659
|
```tsx
|
|
542
|
-
|
|
660
|
+
<Markdown options={{ slugify: str => str }}># 中文</Markdown>
|
|
543
661
|
compiler('# 中文', { slugify: str => str })
|
|
544
662
|
```
|
|
545
663
|
|
|
@@ -722,11 +840,25 @@ The AST consists of the following node types (use `RuleType` to check node types
|
|
|
722
840
|
```tsx
|
|
723
841
|
{ type: RuleType.table, header: [...], cells: [[...]], align: [...] }
|
|
724
842
|
```
|
|
725
|
-
- `RuleType.htmlBlock` - HTML blocks
|
|
843
|
+
- `RuleType.htmlBlock` - HTML blocks and JSX components
|
|
844
|
+
|
|
726
845
|
```tsx
|
|
727
|
-
{
|
|
846
|
+
{
|
|
847
|
+
type: RuleType.htmlBlock,
|
|
848
|
+
tag: "div",
|
|
849
|
+
attrs: {},
|
|
850
|
+
rawAttrs?: string,
|
|
851
|
+
children?: ASTNode[],
|
|
852
|
+
verbatim?: boolean,
|
|
853
|
+
rawText?: string,
|
|
854
|
+
text?: string // @deprecated - use rawText instead
|
|
855
|
+
}
|
|
728
856
|
```
|
|
729
857
|
|
|
858
|
+
**Note (v9.1+):** JSX components with blank lines between opening/closing tags now properly nest children instead of creating sibling nodes.
|
|
859
|
+
|
|
860
|
+
**HTML Block Parsing (v9.2+):** HTML blocks are always fully parsed into the `children` property, even when marked as `verbatim`. The `verbatim` flag acts as a rendering hint (default renderers use `rawText` for verbatim blocks to maintain CommonMark compliance), but `renderRule` implementations can access the fully parsed AST in `children` for all HTML blocks. The `rawText` field contains the original raw HTML content for verbatim blocks, while `rawAttrs` contains the original attribute string.
|
|
861
|
+
|
|
730
862
|
**Inline nodes:**
|
|
731
863
|
|
|
732
864
|
- `RuleType.text` - Plain text
|
|
@@ -772,6 +904,15 @@ The AST consists of the following node types (use `RuleType` to check node types
|
|
|
772
904
|
{ type: RuleType.htmlSelfClosing, tag: "img", attrs: { src: "image.png" } }
|
|
773
905
|
```
|
|
774
906
|
|
|
907
|
+
**JSX Prop Parsing (v9.1+):**
|
|
908
|
+
|
|
909
|
+
The parser intelligently parses JSX prop values:
|
|
910
|
+
|
|
911
|
+
- Arrays/objects are parsed via `JSON.parse()`: `rows={[["a", "b"]]}` → `attrs.rows = [["a", "b"]]`
|
|
912
|
+
- Functions are kept as strings for security: `onClick={() => ...}` → `attrs.onClick = "() => ..."`
|
|
913
|
+
- Booleans are parsed: `enabled={true}` → `attrs.enabled = true`
|
|
914
|
+
- The original raw attribute string is preserved in `rawAttrs` field
|
|
915
|
+
|
|
775
916
|
#### Example AST Structure
|
|
776
917
|
|
|
777
918
|
````tsx
|
|
@@ -852,16 +993,36 @@ if (node.type === RuleType.heading) {
|
|
|
852
993
|
|
|
853
994
|
### Gotchas
|
|
854
995
|
|
|
855
|
-
**
|
|
996
|
+
**JSX prop parsing (v9.1+):** Arrays and objects in JSX props are automatically parsed:
|
|
856
997
|
|
|
998
|
+
<!-- prettier-ignore -->
|
|
857
999
|
```tsx
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1000
|
+
// In markdown:
|
|
1001
|
+
<Table
|
|
1002
|
+
columns={['Name', 'Age']}
|
|
1003
|
+
data={[
|
|
1004
|
+
['Alice', 30],
|
|
1005
|
+
['Bob', 25],
|
|
1006
|
+
]}
|
|
1007
|
+
/>
|
|
1008
|
+
|
|
1009
|
+
// In your component (v9.1+):
|
|
1010
|
+
const Table = ({ columns, data, ...props }) => {
|
|
1011
|
+
// columns is already an array: ["Name", "Age"]
|
|
1012
|
+
// data is already an array: [["Alice", 30], ["Bob", 25]]
|
|
1013
|
+
// No JSON.parse needed!
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// For backwards compatibility, check types:
|
|
1017
|
+
const Table = ({ columns, data, ...props }) => {
|
|
1018
|
+
const parsedColumns =
|
|
1019
|
+
typeof columns === 'string' ? JSON.parse(columns) : columns
|
|
1020
|
+
const parsedData = typeof data === 'string' ? JSON.parse(data) : data
|
|
862
1021
|
}
|
|
863
1022
|
```
|
|
864
1023
|
|
|
1024
|
+
**Function props are kept as strings** for security. Use [renderRule](#optionsrenderrule) for case-by-case handling, or see [evalUnserializableExpressions](#optionsevalunserializableexpressions) for opt-in eval.
|
|
1025
|
+
|
|
865
1026
|
**HTML indentation:** Leading whitespace in HTML blocks is auto-trimmed based on the first line's indentation to avoid markdown syntax conflicts.
|
|
866
1027
|
|
|
867
1028
|
**Code in HTML:** Don't put code directly in HTML divs. Use fenced code blocks instead:
|