Skip to content

Sanitize with DOMPurify by default and add a React Native entry - #1

Merged
devmaxxx merged 6 commits into
mainfrom
feat/react-native-and-dompurify
Oct 13, 2026
Merged

devmaxxx merged 6 commits into
mainfrom
feat/react-native-and-dompurify

Conversation

@devmaxxx

Copy link
Copy Markdown
Owner

Summary

  • Default entry sanitizes. parse from react-html5-parser runs a private DOMPurify instance over the parsed tree in place, so the checked tree is the rendered tree (no second parse, no mutation XSS). On top of DOMPurify's defaults it removes only what reaches past the fragment: <style>, <title>, form/formaction, position: fixed styles and javascript: attribute values. A new purify option merges a DOMPurify config over the built-in one; DOMPurify still runs after a caller's own sanitize.
  • react-html5-parser/core. The unsanitized parser, about 3 kB.
  • react-html5-parser/native. Renders HTML to React Native View, Text and Image. Parses with htmlparser2, bundled into the entry so Metro before 0.79 resolves it. Links open only for http, https, mailto and tel; images load only from http, https and raster data URIs. Options: components, tagStyles, textStyle, onLinkPress, mapElement.
  • React 18/19 audit. SVG attributes mapped by default, uncontrolled form fields, no React 19 head hoisting or <html>/<body> binding, custom elements keep their attribute names, attrsMap applies everywhere.

Breaking changes

  • Peer react >= 18; the native entry also needs react-native >= 0.71.
  • The default entry sanitizes and grows from 1.4 kB to about 15 kB. Import from /core to opt out.
  • components entries render as React components instead of being called as functions, so an inline arrow function remounts its subtree on every render, and a component returning undefined throws on React 18.
  • mapElement receives the component element for a mapped tag.

Sizes (min + gzip)

Entry Size
react-html5-parser 15.1 kB
react-html5-parser/core 3 kB
react-html5-parser/native 26.5 kB

Test plan

  • npm test (typecheck + jest): 208/208 on React 19
  • Same suite on React 18.3: 208/208
  • npm run build:size within limits
  • Code review at high, per module (native, web/DOMPurify, core); findings fixed or listed in the commit
  • Try the native entry in a React Native 0.71 app and a current one

The default entry now sanitizes the parsed tree in place with a private
DOMPurify instance, so what DOMPurify checked is exactly what renders. On
top of DOMPurify's defaults it only removes what reaches past the fragment:
<style> and <title>, form/formaction, position: fixed styles and
javascript: attribute values. A new `purify` option merges a DOMPurify
config over the built-in one, and DOMPurify still runs after a caller's
own `sanitize`.

A new `react-html5-parser/core` entry keeps the unsanitized parser at
about 3 kB, and `react-html5-parser/native` renders HTML to React Native
View, Text and Image components. It parses with htmlparser2, bundled into
the entry so Metro versions that ignore package exports can resolve it,
opens links only for http, https, mailto and tel, and loads images only
from http, https and raster data URIs.

The parser was audited against React 18 and 19: SVG attributes are mapped
by default, form fields stay uncontrolled, React 19 head hoisting and
<html>/<body> binding are avoided, and custom elements keep their
attribute names.

BREAKING CHANGE: react >= 18 is now required (react-native >= 0.71 for the
native entry). `parse` from the default entry sanitizes, which grows it to
about 15 kB; import from `react-html5-parser/core` to opt out. Entries in
`components` render as React components rather than being called as
functions, so an inline arrow function remounts its subtree on every
render.
Build ESM and CommonJS with tsdown 0.21, which still runs on Node 22.13.
CommonJS entries are built one by one: in a shared build rolldown puts its
interop helpers in index.cjs, so core.cjs and native.cjs would require it and
load DOMPurify. publint runs after every build.

Drop the UMD and legacy .module.mjs outputs, and raise the size limits by the
86 B and 10 B the new output costs. CI runs tests and the size check on
pushes and pull requests to main.
Describe parse per entry, add an options table and the order in which the
options run, and list the other exports. Replace placeholder examples with
real ones, fix the attrsMap example that DOMPurify made a no-op, and move
longer recipes to EXAMPLES.md.
@devmaxxx
devmaxxx merged commit 7d6e181 into main Oct 13, 2026
1 check passed
@devmaxxx
devmaxxx deleted the feat/react-native-and-dompurify branch October 13, 2026 23:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant