markdown-to-jsx 9.3.4 → 9.4.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 +150 -27
- package/dist/html.cjs +97 -97
- package/dist/html.d.cts +26 -0
- package/dist/html.d.ts +26 -0
- package/dist/html.js +97 -97
- package/dist/html.js.map +4 -4
- package/dist/index.cjs +87 -87
- package/dist/index.d.cts +28 -2
- package/dist/index.d.ts +28 -2
- package/dist/index.js +87 -87
- package/dist/index.js.map +5 -5
- package/dist/markdown.cjs +110 -110
- package/dist/markdown.js +110 -110
- package/dist/markdown.js.map +5 -5
- package/dist/native.cjs +93 -93
- package/dist/native.d.cts +32 -1
- package/dist/native.d.ts +32 -1
- package/dist/native.js +103 -103
- package/dist/native.js.map +5 -5
- package/dist/react.cjs +83 -83
- package/dist/react.d.cts +34 -3
- package/dist/react.d.ts +34 -3
- package/dist/react.js +84 -84
- package/dist/react.js.map +5 -5
- package/dist/solid.cjs +95 -95
- package/dist/solid.d.cts +26 -0
- package/dist/solid.d.ts +26 -0
- package/dist/solid.js +95 -95
- package/dist/solid.js.map +4 -4
- package/dist/vue.cjs +88 -88
- package/dist/vue.d.cts +34 -3
- package/dist/vue.d.ts +34 -3
- package/dist/vue.js +85 -85
- 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,11 +479,100 @@ 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
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). 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:
|
|
@@ -519,11 +610,12 @@ By default a lightweight URL sanitizer function is provided to avoid common atta
|
|
|
519
610
|
|
|
520
611
|
This can be overridden and replaced with a custom sanitizer if desired via `options.sanitizer`:
|
|
521
612
|
|
|
613
|
+
<!-- prettier-ignore -->
|
|
522
614
|
```tsx
|
|
523
615
|
// sanitizer in this situation would receive:
|
|
524
616
|
// ('javascript:alert("foo")', 'a', 'href')
|
|
525
617
|
|
|
526
|
-
|
|
618
|
+
<Markdown options={{ sanitizer: (value, tag, attribute) => value }}>
|
|
527
619
|
{`[foo](javascript:alert("foo"))`}
|
|
528
620
|
</Markdown>
|
|
529
621
|
|
|
@@ -538,8 +630,9 @@ compiler('[foo](javascript:alert("foo"))', {
|
|
|
538
630
|
|
|
539
631
|
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
632
|
|
|
633
|
+
<!-- prettier-ignore -->
|
|
541
634
|
```tsx
|
|
542
|
-
|
|
635
|
+
<Markdown options={{ slugify: str => str }}># 中文</Markdown>
|
|
543
636
|
compiler('# 中文', { slugify: str => str })
|
|
544
637
|
```
|
|
545
638
|
|
|
@@ -722,10 +815,11 @@ The AST consists of the following node types (use `RuleType` to check node types
|
|
|
722
815
|
```tsx
|
|
723
816
|
{ type: RuleType.table, header: [...], cells: [[...]], align: [...] }
|
|
724
817
|
```
|
|
725
|
-
- `RuleType.htmlBlock` - HTML blocks
|
|
818
|
+
- `RuleType.htmlBlock` - HTML blocks and JSX components
|
|
726
819
|
```tsx
|
|
727
820
|
{ type: RuleType.htmlBlock, tag: "div", attrs: {}, children: [...] }
|
|
728
821
|
```
|
|
822
|
+
**Note (v9.1+):** JSX components with blank lines between opening/closing tags now properly nest children instead of creating sibling nodes.
|
|
729
823
|
|
|
730
824
|
**Inline nodes:**
|
|
731
825
|
|
|
@@ -772,6 +866,15 @@ The AST consists of the following node types (use `RuleType` to check node types
|
|
|
772
866
|
{ type: RuleType.htmlSelfClosing, tag: "img", attrs: { src: "image.png" } }
|
|
773
867
|
```
|
|
774
868
|
|
|
869
|
+
**JSX Prop Parsing (v9.1+):**
|
|
870
|
+
|
|
871
|
+
The parser intelligently parses JSX prop values:
|
|
872
|
+
|
|
873
|
+
- Arrays/objects are parsed via `JSON.parse()`: `rows={[["a", "b"]]}` → `attrs.rows = [["a", "b"]]`
|
|
874
|
+
- Functions are kept as strings for security: `onClick={() => ...}` → `attrs.onClick = "() => ..."`
|
|
875
|
+
- Booleans are parsed: `enabled={true}` → `attrs.enabled = true`
|
|
876
|
+
- The original raw attribute string is preserved in `rawAttrs` field
|
|
877
|
+
|
|
775
878
|
#### Example AST Structure
|
|
776
879
|
|
|
777
880
|
````tsx
|
|
@@ -852,16 +955,36 @@ if (node.type === RuleType.heading) {
|
|
|
852
955
|
|
|
853
956
|
### Gotchas
|
|
854
957
|
|
|
855
|
-
**
|
|
958
|
+
**JSX prop parsing (v9.1+):** Arrays and objects in JSX props are automatically parsed:
|
|
856
959
|
|
|
960
|
+
<!-- prettier-ignore -->
|
|
857
961
|
```tsx
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
962
|
+
// In markdown:
|
|
963
|
+
<Table
|
|
964
|
+
columns={['Name', 'Age']}
|
|
965
|
+
data={[
|
|
966
|
+
['Alice', 30],
|
|
967
|
+
['Bob', 25],
|
|
968
|
+
]}
|
|
969
|
+
/>
|
|
970
|
+
|
|
971
|
+
// In your component (v9.1+):
|
|
972
|
+
const Table = ({ columns, data, ...props }) => {
|
|
973
|
+
// columns is already an array: ["Name", "Age"]
|
|
974
|
+
// data is already an array: [["Alice", 30], ["Bob", 25]]
|
|
975
|
+
// No JSON.parse needed!
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// For backwards compatibility, check types:
|
|
979
|
+
const Table = ({ columns, data, ...props }) => {
|
|
980
|
+
const parsedColumns =
|
|
981
|
+
typeof columns === 'string' ? JSON.parse(columns) : columns
|
|
982
|
+
const parsedData = typeof data === 'string' ? JSON.parse(data) : data
|
|
862
983
|
}
|
|
863
984
|
```
|
|
864
985
|
|
|
986
|
+
**Function props are kept as strings** for security. Use [renderRule](#optionsrenderrule) for case-by-case handling, or see [evalUnserializableExpressions](#optionsevalunserializableexpressions) for opt-in eval.
|
|
987
|
+
|
|
865
988
|
**HTML indentation:** Leading whitespace in HTML blocks is auto-trimmed based on the first line's indentation to avoid markdown syntax conflicts.
|
|
866
989
|
|
|
867
990
|
**Code in HTML:** Don't put code directly in HTML divs. Use fenced code blocks instead:
|