- 47408cd: Parsing is significantly faster, around 40% on large documents, with the biggest gains on link-heavy and code-heavy content. Also fixed an edge case where a reference definition split across blockquote lines could be matched by a link using the raw, unnormalized label.
- 47408cd: Documents with many bold or italic spans now parse in a fraction of the time. Previously, parsing slowed down sharply as the number of emphasized spans grew, which could make very large or adversarial inputs hang.
- 47408cd: The
<Markdown>component no longer re-parses its content when a parent re-renders with unchanged props. Previously the rest-props object was reallocated on every render, which invalidated the internal compile cache and forced a full re-parse each time. - 47408cd: Text on its own line after a nested HTML element (such as content following
</summary>inside<details>) now renders instead of being dropped or misplaced when no blank line separates them. This now behaves consistently across the React, React Native, Solid, and Vue outputs.
- 24661c0: fix: stop fast-skip from truncating bare email local-parts containing inline-special chars
- c0c432f: Markdown with many malformed link starts (e.g. repeated
[]() now parses quickly instead of slowing to a crawl on large inputs.
- 51a68b1: React Native:
overridesnow work the same as on web. Pass an override forcode,pre,strong,em,del,blockquote,hr,h1–h6,ul,ol,li, orinputand it fires for parsed markdown — no more silent no-ops on inline emphasis, fenced code, headings, lists, or GFM task checkboxes. Thestylesprop is also tightened: each key is narrowed to the style type its component actually accepts (TextStyle,ViewStyle, orImageStyle), so passing anImageStyletoparagraphis now a compile-time error. Task list items now render with sensible row + center-aligned defaults so the checkbox and label sit on the same line out of the box; pass your ownstyles.listItemto opt out (e.g. for multi-line task labels). TheMarkdowncomponent additionally acceptsstring[]children to absorb the common JSX case where children arrive as a coalesced array.
- fb5efc2: Fix HTML output for markdown inside
<table>cells (#862). Lists, blockquotes, fenced code, and headings inside a cell now render as nested content without breaking the surrounding rows or dropping closing tags.
- 7ff0713: Fix React 19 RSC dev warning "Attempted to render without development properties"
-
3df970f: Fix frontmatter detection silently consuming content when a thematic break (
---) starts the document. The colon-anywhere heuristic is replaced with proper YAML key-value validation, and a newdisableFrontmatteroption is added to skip detection entirely. -
c7e0d07: Fix
<hr>and other void HTML elements silently dropping all subsequent content when not followed by a newline (#856) -
c7e0d07: Fix HTML blocks with markdown content inside tables (#862) and restore CommonMark-correct behavior for HTML block content without blank lines (#860)
- Markdown lists inside HTML table cells now render as proper nested lists instead of breaking the table structure
- HTML block content on its own line without surrounding blank lines (e.g.
<div>\n*text*\n</div>) is now preserved as literal text per CommonMark Example 189 - HTML block content surrounded by blank lines (e.g.
<div>\n\n*text*\n\n</div>) continues to parse markdown as before (CommonMark Example 188)
-
0dfde05: Fix HTML compiler dropping the closing tag for empty non-void elements (e.g.
<p></p>rendered as<p>,<div></div>rendered as<div>) -
b0a7c68: fix: add key props to thead/tbody in table rendering to resolve React key warning (#858)
-
c7e0d07: Fix Vue adapter "Non-function value encountered for default slot" warning when using component overrides (#855)
- bcf178a: Fix streaming mode incorrectly stripping self-closing custom component tags (e.g.
<CustomButton />) and leaking incomplete trailing tags as escaped text in inline content.
- 1c430ae: Fix missing TypeScript declaration files in published package. Add standalone post-build verification that fails the build when type declarations are not generated, independent of the bundler's plugin system.
- 5eecb05: Skip rendering empty tbody when a table has only a header row and no data rows.
- 130cc33: Suppress React 19 RSC development warning about missing internal properties on manually-created elements.
-
3daa41e: fix: strip trailing asterisks from bare URL href (fixes #839)
When a bare URL was wrapped in bold markdown (
**url**), the generated link'shrefincorrectly included the closing asterisks (e.g.href="https://example.com/foo**"). The parser now trims trailing*from bare URLs so the href is correct. No consumer changes required. -
f520531: resolve emphasis delimiters closing before hard line breaks (two trailing spaces or backslash before newline)
-
f520531: include _store on raw React elements unconditionally so React dev-mode validation works in all bundler environments
- 2d21e43: Fail the build when type declarations are not generated, preventing releases without TypeScript types.
- 58502fc: Resolve broken exports in bundled output caused by Bun bundler bug with cross-entry re-exports
- e0100f0: fix: nested HTML blocks with same tag name now correctly match depth-paired closing tags
- bf5d906: fix: suppress ambiguous setext headings during streaming to avoid premature heading rendering
-
565e3ea: fix: add missing
_ownerfield on raw React elements for dev-mode compatibilityFixes "Cannot set properties of undefined (setting 'validated')" errors in React 19 dev mode by adding the
_ownerfield that React's reconciler expects on all elements. -
565e3ea: fix: prevent void elements from receiving children when preceded by a blank line
Void HTML elements (e.g.
<br>,<hr>,<img>) preceded by a blank line no longer cause React error #137. The parser now returns void elements as content-less blocks, and all compilers guard against passing children to void elements.
- cc1a8a7: Fix "Cannot set properties of undefined (setting 'validated')" error introduced in 9.7.1. React's dev-mode reconciler sets
element._store.validatedto track element creation source; raw elements created by the fast path now include_store: {}in non-production builds.
- 01b68df: - Fix HTML entity decoding in link titles (e.g.
–now correctly decodes to–)- Fix bare email autolinks matching when DNS labels exceed 63 characters
- Fix
<pre>,<script>,<style>, and<textarea>content being incorrectly parsed as markdown instead of rendered verbatim
- 2dca780: Improve HTML compiler performance by ~57%, bringing it to parity with the React compiler.
- 30db3f3: Accept case-insensitive GFM alert blockquote syntax (e.g.,
[!Tip],[!tip]) matching GitHub's behavior. - da2eb8c: Moved benchmarking and documentation website dev dependencies out of the library package for cleaner dependency management.
- 9830b70: Fix entity resolution in CodeSandbox and other bundlers by exposing entities as a public subpath export. Bundlers now resolve
markdown-to-jsx/entitiesusing thebrowsercondition, ensuring the optimized DOM-based decoder (~300B) is used in browsers instead of the full entity table (~29KB). - e537dca: Bypass React.createElement for ~2x faster JSX output by constructing raw React element objects directly. The $$typeof symbol is auto-detected from the installed React version for forward compatibility. Falls back to createElement when a custom createElement option is provided.
- ab93d7b: Replaced the rule-based markdown parser with a compact table-driven parser. Parsing is 27-82% faster depending on input size and bundle size is reduced by ~25% (gzip). Improved CommonMark compliance for HTML block handling and streaming mode reliability. No API changes.
- ab93d7b: Fixed attribute casing preservation across all output adapters. The parser no longer modifies attribute names; each adapter handles its own mappings. React/Native convert to JSX props (class->className, XML namespaces via colon-to-camelCase heuristic). Solid uses
classper framework guidance. Vue passes HTML attributes directly. - ab93d7b: Improved
optimizeForStreaminghandling of incomplete inline syntax. Bold/italic/strikethrough markers, links, images, and nested badge constructs ([](url)) now stream cleanly without flashing raw markdown syntax. Incomplete images are fully suppressed instead of showing alt text.
- 9bf4bad: Fix: Jest test suites failing with "Unexpected token 'export'" when using the library with jsdom environment. The
browsercondition in the package.jsonimportsfield now correctly provides both ESM (import) and CJS (require) sub-conditions, ensuring Jest resolves to the CommonJS version of the browser entities module. - 2432f0b: Fix: preserve camelCase attribute casing for all HTML/JSX tags, not just PascalCase components. This restores expected behavior where attributes like
userIdandfirstNameare no longer lowercased touseridandfirstname.
- a97e2bf: Add
optimizeForStreamingoption to suppress incomplete syntax during streaming. When enabled, incomplete inline code, links, emphasis, and other markdown syntax is hidden cleanly as characters arrive, preventing visual artifacts and flickering. Particularly useful for AI-powered streaming applications.
- 4252da4: Fixed inconsistent spacing between list item nodes when continuation lines have indentation equal to the nested list marker. Previously, text nodes in list items were being concatenated without newlines when continuation lines matched the list's base indentation, causing missing line breaks in the rendered output.
-
13bdaf7: Fixed HTML tags with attributes spanning multiple lines being incorrectly parsed.
Previously, HTML tags with attributes on separate lines (like
<dl-custom\n data-variant='horizontalTable'\n>) would have their attributes incorrectly parsed, sometimes causing duplicate tags or missing attribute values. This fix ensures that newlines between HTML attributes are properly recognized as whitespace separators. -
13bdaf7: The
textfield in HTML AST nodes now contains cleaned inner content without opening/closing tags. UserawTextfor full raw HTML. This affects customrenderRuleimplementations that rely on thetextfield.
-
76b7f12: Fix multi-line HTML tag attribute parsing (#781)
HTML tags with attributes spanning multiple lines were not having their attributes correctly parsed into the AST. This caused custom elements with multi-line
data-*attributes to have emptyattrsobjects, and the React compiler would then duplicate the opening tag when rendering.This fix ensures:
- Attributes are correctly parsed for type 7 HTML blocks with newlines in the opening tag
- The React compiler uses the parsed
childrenarray instead of re-parsingrawTextwhen attributes are already parsed
-
7f724a6: Fix HTML block parsing for sibling elements like
<dt>/<dd>without blank lines between them.Type 6 HTML blocks (such as
<dl>,<dt>,<dd>,<table>,<tr>,<td>) were incorrectly parsed when sibling elements appeared without blank lines between them—the first element would consume all subsequent siblings as its content instead of treating them as separate elements.This fix adds nesting-aware closing tag detection that properly handles:
- Nested elements with the same tag name (e.g.,
<div><div></div></div>) - Sibling elements at the same level (e.g.,
<dt></dt><dd></dd>) - CommonMark compliance for HTML blocks that should extend to blank lines
- Nested elements with the same tag name (e.g.,
-
58010ce: Fix duplicate opening tags for HTML elements with multi-line attributes (#781)
HTML tags with attributes spanning multiple lines (like custom elements with
data-*attributes on separate lines) no longer produce duplicate opening tags in the output. This restores the expected behavior for custom HTML elements used with component overrides. -
3e25913: Fix fenced code blocks consuming nested code block openings as content.
When a fenced code block with a language (e.g.,
```markdown) encountered another code block opening with a language (e.g.,```python) inside it, the inner opening was incorrectly treated as content instead of being recognized as a new block. Now, fence lines with a language immediately following (no space between fence and language) are recognized as new block openings that implicitly close the previous block.This matches behavior of other markdown renderers like GitHub and VSCode. Lines like
``` aaa(with space before info string) remain treated as content per CommonMark spec.
-
8528325: Add CommonMark-compliant text normalization for null bytes and BOM
Per CommonMark security specification, null bytes (U+0000) are now replaced with the replacement character (U+FFFD) instead of passing through unchanged. Additionally, the Byte Order Mark (U+FEFF) is now stripped when it appears at the start of a document, as specified in the CommonMark spec.
These changes improve spec compliance and security. Most documents are unaffected due to fast-path optimization that skips processing when no special characters are present.
- 282affe: Fix lists and other markdown structures not rendering correctly when input has CRLF line endings.
- 282affe: Fix paragraph after nested list being incorrectly absorbed into the nested list item when followed by a blank line.
-
fa21868: Add Chinese (Mandarin) JSDoc documentation to all public APIs. All exported functions, types, interfaces, and components now include bilingual documentation using the
@lang zhtag for Simplified Chinese translations, improving developer experience for Chinese-speaking users. -
897c4c2: Automatic browser bundle optimization via conditional exports. Browser builds now automatically use DOM-based entity decoding (
textarea.innerHTML) instead of shipping the full ~11KB entity lookup table, reducing gzipped bundle size by ~11KB.This optimization is automatic for bundlers that support the
importsfield withbrowsercondition (Webpack 5+, Vite, esbuild, Rollup, Parcel). No configuration required.Server-side/Node.js builds retain the full O(1) entity lookup table for maximum performance.
This feature uses the
importsfield in package.json. All modern bundlers support this field (Webpack 5+, Vite, esbuild, Rollup, Parcel).
-
7605d88: Add React Server Components (RSC) support with automatic environment detection.
The
Markdowncomponent now seamlessly works in both RSC and client-side React environments without requiring 'use client' directives. The component automatically detects hook availability and adapts its behavior accordingly:- In RSC environments: Uses direct compilation without hooks for optimal server performance
- In client environments: Uses hooks and memoization for optimal client performance
MarkdownProviderandMarkdownContextgracefully become no-ops in RSC environments- Maintains identical output and API in both contexts
- Zero breaking changes for existing users
This enables better bundle splitting and SSR performance by allowing markdown rendering to happen on the server when possible.
-
d2075d2: Fix hard line breaks (two trailing spaces) inside list items not being converted to
<br/>.In v9, hard line breaks inside list items were being lost because the first line content and continuation lines were being parsed separately, causing the trailing spaces before the newline to be stripped before the hard break could be detected.
The fix ensures that for tight list items (without blank lines), simple text continuation lines are collected and concatenated with the first line content before parsing. This preserves the trailing spaces + newline sequence that triggers hard break detection.
This fix also handles hard line breaks inside blockquotes that are nested within list items, ensuring the blockquote continuation lines are properly collected together.
Fixes #766.
- 775b4bf: Expose
parserandRuleTypefrom the markdown entry point as documented.
- 7ee8a22: Ensure
renderRulealways executes before any other rendering code across all renderers. TherenderRulefunction now has full control over node rendering, including normally-skipped nodes likeref,footnote, andfrontmatter. Additionally,renderChildrenin the markdown renderer now invokesrenderRulefor recursively rendered child nodes, ensuring consistent behavior when customizing rendering logic. - 7ee8a22: HTML blocks are now always fully parsed into the AST
childrenproperty, even when marked asverbatim. Theverbatimflag now acts as a rendering hint rather than a parsing control. Default renderers still userawTextfor verbatim blocks (maintaining CommonMark compliance), butrenderRuleimplementations can now access the fully parsed AST inchildrenfor all HTML blocks. ThenoInnerParseproperty has been replaced withverbatimfor clarity. - 7ee8a22: Add
HTMLNode.rawTextfield for consistency withrawAttrs. TherawTextfield contains the raw text content for verbatim HTML blocks, whilechildrencontains the parsed AST. Thetextproperty is now deprecated and will be removed in a future major version. Both fields are set to the same value for backward compatibility.
-
c1be885: Added context providers and memoization to all major renderers for better developer experience and performance.
React:
MarkdownContext- React context for default optionsMarkdownProvider- Provider component to avoid prop-drillinguseMemo- 3-stage memoization (options, content, JSX)
React Native:
MarkdownContext- React context for default optionsMarkdownProvider- Provider component to avoid prop-drillinguseMemo- 3-stage memoization (options, content, JSX)
Vue:
MarkdownOptionsKey- InjectionKey for provide/inject patternMarkdownProvider- Provider component using Vue's providecomputed- Reactive memoization for options, content, and JSX
Benefits:
- Avoid prop-drilling - Set options once at the top level:
<MarkdownProvider options={commonOptions}> <App> <Markdown>...</Markdown> <Markdown>...</Markdown> </App> </MarkdownProvider>
- Performance optimization - Content is only parsed when it actually changes, not on every render
- Fully backwards compatible - Existing usage works unchanged, providers are optional
Example:
import { MarkdownProvider } from 'markdown-to-jsx/react' function App() { return ( <MarkdownProvider options={{ wrapper: 'article', tagfilter: true }}> <Markdown># Page 1</Markdown> <Markdown># Page 2</Markdown> {/* Both inherit options from provider */} </MarkdownProvider> ) }
-
ef8a002: Added opt-in
options.evalUnserializableExpressionsto eval function expressions and other unserializable JSX props from trusted markdown sources.⚠️ SECURITY WARNING: STRONGLY DISCOURAGED FOR USER INPUTSThis option uses
eval()and should ONLY be used with completely trusted markdown sources (e.g., your own documentation). Never enable this for user-submitted content.Usage:
// For trusted sources only const markdown = ` <Button onPress={() => alert('clicked!')} /> <ApiEndpoint url={process.env.API_URL} /> ` parser(markdown, { evalUnserializableExpressions: true }) // Components receive: // - onPress: actual function () => alert('clicked!') // - url: the value of process.env.API_URL from your environment // Without this option, these would be strings "() => alert('clicked!')" and "process.env.API_URL"
Safer alternative: Use
renderRuleto handle stringified expressions on a case-by-case basis with your own validation and allowlists.See the README for detailed security considerations and safe alternatives.
-
ef8a002: JSX prop values are now intelligently parsed instead of always being strings:
- Arrays and objects are parsed via
JSON.parse():data={[1, 2, 3]}→attrs.data = [1, 2, 3] - Booleans are parsed:
enabled={true}→attrs.enabled = true - Functions are kept as strings for security:
onClick={() => ...}→attrs.onClick = "() => ..." - Complex expressions are kept as strings:
value={someVar}→attrs.value = "someVar"
The original raw attribute string is preserved in the
rawAttrsfield.Benefits:
- Type-safe access to structured data without manual parsing
- Backwards compatible - check types before using
- Secure by default - functions remain as strings
Example:
// In markdown: <ApiTable rows={[ ['Name', 'Value'], ['foo', 'bar'], ]} /> // In your component: const ApiTable = ({ rows }) => { // rows is already an array, no JSON.parse needed! return <table>...</table> } // For backwards compatibility: const rows = typeof props.rows === 'string' ? JSON.parse(props.rows) : props.rows
Security: Functions remain as strings by default. Use
renderRulefor case-by-case handling, or see the newoptions.evalUnserializableExpressionsfeature for opt-in eval (not recommended for user inputs). - Arrays and objects are parsed via
-
ef8a002: JSX components with double-newlines (blank lines) between opening and closing tags now properly nest children instead of creating sibling nodes. This fixes incorrect AST structure for JSX/MDX content.
Before:
<Figure> <div>content</div> </Figure>
Parsed as 3 siblings:
<Figure>,<div>,</Figure>After:
Parsed as parent-child:
<Figure>contains<div>as a childThis was a bug where the parser incorrectly treated JSX components as siblings when double-newlines were present between the tags. The fix ensures proper parent-child relationships match expected JSX/MDX semantics.
- 08dfe8a: Fix regression: Tables within list items are now properly parsed.
- c5b6259: Fixed URIError when parsing HTML attributes containing the % character (e.g.,
width="100%"). The parser now gracefully handles invalid URI encodings in attribute values instead of throwing an error.
- 7ac3408: Restore angle-bracket autolinks when raw HTML parsing is disabled so
<https://...>still renders as links - 7ac3408: Improve autolink parsing: stricter angle controls, domain underscore validation, and added coverage for mailto labels and raw-HTML-disabled cases.
- a84c300: Ensure Solid renderer uses Solid's hyperscript runtime so JSX returns real elements instead of
[object Object]placeholders
- c1b0ea2: Fix unintended node-specific code from entering browser bundles by changing build target from 'node' to 'browser'
- a482de6: Add SolidJS integration with full JSX output support. Includes compiler, parser, astToJSX, and Markdown component with reactive support via signals/accessors.
- f9a8fca: Add Vue.js 3+ integration. Includes
compiler,parser,astToJSX, andMarkdowncomponent. Vue uses standard HTML attributes (class, not className) with minimal attribute mapping (only 'for' -> 'htmlFor').
- 2bb3f2b: Fix AST and options mutation bugs that could cause unexpected side effects when using memoization or reusing objects across multiple compiler calls.
-
88d4b1f: Add comprehensive React Native support with new
/nativeexport. Includes:- React Native Component Mapping: Enhanced HTML tag to React Native component mapping with semantic support for
img→Image, block elements (div,section,article,blockquote,ul,ol,li,table, etc.) →View, and inline elements →Text - Link Handling: Native link support with
onLinkPressandonLinkLongPresscallbacks, defaulting toLinking.openURL - Styling System: Complete
NativeStyleKeytype system with styles for all markdown elements and HTML semantic tags - Component Overrides: Full support for overriding default components with custom React Native components and props
- Accessibility: Built-in accessibility support with
accessibilityLabelfor images and proper link handling - Type Safety: Comprehensive TypeScript definitions with
NativeOptionsandNativeStyleKeytypes - Performance: Optimized rendering with proper React Native best practices and component lifecycle
React Native is an optional peer dependency, making this a zero-dependency addition for existing users.
- React Native Component Mapping: Enhanced HTML tag to React Native component mapping with semantic support for
-
f93214a: Fix infinite recursion when using
forceBlock: truewith empty unclosed HTML tagsWhen
React.createElement(Markdown, {options: {forceBlock: true}}, '<var>')was called with an empty unclosed tag, it would cause infinite recursion. The parser would set thetextfield to the opening tag itself (e.g.,<var>), which would then be parsed again in the rendering phase, causing recursion.This fix adds detection in
createVerbatimHTMLBlockto detect whenforceBlockis used and the text contains just the opening tag (empty unclosed tag), rendering it as an empty element to prevent recursion.
- 733f10e: Fix lazy continuation lines for list items when continuation text appears at base indentation without a blank line. Previously, continuation text was incorrectly appended inline to the list item. Now both the existing inline content and the continuation text are properly wrapped in separate paragraphs.
-
0ba757d: Add
preserveFrontmatteroption to control whether YAML frontmatter is rendered in the output. When set totrue, frontmatter is rendered as a<pre>element in HTML/JSX output. For markdown-to-markdown compilation, frontmatter is preserved by default but can be excluded withpreserveFrontmatter: false.Compiler Type Default Behavior When preserveFrontmatter: trueWhen preserveFrontmatter: falseReact/HTML ❌ Don't render frontmatter ✅ Render as <pre>element❌ Don't render frontmatter Markdown-to-Markdown ✅ Preserve frontmatter ✅ Preserve frontmatter ❌ Exclude frontmatter
- f945132: Fix lazy continuation lines for list items when continuation text appears at base indentation without a blank line before it. Previously, such lines were incorrectly parsed as separate paragraphs instead of being appended to the list item content.
- 36ef089: yWork around a bundling bug with exporting TypeScript namespaces directly. Bonus: MarkdownToJSX is now declared ambiently so you may not need to import it.
-
1ce83eb: Complete GFM+CommonMark specification compliance
- Full CommonMark compliance: All 652 official test cases now pass
- Verified GFM extensions: Tables, task lists, strikethrough, autolinks with spec compliance
- Tag filtering: Default filtering of dangerous HTML tags (
<script>,<iframe>, etc.) in both HTML string output and React JSX output - URL sanitization: Protection against
javascript:,vbscript:, and maliciousdata:URLs
Default filtering of dangerous HTML tags:
<script>,<iframe>,<object>,<embed><title>,<textarea>,<style>,<xmp><plaintext>,<noembed>,<noframes>
- Tagfilter enabled by default: Dangerous HTML tags are now escaped by default in both HTML and React output
- Inline formatting restrictions: Inline formatting delimiters (emphasis, bold, strikethrough, mark) can no longer span across newlines, per CommonMark specification
No changes necessary in most cases, but if you need to render potentially dangerous HTML tags, you can disable tag filtering:
compiler(markdown, { tagfilter: false })
Previous Behavior (Non-Compliant): The library previously allowed inline formatting to span multiple lines:
_Hello World._
This was parsed as a single
<em>element containing the newline.New Behavior (CommonMark Compliant): Per CommonMark specification, inline formatting cannot span newlines. The above example is now parsed as literal underscores:
_Hello World._
Renders as:
<p>_Hello World._</p>Impact:
- Single-line formatting still works:
*Hello World*→<em>Hello World</em> - Multi-line formatting is now rejected:
*Hello\nWorld*→ literal asterisks - Affects all inline formatting:
*emphasis*,**bold**,~~strikethrough~~,==mark==
Migration Options: If you have markdown with multi-line inline formatting:
- Keep formatting on a single line:
*Hello World* - Use HTML tags:
<em>Hello\nWorld</em> - Accept that multi-line formatting renders as literal delimiters
Examples:
# Works (single line) _This is emphasized_ **This is bold** # No longer works (multi-line) _This is emphasized_ **This is bold** # Renders as literal delimiters: <p>_This is emphasized_</p> <p>**This is bold**</p> # Workaround: Use HTML tags <em>This is emphasized</em> <strong>This is bold</strong>
-
1ce83eb: Remove internal type definitions and rename
MarkdownToJSX.RuleOutputtoMarkdownToJSX.ASTRenderThis change removes internal type definitions from the
MarkdownToJSXnamespace:- Removed
NestedParsertype - Removed
Parsertype - Removed
Ruletype - Removed
Rulestype - Renamed
RuleOutputtoASTRenderfor clarity
Breaking changes:
If you are using the internal types directly:
- Code referencing
MarkdownToJSX.NestedParser,MarkdownToJSX.Parser,MarkdownToJSX.Rule, orMarkdownToJSX.Ruleswill need to be updated - The
renderRuleoption inMarkdownToJSX.Optionsnow usesASTRenderinstead ofRuleOutputfor therenderChildrenparameter type HTMLNode.childrentype changed fromReturnType<MarkdownToJSX.NestedParser>toASTNode[](semantically equivalent, but requires updates if using the old type)
- Removed
-
1ce83eb: Remove
options.namedCodesToUnicode. The library now encodes the full HTML entity list by default per CommonMark specification requirements.Migration:
If you were using
options.namedCodesToUnicodeto add custom entity mappings, you can remove the option entirely as all specified HTML entities are now supported automatically. -
1ce83eb: Drop support for React versions less than 16
- Update peer dependency requirement from
>= 0.14.0to>= 16.0.0 - Remove legacy code that wrapped string children in
<span>elements for React < 16 compatibility - Directly return single children and null without wrapper elements
- Update peer dependency requirement from
-
1ce83eb: Upgrade to React 19 types
- Update to
@types/react@^19.2.2and@types/react-dom@^19.2.2 - Use
React.JSX.*namespace instead ofJSX.*for React 19 compatibility
- Update to
-
1ce83eb: Adopt CommonMark-compliant class naming for code blocks
Code blocks now use both the
language-andlang-class name prefixes to match the CommonMark specification for compatibility.```js console.log('hello') ```
Generated:
<pre><code class="lang-js">console.log('hello');</code></pre>
```js console.log('hello') ```
Generated:
<pre><code class="language-js lang-js">console.log('hello');</code></pre>
-
1ce83eb: Separate JSX renderer from compiler and add new entry points
-
New
parserfunction: Low-level API that returns AST nodes. Exported from main entry point and all sub-entry points.import { parser } from 'markdown-to-jsx' const source = '# Hello world' const ast = parser(source)
-
New
/reactentry point: React-specific entry point that exports compiler, Markdown component, parser, types, and utils.import Markdown, { astToJSX, compiler, parser } from 'markdown-to-jsx/react' const source = '# Hello world' const oneStepJSX = compiler(source) const twoStepJSX = astToJSX(parser(source)) function App() { return <Markdown children={source} /> // or // return <Markdown>{source}</Markdown> }
-
New
/htmlentry point: HTML string output entry point that exports html function, parser, types, and utils.import { astToHTML, compiler, parser } from 'markdown-to-jsx/html' const source = '# Hello world' const oneStepHTML = compiler(source) const twoStepHTML = astToHTML(parser(source))
-
New
/markdownentry point: Useful for situations where editing of the markdown is desired without resorting to gnarly regex-based parsing.import { astToMarkdown, compiler, parser } from 'markdown-to-jsx/markdown' const source = '# Hello world' const oneStepMarkdown = compiler(source) const twoStepMarkdown = astToMarkdown(parser(source))
React code in the main entry point
markdown-to-jsxis deprecated and will be removed in a future major release. In v10, the main entry point will only export the parser function, the types, and any exposed utility functions.- For React-specific usage, switch imports to
markdown-to-jsx/react - For HTML output, use
markdown-to-jsx/htmlentry point - Use
parser()for direct acces to AST
-
-
450d2bb: Added
astoption to compiler to expose the parsed AST directly. Whenast: true, the compiler returns the AST structure (ASTNode[]) instead of rendered JSX.Breaking Changes:
- The internal type
ParserResulthas been renamed toASTNodefor clarity. If you were accessing this type directly (e.g., via module augmentation or type manipulation), you'll need to update references fromMarkdownToJSX.ParserResulttoMarkdownToJSX.ASTNode.
First time the AST is accessible to users! This enables:
- AST manipulation and transformation before rendering
- Custom rendering logic without parsing
- Caching parsed AST for performance
- Linting or validation of markdown structure
Usage:
import { compiler } from 'markdown-to-jsx' import type { MarkdownToJSX } from 'markdown-to-jsx' // Get the AST structure const ast: MarkdownToJSX.ASTNode[] = compiler('# Hello world', { ast: true, }) // Inspect/modify AST console.log(ast) // Array of parsed nodes // Render AST to JSX using createRenderer (not implemented yet)
The AST format is
MarkdownToJSX.ASTNode[]. When footnotes are present, the returned value will be an object withastandfootnotesproperties instead of just the AST array. - The internal type
-
3fa0c22: Refactored inline formatting parsing to eliminate ReDoS vulnerabilities and improve performance. The previous regex-based approach was susceptible to exponential backtracking on certain inputs and had several edge case bugs with nested formatting, escaped characters, and formatting inside links. The new implementation uses a custom iterative scanner that runs in O(n) time and is immune to ReDoS attacks.
This also consolidates multiple formatting rule types into a single unified rule with boolean flags, reducing code duplication and bundle size. Performance has improved measurably on simple markdown strings:
Breaking Changes:
The following
RuleTypeenum values have been removed and consolidated into a singleRuleType.textFormatted:RuleType.textBoldedRuleType.textEmphasizedRuleType.textMarkedRuleType.textStrikethroughed
If you're using these rule types directly (e.g., for custom AST processing or overrides), you'll need to update your code to check for
RuleType.textFormattedinstead and inspect the node's boolean flags (bold,italic,marked,strikethrough) to determine which formatting is applied.
-
a421067: fix: overhaul HTML block parsing to eliminate exponential backtracking
Replaced the complex nested regex
HTML_BLOCK_ELEMENT_Rwith an efficient iterative depth-counting algorithm that maintains O(n) complexity. The new implementation uses stateful regex matching withlastIndexto avoid exponential backtracking on nested HTML elements while preserving all existing functionality.Performance improvements:
- Eliminates O(2^n) worst-case exponential backtracking
- Linear O(n) time complexity regardless of nesting depth
-
e6b1e14: Fix renderer crash on extremely deeply nested markdown content
Previously, rendering markdown with extremely deeply nested content (e.g., thousands of nested bold markers like
****************...text...****************) would cause a stack overflow crash. The renderer now gracefully handles such edge cases by falling back to plain text rendering instead of crashing.Technical details:
- Added render depth tracking to prevent stack overflow
- Graceful fallback at 2500 levels of nesting (way beyond normal usage)
- Try/catch safety net as additional protection for unexpected errors
- Zero performance impact during normal operation
- Prevents crashes while maintaining O(n) parsing complexity
This fix ensures stability even with adversarial or malformed inputs while having no impact on normal markdown documents.
-
fe95c02: Remove unnecessary wrapper when footnotes are present.
-
acc11ad: Fix null children crashing app in production
When
nullis passed as children to the<Markdown>component, it would previously crash the app in production. This fix handles this case by converting it to empty string.Before this fix, the following code would crash in production:
<Markdown>{null}</Markdown>
After this fix, this case is handled gracefully and renders nothing.
-
7e487bd: Fix the issue where YAML frontmatter in code blocks doesn't render properly.
This is done by lowering the parsing priority of Setext headings to match ATX headings; both are now prioritized lower than code blocks.
- 8e4c270: Mark react as an optional peer dependency as when passing createElement, you don't need React
- 73d4398: Cut down on unnecessary matching operations by improving qualifiers. Also improved the matching speed of paragraphs, which led to a roughly 2x boost in throughput for larger input strings.
- da003e4: Fix exponential backtracking issue for unpaired inline delimiter sequences.
- 4351ef5: Adjust text parsing to not split on double spaces unless followed by a newline.
- 4351ef5: Special case detection of :shortcode: so the text processor doesn't break it into chunks, enables shortcode replacement via renderRule.
-
4a692dc: Fixes the issue where link text containing multiple nested brackets is not parsed correctly.
Before:
[title[bracket1][bracket2]](url)fails to parse as a link After:[title[bracket1][bracket2]](url)correctly parses as a link
- bf9dd3d: Unescape content intended for JSX attributes.
- 95dda3e: Avoid creating unnecessary paragraphs inside of HTML.
- 95dda3e: Fix HTML parser to avoid processing the inside of
<pre>blocks.
- db378c7: Implement early short-circuit for rules to avoid expensive throwaway work.
- db378c7: Simpler fix that preserves existing performance.
- db378c7: Various low-hanging minor performance enhancements by doing less work.
- db378c7: Improve compression by inlining static RuleType entries when used in the codebase.
-
89c87e5: Handle spaces in text as a stop token to improve processing, also adapt paragraph detection to exclude non-atx compliant headings if that option is enabled.
Fixes #680
- 654855b: Sanitize more attributes by default to help address XSS vectors.
- 7639c08: Improve splitting of style attributes.
- 0ddaabb: Remove unescaping of content inside fenced code blocks.
- 07b4280: Better handle exotic backtick scenarios for inline code blocks.
- 0dad192: Fix consecutive marked text.
- adc08c7: Further optimize the plain text splitting regex.
- c8bc5f3: Remove redundant detectors when processing paragraphs.
- d96a8d8: Replace some regexes with optimized functions to avoid polynomial time scenarios. Also fixes compatibility issues in some older browsers with the
trimEndAPI. - 7be3d77: Optimize regexes and parsing to do less work.
- cf7693c: Rework inline code syntax handling, handle escaped characters in code blocks correctly so they render without the backslash.
- 8026103: Handle paragraph splitting better, fixes #641.
- 1ea00bb: Adjust table row parsing to better handle inline syntaxes and improve performance.
- 52a727c: Use
ReactNodeinstead ofReactChildfor React 19 compatibility - 4fa87d8: Bump ws from 8.11.0 to 8.18.0
- 9d42449: Factor out unnecessary element cloning.
- 8920038: Remove use of explicit React.createElement.
-
20777bf: Add support for GFM alert-style blockquotes.
> [!Note] > This is a note-flavored alert blockquote. The "Note" text is injected as a `<header>` by > default and the blockquote can be styled via the injected class `markdown-alert-note` > for example.
- 5d7900b: Adjust type signature for
<Markdown>component to allow for easier composition. - 918b44b: Use newer
React.JSX.*namespace instead ofJSX.*for React 19 compatibility. - 91a5948: Arbitrary HTML no longer punches out pipes when parsing rows. If you absolutely need a pipe character that isn't a table separator, either escape it or enclose it in backticks to trigger inline code handling.
- 23caecb: Drop encountered
refattributes when processing inline HTML, React doesn't handle it well.
- 0274445: Fix false detection of tables in some scenarios.
- 69f815e: Handle
classattribute from arbitrary HTML properly to avoid React warnings. - 857809a: Fenced code blocks are now tolerant to a missing closing sequence; this improves use in LLM scenarios where the code block markdown is being streamed into the editor in chunks.
- 87d8bd3: Handle
classattribute from arbitrary HTML properly to avoid React warnings.
-
2281a4d: Add
options.disableAutoLinkto customize bare URL handling behavior.By default, bare URLs in the markdown document will be converted into an anchor tag. This behavior can be disabled if desired.
<Markdown options={{ disableAutoLink: true }}> The URL https://quantizor.dev will not be rendered as an anchor tag. </Markdown> // or compiler( 'The URL https://quantizor.dev will not be rendered as an anchor tag.', { disableAutoLink: true } ) // renders: <span> The URL https://quantizor.dev will not be rendered as an anchor tag. </span>
- fb3d716: Simplify handling of fallback scenario if a link reference is missing its corresponding footnote.
- b16f668: Fix issue with lookback cache resulting in false detection of lists inside lists in some scenarios
- 58b96d3: fix: handle empty HTML tags more consistently #597
-
62a16f3: Allow modifying HTML attribute sanitization when
options.sanitizeris passed by the composer.By default a lightweight URL sanitizer function is provided to avoid common attack vectors that might be placed into the
hrefof 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 calledsanitizer.This can be overridden and replaced with a custom sanitizer if desired via
options.sanitizer:// sanitizer in this situation would receive: // ('javascript:alert("foo")', 'a', 'href') ;<Markdown options={{ sanitizer: (value, tag, attribute) => value }}> {`[foo](javascript:alert("foo"))`} </Markdown> // or compiler('[foo](javascript:alert("foo"))', { sanitizer: (value, tag, attribute) => value, })
- 553a175: Replace RuleType enum with an object
- 7603248: Fix parsing isolation of individual table cells.
- f9328cc: Improved block html detection regex to handle certain edge cases that cause extreme slowness. Thank you @devbrains-com for the basis for this fix 🤝
-
a9e5276: Browsers assign element with
idto the global scope using the value as the variable name. E.g.:<h1 id="analytics">can be referenced viawindow.analytics. This can be a problem when a name conflict happens. For instance, pages that expectanalytics.push()to be a function will stop working if the an element with anidofanalyticsexists in the page.In this change, we export the
slugifyfunction so that users can easily augment it. This can be used to avoid variable name conflicts by giving the element a differentid.import { slugify } from 'markdown-to-jsx'; options={{ slugify: str => { let result = slugify(str) return result ? '-' + str : result; } }}
-
f5a0079: fix: double newline between consecutive blockquote syntax creates separate blockquotes
Previously, for consecutive blockquotes they were rendered as one:
Input
> Block A.1 > Block A.2 > Block B.1
Output
<blockquote> <p>Block A.1</p> <p>Block A.2</p> <p>Block.B.1</p> </blockquote>
This is not compliant with the GFM spec which states that consecutive blocks should be created if there is a blank line between them.
- 8eb8a13: Handle newlines inside of HTML tags themselves (not just nested children.)
- c72dd31: Default
childrento an empty string if no content is passed. - 4f752c8: Fix handling of deeply-nested HTML in some scenarios.
- 1486aa4: Handle extra brackets in links, thanks @zegl!
- 1486aa4: Allow a newline to appear within inline formatting like bold, emphasis, etc, thanks @austingreco!
- 1486aa4: Starting using changesets
- fd35402: Fix HTML block regex for custom component scenarios where a nested component shares the same prefix as the parent, e.g. Accordion vs AccordionItem.
- 1486aa4: Fix support for multi-line footnotes, thanks @zegl!