Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Release

on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"

permissions:
contents: write
# npm trusted publishing: the package is published with an OIDC token and
# gets provenance, so no NPM_TOKEN secret is stored.
id-token: write

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
cache: npm
registry-url: https://registry.npmjs.org

# Trusted publishing needs npm 11.5.1 or newer.
- run: npm install -g npm@latest

- name: Check the tag matches package.json
run: |
version=$(node -p "require('./package.json').version")
if [ "$version" != "$GITHUB_REF_NAME" ]; then
echo "Tag $GITHUB_REF_NAME does not match package.json version $version"
exit 1
fi

- run: npm ci
- run: npm test
- run: npm run build:size
- run: npm publish

- name: Create the GitHub release from CHANGELOG.md
env:
GH_TOKEN: ${{ github.token }}
run: |
awk -v v="$GITHUB_REF_NAME" '/^## /{ if (f) exit; f = ($2 == v); next } f' CHANGELOG.md > notes.md
gh release create "$GITHUB_REF_NAME" --verify-tag --title "$GITHUB_REF_NAME" --notes-file notes.md
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Changelog

## 1.0.1

- `exports` names the type file for each entry and module format, so TypeScript picks `.d.ts` for `import` and `.d.cts` for `require` without guessing.
- The package ships `CHANGELOG.md`, and `author` names the maintainer.

## 1.0.0

The default entry now sanitizes, and React Native gets its own entry.

### Breaking changes

- **React 18 or newer.** The `react` peer range moves from `>=16.8.0` to `>=18.0.0`. The native entry also needs `react-native >=0.71`.
- **`parse` sanitizes by default.** `parse` from `react-html5-parser` runs DOMPurify over the parsed tree. Output that used to render can now be stripped:
- `<script>`, event handlers (`onclick`), `javascript:` URLs, `<iframe>`, `<embed>` and `<object>`;
- `<style>` and `<title>`, which act on the whole page;
- the `form` and `formaction` attributes;
- `style` attributes that set `position: fixed`;
- attributes outside DOMPurify's allowlist, such as `contenteditable` and `fetchpriority`;
- unknown and custom elements, unless the tag is mapped in `components` (their content is kept).
- **Bigger default entry.** It grows from about 1.4 kB to about 15 kB min + gzip, because DOMPurify is bundled. Import from `react-html5-parser/core` to keep the old size and behaviour.
- **`sanitize` no longer replaces the sanitizer on the default entry.** DOMPurify runs after your `sanitize` function. To use only your own sanitizer, import from `/core`.
- **`components` entries render as React components.** They were called as plain functions before. Hooks inside them now work, but an inline arrow function remounts its subtree on every render, and a component that returns `undefined` throws on React 18. Define components outside render and return `null` for nothing.
- **`mapElement` receives the component element** for a tag mapped in `components`, not the element the component returned.
- **`MapComponentFn` props no longer include `key`.** The type is now `ComponentProps` (`PropsWithChildren<Attributes>`).

### Added

- `react-html5-parser/core`: the unsanitized parser, about 3 kB.
- `react-html5-parser/native`: renders HTML to React Native `View`, `Text` and `Image`, with `components`, `tagStyles`, `textStyle`, `onLinkPress` and `mapElement` options. Links open only for `http`, `https`, `mailto` and `tel`; images load only from `http`, `https` and raster data URIs.
- `purify` option on the default entry: a DOMPurify config merged over the built-in one.
- CommonJS builds next to ESM for every entry.

### Fixed

- SVG attributes (`viewBox`, `stroke-width`, `xlink:href` and the rest) map to their React names by default.
- Form fields render uncontrolled (`defaultValue`, `defaultChecked`), so React no longer warns about a `value` without `onChange`.
- On React 19, `<script async>` is rendered inert instead of hoisted and run, and `<html>` / `<body>` no longer bind to the page's own.
- Custom elements keep their attribute names as written.
- `attrsMap` applies to every element, including mapped components.

### Upgrading

