Gathering detailed insights and metrics for markdown-to-jsx
Gathering detailed insights and metrics for markdown-to-jsx
Gathering detailed insights and metrics for markdown-to-jsx
Gathering detailed insights and metrics for markdown-to-jsx
🏭 The most lightweight, customizable React markdown component.
npm install markdown-to-jsx
Typescript
Module System
Min. Node Version
Node Version
NPM Version
95.1
Supply Chain
99.1
Quality
94.3
Maintenance
100
Vulnerability
100
License
TypeScript (99.33%)
JavaScript (0.67%)
Total Downloads
942,400,977
Last Day
105,370
Last Week
4,328,474
Last Month
18,682,378
Last Year
252,115,355
2,057 Stars
644 Commits
178 Forks
10 Watching
4 Branches
53 Contributors
Latest Version
7.7.2
Package Id
markdown-to-jsx@7.7.2
Unpacked Size
478.16 kB
Size
137.23 kB
File Count
13
NPM Version
10.8.2
Node Version
20.18.1
Publised On
18 Dec 2024
Cumulative downloads
Total Downloads
Last day
-21.6%
105,370
Compared to previous day
Last week
-8.1%
4,328,474
Compared to previous week
Last month
-20.5%
18,682,378
Compared to previous month
Last year
49.5%
252,115,355
Compared to previous year
1
31
markdown-to-jsx
The most lightweight, customizable React markdown component.
markdown-to-jsx
uses a heavily-modified fork of simple-markdown as its parsing engine and extends it in a number of ways to make your life easier. Notably, this package offers the following additional benefits:
Arbitrary HTML is supported and parsed into the appropriate JSX representation
without dangerouslySetInnerHTML
Any HTML tags rendered by the compiler and/or <Markdown>
component can be overridden to include additional
props or even a different HTML representation entirely.
GFM task list support.
Fenced code blocks with highlight.js support; see Syntax highlighting for instructions on setting up highlight.js.
All this clocks in at around 6 kB gzipped, which is a fraction of the size of most other React markdown components.
Requires React >= 0.14.
Install markdown-to-jsx
with your favorite package manager.
1npm i markdown-to-jsx
markdown-to-jsx
exports a React component by default for easy JSX composition:
ES6-style usage*:
1import Markdown from 'markdown-to-jsx' 2import React from 'react' 3import { render } from 'react-dom' 4 5render(<Markdown># Hello world!</Markdown>, document.body) 6 7/* 8 renders: 9 10 <h1>Hello world!</h1> 11 */
* NOTE: JSX does not natively preserve newlines in multiline text. In general, writing markdown directly in JSX is discouraged and it's a better idea to keep your content in separate .md files and require them, perhaps using webpack's raw-loader.
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":
1Hello. _Beautiful_ day isn't it?
But this string would be considered "block" due to the existence of a header tag, which is a block-level HTML element:
1# Whaddup?
However, if you really want all input strings to be treated as "block" layout, simply pass options.forceBlock = true
like this:
1;<Markdown options={{ forceBlock: true }}>Hello there old chap!</Markdown> 2 3// or 4 5compiler('Hello there old chap!', { forceBlock: true }) 6 7// renders 8;<p>Hello there old chap!</p>
The inverse is also available by passing options.forceInline = true
:
1;<Markdown options={{ forceInline: true }}># You got it babe!</Markdown> 2 3// or 4 5compiler('# You got it babe!', { forceInline: true }) 6 7// renders 8;<span># You got it babe!</span>
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.
1const str = '# Heck Yes\n\nThis is great!' 2 3<Markdown options={{ wrapper: 'article' }}> 4 {str} 5</Markdown>; 6 7// or 8 9compiler(str, { wrapper: 'article' }); 10 11// renders 12 13<article> 14 <h1>Heck Yes</h1> 15 <p>This is great!</p> 16</article>
To get an array of children back without a wrapper, set wrapper
to null
. This is particularly useful when using compiler(…)
directly.
1compiler('One\n\nTwo\n\nThree', { wrapper: null }) 2 3// returns 4;[<p>One</p>, <p>Two</p>, <p>Three</p>]
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.
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
.
1// Using `forceWrapper` with a single, inline child… 2<Markdown options={{ wrapper: 'aside', forceWrapper: true }}> 3 Mumble, mumble… 4</Markdown> 5 6// renders 7 8<aside>Mumble, mumble…</aside>
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.
1import Markdown from 'markdown-to-jsx' 2import React from 'react' 3import { render } from 'react-dom' 4 5// surprise, it's a div instead! 6const MyParagraph = ({ children, ...props }) => <div {...props}>{children}</div> 7 8render( 9 <Markdown 10 options={{ 11 overrides: { 12 h1: { 13 component: MyParagraph, 14 props: { 15 className: 'foo', 16 }, 17 }, 18 }, 19 }} 20 > 21 # Hello world! 22 </Markdown>, 23 document.body 24) 25 26/* 27 renders: 28 29 <div class="foo"> 30 Hello World 31 </div> 32 */
If you only wish to provide a component override, a simplified syntax is available:
1{ 2 overrides: { 3 h1: MyParagraph, 4 }, 5}
Depending on the type of element, there are some props that must be preserved to ensure the markdown is converted as intended. They are:
a
: title
, href
img
: title
, alt
, src
input[type="checkbox"]
: checked
, readonly
(specifically, the one rendered by a GFM task list)ol
: start
td
: style
th
: style
Any conflicts between passed props
and the specific properties above will be resolved in favor of markdown-to-jsx
's code.
Some element mappings are a bit different from other libraries, in particular:
span
: Used for inline text.code
: Used for inline code.pre > code
: Code blocks are a code
element with a pre
as its direct ancestor.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.
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:
1import Markdown from 'markdown-to-jsx' 2import React from 'react' 3import { render } from 'react-dom' 4 5import DatePicker from './date-picker' 6 7const md = ` 8# DatePicker 9 10The DatePicker works by supplying a date to bias towards, 11as well as a default timezone. 12 13<DatePicker biasTowardDateTime="2017-12-05T07:39:36.091Z" timezone="UTC+5" /> 14` 15 16render( 17 <Markdown 18 children={md} 19 options={{ 20 overrides: { 21 DatePicker: { 22 component: DatePicker, 23 }, 24 }, 25 }} 26 />, 27 document.body 28)
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.
In the following case, DatePicker
could simply run parseInt()
on the passed startTime
for example:
1import Markdown from 'markdown-to-jsx' 2import React from 'react' 3import { render } from 'react-dom' 4 5import DatePicker from './date-picker' 6 7const md = ` 8# DatePicker 9 10The DatePicker works by supplying a date to bias towards, 11as well as a default timezone. 12 13<DatePicker 14 biasTowardDateTime="2017-12-05T07:39:36.091Z" 15 timezone="UTC+5" 16 startTime={1514579720511} 17/> 18` 19 20render( 21 <Markdown 22 children={md} 23 options={{ 24 overrides: { 25 DatePicker: { 26 component: DatePicker, 27 }, 28 }, 29 }} 30 />, 31 document.body 32)
Another possibility is to use something like recompose's withProps()
HOC to create various pregenerated scenarios and then reference them by name in the markdown:
1import Markdown from 'markdown-to-jsx' 2import React from 'react' 3import { render } from 'react-dom' 4import withProps from 'recompose/withProps' 5 6import DatePicker from './date-picker' 7 8const DecemberDatePicker = withProps({ 9 range: { 10 start: new Date('2017-12-01'), 11 end: new Date('2017-12-31'), 12 }, 13 timezone: 'UTC+5', 14})(DatePicker) 15 16const md = ` 17# DatePicker 18 19The DatePicker works by supplying a date to bias towards, 20as well as a default timezone. 21 22<DatePicker 23 biasTowardDateTime="2017-12-05T07:39:36.091Z" 24 timezone="UTC+5" 25 startTime={1514579720511} 26/> 27 28Here's an example of a DatePicker pre-set to only the month of December: 29 30<DecemberDatePicker /> 31` 32 33render( 34 <Markdown 35 children={md} 36 options={{ 37 overrides: { 38 DatePicker, 39 DecemberDatePicker, 40 }, 41 }} 42 />, 43 document.body 44)
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]
:
1import Markdown from 'markdown-to-jsx' 2import React from 'react' 3import { render } from 'react-dom' 4 5const md = ` 6# Hello world 7` 8 9render( 10 <Markdown 11 children={md} 12 options={{ 13 createElement(type, props, children) { 14 return ( 15 <div className="parent"> 16 {React.createElement(type, props, children)} 17 </div> 18 ) 19 }, 20 }} 21 />, 22 document.body 23)
Forces the compiler to have space between hash sign #
and the header text which is explicitly stated in the most of the markdown specs.
The opening sequence of
#
characters must be followed by a space or by the end of line.
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:
1import Markdown, { RuleType } from 'markdown-to-jsx' 2import TeX from '@matejmazur/react-katex' 3 4const exampleContent = 5 'Some important formula:\n\n```latex\nmathbb{N} = { a in mathbb{Z} : a > 0 }\n```\n' 6 7function App() { 8 return ( 9 <Markdown 10 children={exampleContent} 11 options={{ 12 renderRule(next, node, renderChildren, state) { 13 if (node.type === RuleType.codeBlock && node.lang === 'latex') { 14 return ( 15 <TeX as="div" key={state.key}>{String.raw`${node.text}`}</TeX> 16 ) 17 } 18 19 return next() 20 }, 21 }} 22 /> 23 ) 24}
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
.
This can be overridden and replaced with a custom sanitizer if desired via options.sanitizer
:
1// sanitizer in this situation would receive: 2// ('javascript:alert("foo")', 'a', 'href') 3 4;<Markdown options={{ sanitizer: (value, tag, attribute) => value }}> 5 {`[foo](javascript:alert("foo"))`} 6</Markdown> 7 8// or 9 10compiler('[foo](javascript:alert("foo"))', { 11 sanitizer: (value, tag, attribute) => value, 12})
By default, a lightweight deburring function 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:
1<Markdown options={{ slugify: str => str }}># 中文</Markdown> 2 3// or 4 5compiler('# 中文', { slugify: str => str }) 6 7// renders: 8<h1 id="中文">中文</h1>
The original function is available as a library export called slugify
.
By default only a couple of named html codes are converted to unicode characters:
&
(&
)'
('
)>
(>
)<
(<
)
(
)"
("
)Some projects require to extend this map of named codes and unicode characters. To customize this list with additional html codes pass the option namedCodesToUnicode as object with the code names needed as in the example below:
1<Markdown options={{ namedCodesToUnicode: { 2 le: '\u2264', 3 ge: '\u2265', 4 '#39': '\u0027', 5} }}>This text is ≤ than this text.</Markdown>; 6 7// or 8 9compiler('This text is ≤ than this text.', namedCodesToUnicode: { 10 le: '\u2264', 11 ge: '\u2265', 12 '#39': '\u0027', 13}); 14 15// renders: 16 17<p>This text is ≤ than this text.</p>
By default, bare URLs in the markdown document will be converted into an anchor tag. This behavior can be disabled if desired.
1<Markdown options={{ disableAutoLink: true }}> 2 The URL https://quantizor.dev will not be rendered as an anchor tag. 3</Markdown> 4 5// or 6 7compiler( 8 'The URL https://quantizor.dev will not be rendered as an anchor tag.', 9 { disableAutoLink: true } 10) 11 12// renders: 13 14<span> 15 The URL https://quantizor.dev will not be rendered as an anchor tag. 16</span>
By default, raw HTML is parsed to JSX. This behavior can be disabled if desired.
1<Markdown options={{ disableParsingRawHTML: true }}> 2 This text has <span>html</span> in it but it won't be rendered 3</Markdown>; 4 5// or 6 7compiler('This text has <span>html</span> in it but it won't be rendered', { disableParsingRawHTML: true }); 8 9// renders: 10 11<span>This text has <span>html</span> in it but it won't be rendered</span>
When using fenced code blocks 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
:
1import { Markdown, RuleType } from 'markdown-to-jsx' 2 3const mdContainingFencedCodeBlock = '```js\nconsole.log("Hello world!");\n```\n' 4 5function App() { 6 return ( 7 <Markdown 8 children={mdContainingFencedCodeBlock} 9 options={{ 10 overrides: { 11 code: SyntaxHighlightedCode, 12 }, 13 }} 14 /> 15 ) 16} 17 18/** 19 * Add the following tags to your page <head> to automatically load hljs and styles: 20 21 <link 22 rel="stylesheet" 23 href="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/styles/nord.min.css" 24 /> 25 26 * NOTE: for best performance, load individual languages you need instead of all 27 of them. See their docs for more info: https://highlightjs.org/ 28 29 <script 30 crossorigin 31 src="https://unpkg.com/@highlightjs/cdn-assets@11.9.0/highlight.min.js" 32 ></script> 33 */ 34 35function SyntaxHighlightedCode(props) { 36 const ref = (React.useRef < HTMLElement) | (null > null) 37 38 React.useEffect(() => { 39 if (ref.current && props.className?.includes('lang-') && window.hljs) { 40 window.hljs.highlightElement(ref.current) 41 42 // hljs won't reprocess the element unless this attribute is removed 43 ref.current.removeAttribute('data-highlighted') 44 } 45 }, [props.className, props.children]) 46 47 return <code {...props} ref={ref} /> 48}
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.
Here are instructions for some of the popular bundlers:
Everything will work just fine! Simply Alias react
to preact/compat
like you probably already are doing.
Using the options.overrides
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.
1const Table: React.FC< 2 JSX.IntrinsicElements['table'] & { 3 columns: string 4 dataSource: string 5 } 6> = ({ columns, dataSource, ...props }) => { 7 const parsedColumns = JSON.parse(columns) 8 const parsedData = JSON.parse(dataSource) 9 10 return ( 11 <div {...props}> 12 <h1>Columns</h1> 13 {parsedColumns.map(column => ( 14 <span key={column.key}>{column.title}</span> 15 ))} 16 17 <h2>Data</h2> 18 {parsedData.map(datum => ( 19 <span key={datum.key}>{datum.Month}</span> 20 ))} 21 </div> 22 ) 23} 24 25/** 26 * Example HTML in markdown: 27 * 28 * <Table 29 * columns={[{ title: 'Month', dataIndex: 'Month', key: 'Month' }]} 30 * dataSource={[ 31 * { 32 * Month: '2024-09-01', 33 * 'Forecasted Revenue': '$3,137,678.85', 34 * 'Forecasted Expenses': '$2,036,660.28', 35 * key: 0, 36 * }, 37 * ]} 38 * /> 39 */
People usually write HTML like this:
1<div>Hey, how are you?</div>
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.
To get around this, markdown-to-jsx
left-trims approximately as much whitespace as the first line inside the HTML block. So for example:
1<div># Hello How are you?</div>
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.
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. 🙃
⛔️
1<div> 2 var some = code(); 3</div>
✅
1<div> 2```js 3var some = code(); 4``\` 5</div>
If desired, the compiler function is a "named" export on the markdown-to-jsx
module:
1import { compiler } from 'markdown-to-jsx' 2import React from 'react' 3import { render } from 'react-dom' 4 5render(compiler('# Hello world!'), document.body) 6 7/* 8 renders: 9 10 <h1>Hello world!</h1> 11 */
It accepts the following arguments:
1compiler(markdown: string, options: object?)
See Github Releases.
Like this library? It's developed entirely on a volunteer basis; chip in a few bucks if you can via the Sponsor link!
MIT
No vulnerabilities found.
No security vulnerabilities found.