Releases: quantizor/markdown-to-jsx
v9.5.0
Minor Changes
-
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.
Patch Changes
-
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.
v9.4.2
v9.4.1
Patch Changes
- 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.
v9.4.0
Minor Changes
-
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
Patch Changes
-
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.
v9.3.5
v9.3.4
v9.3.3
v9.3.2
v9.3.1
v9.3.0
Minor Changes
- 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').
Patch Changes
- 2bb3f2b: Fix AST and options mutation bugs that could cause unexpected side effects when using memoization or reusing objects across multiple compiler calls.