See [Upgrading from 0.x](README.md#upgrading-from-0x).
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2022 Max Syn
Copyright (c) 2022-2026 Maksym Synenko

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,34 @@ An error thrown inside `onError` reaches the caller. A component from `component
- `hidden="until-found"` becomes plain `hidden`: React 19 treats `hidden` as a boolean.
- In the core entry, `async` is dropped from `<script>`, so React 19 renders it inert instead of hoisting and running it, and `<title>`, `<meta>` and `<link>` are hoisted into the document `<head>` by React 19.

## Upgrading from 0.x

1.0 sanitizes by default and needs React 18. Every change is in the [changelog](CHANGELOG.md#100).

**Keep the 0.x output.** Import from the core entry. It is the 0.x parser with the 1.0 fixes, and runs only the `sanitize` you pass:

```diff
- import { parse } from "react-html5-parser";
+ import { parse } from "react-html5-parser/core";
```

**Stay on the default entry.** Check what DOMPurify now removes from your content (see [Built-in sanitizer](#built-in-sanitizer)), then:

- Drop your own DOMPurify call from `sanitize`; the built-in one runs anyway. Keep `sanitize` only for rewrites.
- Allow a tag or attribute you need through `purify`, or map the tag in `components`:

```ts
parse(html, { purify: { ADD_ATTR: ["contenteditable"] } });
```

**`components`.** Entries are now rendered as React components, not called as functions:

- Define the map at module scope. An inline object or arrow function gives the tag a new component type on every render, and React remounts its subtree.
- Return `null`, not `undefined`, to render nothing. React 18 throws on `undefined`.
- `mapElement` now receives the `<YourComponent>` element for a mapped tag, not what your component returned.

**Types.** `MapComponentFn` props are now `ComponentProps` and no longer include `key`.

## Development

```sh
Expand All @@ -381,6 +409,8 @@ npm run build # tsdown to dist/ (ESM + CJS + types), then publint
npm run build:size # build, then check the size limits above
```

To release, bump `version` in `package.json`, add its section to `CHANGELOG.md`, merge, then run `npm run release` on `main`. It tags the version and pushes the tag; the Release workflow publishes to npm with provenance and creates the GitHub release from the changelog section.

## Contributing

Issues and pull requests are welcome at [github.com/devmaxxx/react-html5-parser](https://github.com/devmaxxx/react-html5-parser). For a bug, include the HTML string and the options you passed to `parse`. A pull request should come with a test in `__tests__/` and keep `npm test` and `npm run build:size` green.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 31 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-html5-parser",
"version": "1.0.0",
"version": "1.0.1",
"description": "Turn HTML strings into React and React Native elements, sanitized by default. No dangerouslySetInnerHTML.",
"keywords": [
"react",
Expand Down Expand Up @@ -31,23 +31,41 @@
"url": "git+https://github.com/devmaxxx/react-html5-parser.git"
},
"license": "MIT",
"author": "devmaxxx",
"author": "Maksym Synenko (https://github.com/devmaxxx)",
"sideEffects": false,
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"./core": {
"import": "./dist/core.js",
"require": "./dist/core.cjs"
"import": {
"types": "./dist/core.d.ts",
"default": "./dist/core.js"
},
"require": {
"types": "./dist/core.d.cts",
"default": "./dist/core.cjs"
}
},
"./native": {
"import": "./dist/native.js",
"require": "./dist/native.cjs"
"import": {
"types": "./dist/native.d.ts",
"default": "./dist/native.js"
},
"require": {
"types": "./dist/native.d.cts",
"default": "./dist/native.cjs"
}
},
"./package.json": "./package.json"
},
Expand All @@ -57,6 +75,7 @@
"dist",
"native",
"README.md",
"CHANGELOG.md",
"EXAMPLES.md"
],
"scripts": {
Expand All @@ -66,7 +85,7 @@
"build:size": "npm run build && npm run sizecheck",
"test": "npm run typecheck && jest",
"test:watch": "jest --watchAll",
"release": "npm run test && npm run build:size && git tag $npm_package_version && git push && git push --tags && npm publish",
"release": "npm run test && npm run build:size && git tag $npm_package_version && git push && git push origin $npm_package_version",
"typecheck": "tsc --noEmit -p .",
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""
},
Expand Down Expand Up @@ -155,5 +174,8 @@
"domhandler": "6.0.1",
"entities": "8.1.0",
"htmlparser2": "12.0.0"
},
"publishConfig": {
"access": "public"
}
}
Loading