diff --git a/index.js b/index.js
index 1c36187..ff903ce 100644
--- a/index.js
+++ b/index.js
@@ -1,7 +1,9 @@
/**
- * @typedef {import('hast-util-to-jsx-runtime').Components} Components
* @typedef {import('hast-util-to-jsx-runtime').ExtraProps} ExtraProps
+ * @typedef {import('./lib/index.js').AllowElement} AllowElement
+ * @typedef {import('./lib/index.js').Components} Components
* @typedef {import('./lib/index.js').Options} Options
+ * @typedef {import('./lib/index.js').UrlTransform} UrlTransform
*/
export {Markdown as default, defaultUrlTransform} from './lib/index.js'
diff --git a/lib/index.js b/lib/index.js
index 2ebf91f..335d849 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -7,7 +7,7 @@
* @typedef {import('hast').Nodes} Nodes
* @typedef {import('hast').Parents} Parents
* @typedef {import('hast').Root} Root
- * @typedef {import('hast-util-to-jsx-runtime').Components} Components
+ * @typedef {import('hast-util-to-jsx-runtime').Components} JsxRuntimeComponents
* @typedef {import('remark-rehype').Options} RemarkRehypeOptions
* @typedef {import('unist-util-visit').BuildVisitor} Visitor
* @typedef {import('unified').PluggableList} PluggableList
@@ -15,7 +15,7 @@
/**
* @callback AllowElement
- * Decide if `element` should be allowed.
+ * Filter elements.
* @param {Readonly} element
* Element to check.
* @param {number} index
@@ -25,6 +25,9 @@
* @returns {boolean | null | undefined}
* Whether to allow `element` (default: `false`).
*
+ * @typedef {Partial} Components
+ * Map tag names to components.
+ *
* @typedef Deprecation
* Deprecation.
* @property {string} from
@@ -37,20 +40,20 @@
* @typedef Options
* Configuration.
* @property {AllowElement | null | undefined} [allowElement]
- * Function called to check if an element is allowed (when truthy) or not,
- * `allowedElements` or `disallowedElements` is used first!
+ * Filter elements (optional);
+ * `allowedElements` / `disallowedElements` is used first.
* @property {ReadonlyArray | null | undefined} [allowedElements]
- * Tag names to allow (cannot combine w/ `disallowedElements`), all tag names
- * are allowed by default.
+ * Tag names to allow (default: all tag names);
+ * cannot combine w/ `disallowedElements`.
* @property {string | null | undefined} [children]
- * Markdown to parse.
+ * Markdown.
* @property {string | null | undefined} [className]
- * Wrap the markdown in a `div` with this class name.
- * @property {Partial | null | undefined} [components]
- * Map tag names to React components.
+ * Wrap in a `div` with this class name.
+ * @property {Components | null | undefined} [components]
+ * Map tag names to components.
* @property {ReadonlyArray | null | undefined} [disallowedElements]
- * Tag names to disallow (cannot combine w/ `allowedElements`), all tag names
- * are allowed by default.
+ * Tag names to disallow (default: `[]`);
+ * cannot combine w/ `allowedElements`.
* @property {PluggableList | null | undefined} [rehypePlugins]
* List of rehype plugins to use.
* @property {PluggableList | null | undefined} [remarkPlugins]
@@ -60,16 +63,16 @@
* @property {boolean | null | undefined} [skipHtml=false]
* Ignore HTML in markdown completely (default: `false`).
* @property {boolean | null | undefined} [unwrapDisallowed=false]
- * Extract (unwrap) the children of not allowed elements (default: `false`);
- * normally when say `strong` is disallowed, it and it’s children are dropped,
+ * Extract (unwrap) what’s in disallowed elements (default: `false`);
+ * normally when say `strong` is not allowed, it and it’s children are dropped,
* with `unwrapDisallowed` the element itself is replaced by its children.
* @property {UrlTransform | null | undefined} [urlTransform]
* Change URLs (default: `defaultUrlTransform`)
*
* @callback UrlTransform
- * Transform URLs.
+ * Transform all URLs.
* @param {string} url
- * URL to transform.
+ * URL.
* @param {string} key
* Property name (example: `'href'`).
* @param {Readonly} node
@@ -139,8 +142,7 @@ const deprecations = [
* Component to render markdown.
*
* @param {Readonly} options
- * Configuration (required).
- * Note: React types require that props are passed.
+ * Props.
* @returns {JSX.Element}
* React element.
*/
diff --git a/package.json b/package.json
index 0953edc..188d85b 100644
--- a/package.json
+++ b/package.json
@@ -82,6 +82,7 @@
"hast-util-to-jsx-runtime": "^2.0.0",
"html-url-attributes": "^3.0.0",
"mdast-util-to-hast": "^13.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.0.0",
"unified": "^11.0.0",
diff --git a/readme.md b/readme.md
index 0d37553..7df6a92 100644
--- a/readme.md
+++ b/readme.md
@@ -18,13 +18,13 @@ React component to render markdown.
## Feature highlights
-* [x] **[safe][security] by default**
+* [x] **[safe][section-security] by default**
(no `dangerouslySetInnerHTML` or XSS attacks)
-* [x] **[components][]**
+* [x] **[components][section-components]**
(pass your own component to use instead of `` for `## hi`)
-* [x] **[plugins][]**
+* [x] **[plugins][section-plugins]**
(many plugins you can pick and choose from)
-* [x] **[compliant][syntax]**
+* [x] **[compliant][section-syntax]**
(100% to CommonMark, 100% to GFM with a plugin)
## Contents
@@ -34,8 +34,13 @@ React component to render markdown.
* [Install](#install)
* [Use](#use)
* [API](#api)
- * [`props`](#props)
- * [`uriTransformer`](#uritransformer)
+ * [`Markdown`](#markdown)
+ * [`defaultUrlTransform(url)`](#defaulturltransformurl)
+ * [`AllowElement`](#allowelement)
+ * [`Components`](#components)
+ * [`ExtraProps`](#extraprops)
+ * [`Options`](#options)
+ * [`UrlTransform`](#urltransform)
* [Examples](#examples)
* [Use a plugin](#use-a-plugin)
* [Use a plugin with options](#use-a-plugin-with-options)
@@ -57,25 +62,23 @@ React component to render markdown.
This package is a [React][] component that can be given a string of markdown
that it’ll safely render to React elements.
-You can pass plugins to change how markdown is transformed to React elements and
-pass components that will be used instead of normal HTML elements.
+You can pass plugins to change how markdown is transformed and pass components
+that will be used instead of normal HTML elements.
-* to learn markdown, see this [cheatsheet and tutorial][cheat]
+* to learn markdown, see this [cheatsheet and tutorial][commonmark-help]
* to try out `react-markdown`, see [our demo][demo]
## When should I use this?
There are other ways to use markdown in React out there so why use this one?
-The two main reasons are that they often rely on `dangerouslySetInnerHTML` or
-have bugs with how they handle markdown.
-`react-markdown` uses a syntax tree to build the virtual dom which allows for
-updating only the changing DOM instead of completely overwriting.
-`react-markdown` is 100% CommonMark compliant and has plugins to support other
-syntax extensions (such as GFM).
-
-These features are supported because we use [unified][], specifically [remark][]
-for markdown and [rehype][] for HTML, which are popular tools to transform
-content with plugins.
+The three main reasons are that they often rely on `dangerouslySetInnerHTML`,
+have bugs with how they handle markdown, or don’t let you swap elements for
+components.
+`react-markdown` builds a virtual DOM, so React only replaces what changed,
+from a syntax tree.
+That’s supported because we use [unified][], specifically [remark][] for
+markdown and [rehype][] for HTML, which are popular tools to transform content
+with plugins.
This package focusses on making it easy for beginners to safely use markdown in
React.
@@ -87,7 +90,7 @@ If you instead want to use JavaScript and JSX *inside* markdown files, use
## Install
This package is [ESM only][esm].
-In Node.js (version 12.20+, 14.14+, or 16.0+), install with [npm][]:
+In Node.js (version 16+), install with [npm][]:
```sh
npm install react-markdown
@@ -96,14 +99,14 @@ npm install react-markdown
In Deno with [`esm.sh`][esmsh]:
```js
-import Markdown from 'https://esm.sh/react-markdown@7'
+import Markdown from 'https://esm.sh/react-markdown@8'
```
In browsers with [`esm.sh`][esmsh]:
```html
```
@@ -113,10 +116,12 @@ A basic hello world:
```jsx
import React from 'react'
-import Markdown from 'react-markdown'
import ReactDom from 'react-dom'
+import Markdown from 'react-markdown'
-ReactDom.render(# Hello, *world*!, document.body)
+const markdown = '# Hi, *Pluto*!'
+
+ReactDom.render({markdown}, document.body)
```
@@ -124,15 +129,15 @@ ReactDom.render(# Hello, *world*!, document.body)
```jsx
- Hello, world!
+ Hi, Pluto!
```
Here is an example that shows passing the markdown as a string and how
-to use a plugin ([`remark-gfm`][gfm], which adds support for strikethrough,
-tables, tasklists and URLs directly):
+to use a plugin ([`remark-gfm`][remark-gfm], which adds support for
+footnotes, strikethrough, tables, tasklists and URLs directly):
```jsx
import React from 'react'
@@ -140,10 +145,10 @@ import ReactDom from 'react-dom'
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
-const markdown = `Just a link: https://reactjs.com.`
+const markdown = `Just a link: www.nasa.gov.`
ReactDom.render(
- ,
+ {markdown},
document.body
)
```
@@ -153,7 +158,7 @@ ReactDom.render(
```jsx
- Just a link: https://reactjs.com.
+ Just a link: www.nasa.gov.
```
@@ -162,67 +167,148 @@ ReactDom.render(
## API
This package exports the following identifier:
-[`uriTransformer`][uri-transformer].
-The default export is `Markdown`.
-
-### `props`
-
-* `allowElement` (`(element, index, parent) => boolean?`, optional)\
- function called to check if an element is allowed (when truthy) or not,
- `allowedElements` or `disallowedElements` is used first!
-* `allowedElements` (`Array`, optional)\
- tag names to allow (cannot combine w/ `disallowedElements`), all tag names
- are allowed by default
-* `children` (`string`, optional)\
- markdown to parse
-* `className` (`string?`)\
- wrap the markdown in a `div` with this class name
-* `components` (`Record`, optional)\
- map tag names to React components
-* `disallowedElements` (`Array`, optional)\
- tag names to disallow (cannot combine w/ `allowedElements`), all tag names
- are allowed by default
-* `rehypePlugins` (`Array`, optional)\
- list of [rehype plugins][rehype-plugins] to use
-* `remarkPlugins` (`Array`, optional)\
- list of [remark plugins][remark-plugins] to use
-* `remarkRehypeOptions` (`Object?`, optional)\
- options to pass through to [`remark-rehype`][remark-rehype]
-* `skipHtml` (`boolean`, default: `false`)\
- ignore HTML in markdown completely
-* `transformImageUri` (`(src, alt, title) => string`, default:
- [`uriTransformer`][uri-transformer])\
- change URLs on images;
- pass `false` to allow all URLs, which is unsafe (see [security][])
-* `transformLinkUri` (`(href, children, title) => string`, default:
- [`uriTransformer`][uri-transformer])\
- change URLs on links;
- pass `false` to allow all URLs, which is unsafe (see [security][])
-* `unwrapDisallowed` (`boolean`, default: `false`)\
- extract (unwrap) the children of not allowed elements;
- normally when say `strong` is disallowed, it and it’s children are dropped,
+[`defaultUrlTransform`][api-default-url-transform].
+The default export is [`Markdown`][api-markdown].
+
+### `Markdown`
+
+Component to render markdown.
+
+###### Parameters
+
+* `options` ([`Options`][api-options])
+ — props
+
+###### Returns
+
+React element (`JSX.Element`).
+
+### `defaultUrlTransform(url)`
+
+Make a URL safe.
+
+###### Parameters
+
+* `url` (`string`)
+ — URL
+
+###### Returns
+
+Safe URL (`string`).
+
+### `AllowElement`
+
+Filter elements (TypeScript type).
+
+###### Fields
+
+* `node` ([`Element` from `hast`][hast-element])
+ — element to check
+* `index` (`number | undefined`)
+ — index of `element` in `parent`
+* `parent` ([`Node` from `hast`][hast-node])
+ — parent of `element`
+
+###### Returns
+
+Whether to allow `element` (`boolean`, optional).
+
+### `Components`
+
+Map tag names to components (TypeScript type).
+
+###### Type
+
+```ts
+import type {Element} from 'hast'
+
+type Components = Partial<{
+ [TagName in keyof JSX.IntrinsicElements]:
+ // Class component:
+ | (new (props: JSX.IntrinsicElements[TagName] & ExtraProps) => JSX.ElementClass)
+ // Function component:
+ | ((props: JSX.IntrinsicElements[TagName] & ExtraProps) => JSX.Element | string | null | undefined)
+ // Tag name:
+ | keyof JSX.IntrinsicElements
+}>
+```
+
+### `ExtraProps`
+
+Extra fields we pass to components (TypeScript type).
+
+###### Fields
+
+* `node` ([`Element` from `hast`][hast-element], optional)
+ — original node
+
+### `Options`
+
+Configuration (TypeScript type).
+
+###### Fields
+
+* `allowElement` ([`AllowElement`][api-allow-element], optional)
+ — filter elements;
+ `allowedElements` / `disallowedElements` is used first
+* `allowedElements` (`Array`, default: all tag names)
+ — tag names to allow;
+ cannot combine w/ `disallowedElements`
+* `children` (`string`, optional)
+ — markdown
+* `className` (`string`, optional)
+ — wrap in a `div` with this class name
+* `components` ([`Components`][api-components], optional)
+ — map tag names to components
+* `disallowedElements` (`Array`, default: `[]`)
+ — tag names to disallow;
+ cannot combine w/ `allowedElements`
+* `rehypePlugins` (`Array`, optional)
+ — list of [rehype plugins][rehype-plugins] to use
+* `remarkPlugins` (`Array`, optional)
+ — list of [remark plugins][remark-plugins] to use
+* `remarkRehypeOptions` ([`Options` from
+ `remark-rehype`][remark-rehype-options], optional)
+ — options to pass through to `remark-rehype`
+* `skipHtml` (`boolean`, default: `false`)
+ — ignore HTML in markdown completely
+* `unwrapDisallowed` (`boolean`, default: `false`)
+ — extract (unwrap) what’s in disallowed elements;
+ normally when say `strong` is not allowed, it and it’s children are dropped,
with `unwrapDisallowed` the element itself is replaced by its children
+* `urlTransform` ([`UrlTransform`][api-url-transform], default:
+ [`defaultUrlTransform`][api-default-url-transform])
+ — change URLs
+
+### `UrlTransform`
-### `uriTransformer`
+Transform URLs (TypeScript type).
-Our default URL transform, which you can overwrite (see props above).
-It’s given a URL and cleans it, by allowing only `http:`, `https:`, `mailto:`,
-and `tel:` URLs, absolute paths (`/example.png`), and hashes (`#some-place`).
+###### Fields
-See the [source code here][uri].
+* `url` (`string`)
+ — URL
+* `key` (`string`, example: `'href'`)
+ — property name
+* `node` ([`Element` from `hast`][hast-element])
+ — element to check
+
+###### Returns
+
+Transformed URL (`string`, optional).
## Examples
### Use a plugin
This example shows how to use a remark plugin.
-In this case, [`remark-gfm`][gfm], which adds support for strikethrough, tables,
-tasklists and URLs directly:
+In this case, [`remark-gfm`][remark-gfm], which adds support for strikethrough,
+tables, tasklists and URLs directly:
```jsx
import React from 'react'
-import Markdown from 'react-markdown'
import ReactDom from 'react-dom'
+import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
const markdown = `A paragraph with *emphasis* and **strong importance**.
@@ -240,7 +326,7 @@ A table:
`
ReactDom.render(
- ,
+ {markdown},
document.body
)
```
@@ -259,21 +345,21 @@ ReactDom.render(
https://reactjs.org.
-
+
A table:
@@ -287,17 +373,20 @@ ReactDom.render(
This example shows how to use a plugin and give it options.
To do that, use an array with the plugin at the first place, and the options
second.
-[`remark-gfm`][gfm] has an option to allow only double tildes for strikethrough:
+[`remark-gfm`][remark-gfm] has an option to allow only double tildes for
+strikethrough:
```jsx
import React from 'react'
-import Markdown from 'react-markdown'
import ReactDom from 'react-dom'
+import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
+const markdown = 'This ~is not~ strikethrough, but ~~this is~~!'
+
ReactDom.render(
- This ~is not~ strikethrough, but ~~this is~~!
+ {markdown}
,
document.body
)
@@ -322,6 +411,8 @@ In this case, we apply syntax highlighting with the seriously super amazing
[`react-syntax-highlighter`][react-syntax-highlighter] by
[**@conorhastings**][conor]:
+
+
```jsx
import React from 'react'
import ReactDom from 'react-dom'
@@ -380,25 +471,24 @@ ReactDom.render(
### Use remark and rehype plugins (math)
-This example shows how a syntax extension (through [`remark-math`][math])
+This example shows how a syntax extension (through [`remark-math`][remark-math])
is used to support math in markdown, and a transform plugin
-([`rehype-katex`][katex]) to render that math.
+([`rehype-katex`][rehype-katex]) to render that math.
```jsx
import React from 'react'
import ReactDom from 'react-dom'
import Markdown from 'react-markdown'
-import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
-
+import remarkMath from 'remark-math'
import 'katex/dist/katex.min.css' // `rehype-katex` does not import the CSS for you
+const markdown = `The lift coefficient ($C_L$) is a dimensionless coefficient.`
+
ReactDom.render(
- ,
+
+ {markdown}
+ ,
document.body
)
```
@@ -409,14 +499,12 @@ ReactDom.render(
```jsx
The lift coefficient (
-
-
-
-
-
-
- {/* … */}
-
+
+
+
+
+
+ {/* … */}
) is a dimensionless coefficient.
@@ -452,15 +540,23 @@ extensions.
## Types
This package is fully typed with [TypeScript][].
-It exports `Options` and `Components` types, which specify the interface of the
-accepted props and components.
+It exports the additional types
+[`AllowElement`][api-allow-element],
+[`ExtraProps`][api-extra-props],
+[`Components`][api-components],
+[`Options`][api-options], and
+[`UrlTransform`][api-url-transform].
## Compatibility
-Projects maintained by the unified collective are compatible with all maintained
+Projects maintained by the unified collective are compatible with maintained
versions of Node.js.
-As of now, that is Node.js 12.20+, 14.14+, and 16.0+.
-Our projects sometimes work with older versions, but this is not guaranteed.
+
+When we cut a new major release, we drop support for unmaintained versions of
+Node.
+This means we try to keep the current release line, `react-markdown@^8`,
+compatible with Node.js 12.
+
They work in all modern browsers (essentially: everything not IE 11).
You can use a bundler (such as esbuild, webpack, or Rollup) to use this package
in your project, and use its options (or plugins) to add support for legacy
@@ -501,7 +597,7 @@ because it is dangerous and defeats the purpose of this library.
However, if you are in a trusted environment (you trust the markdown), and
can spare the bundle size (±60kb minzipped), then you can use
-[`rehype-raw`][raw]:
+[`rehype-raw`][rehype-raw]:
```jsx
import React from 'react'
@@ -509,14 +605,14 @@ import ReactDom from 'react-dom'
import Markdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
-const input = `
+const markdown = `
Some *emphasis* and strong!
`
ReactDom.render(
-
,
+
{markdown},
document.body
)
```
@@ -525,15 +621,17 @@ ReactDom.render(
Show equivalent JSX
```jsx
-
-
Some emphasis and strong!
+
+
+ Some emphasis and strong!
+
```
**Note**: HTML in markdown is still bound by how [HTML works in
-CommonMark][cm-html].
+CommonMark][commonmark-html].
Make sure to use blank lines around block-level HTML that again contains
markdown!
@@ -543,7 +641,6 @@ You can also change the things that come from markdown:
```jsx