diff --git a/package-lock.json b/package-lock.json index 6e40cb4..d3d6c83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "preact-render-to-string", - "version": "6.5.12", + "version": "6.5.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "preact-render-to-string", - "version": "6.5.12", + "version": "6.5.13", "license": "MIT", "devDependencies": { "@babel/plugin-transform-react-jsx": "^7.12.12", @@ -2561,10 +2561,11 @@ }, "node_modules/baseline-rts": { "name": "preact-render-to-string", - "version": "6.5.11", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz", - "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==", + "version": "6.5.13", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.13.tgz", + "integrity": "sha512-iGPd+hKPMFKsfpR2vL4kJ6ZPcFIoWZEcBf0Dpm3zOpdVvj77aY8RlLiQji5OMrngEyaxGogeakTb54uS2FvA6w==", "dev": true, + "license": "MIT", "peerDependencies": { "preact": ">=10" } diff --git a/src/index.js b/src/index.js index 9c69683..79db9cc 100644 --- a/src/index.js +++ b/src/index.js @@ -6,7 +6,8 @@ import { HTML_LOWER_CASE, HTML_ENUMERATED, SVG_CAMEL_CASE, - createComponent + createComponent, + flattenTopLevelFragments } from './lib/util.js'; import { options, h, Fragment } from 'preact'; import { @@ -342,6 +343,8 @@ function _renderToString( // Fragments are the least used components of core that's why // branching here for comments has the least effect on perf. return '<!--' + encodeEntities(props.UNSTABLE_comment) + '-->'; + } else if ('dangerouslySetInnerHTML' in props) { + return props.dangerouslySetInnerHTML.__html; } rendered = props.children; @@ -395,14 +398,7 @@ function _renderToString( options.errorBoundaries && (type.getDerivedStateFromError || component.componentDidCatch) ) { - // When a component returns a Fragment node we flatten it in core, so we - // need to mirror that logic here too - let isTopLevelFragment = - rendered != null && - rendered.type === Fragment && - rendered.key == null && - rendered.props.tpl == null; - rendered = isTopLevelFragment ? rendered.props.children : rendered; + rendered = flattenTopLevelFragments(rendered); try { return _renderToString( @@ -432,12 +428,7 @@ function _renderToString( context = assign({}, context, component.getChildContext()); } - let isTopLevelFragment = - rendered != null && - rendered.type === Fragment && - rendered.key == null && - rendered.props.tpl == null; - rendered = isTopLevelFragment ? rendered.props.children : rendered; + rendered = flattenTopLevelFragments(rendered); return _renderToString( rendered, @@ -459,14 +450,7 @@ function _renderToString( } } - // When a component returns a Fragment node we flatten it in core, so we - // need to mirror that logic here too - let isTopLevelFragment = - rendered != null && - rendered.type === Fragment && - rendered.key == null && - rendered.props.tpl == null; - rendered = isTopLevelFragment ? rendered.props.children : rendered; + rendered = flattenTopLevelFragments(rendered); try { // Recurse into children before invoking the after-diff hook @@ -542,7 +526,7 @@ function _renderToString( : result; } catch (e) { if (!e || typeof e.then != 'function') throw e; - + return e.then(renderNestedChildren); } }; diff --git a/src/lib/util.js b/src/lib/util.js index c5f7158..4ea375a 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -1,3 +1,5 @@ +import { Fragment } from 'preact'; + export const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/; export const UNSAFE_NAME = /[\s\n\\/='"\0<>]/; export const NAMESPACE_REPLACE_REGEX = /^(xlink|xmlns|xml)([A-Z])/; @@ -179,3 +181,20 @@ export class Deferred { }); } } + +/** + * When a component returns a Fragment node we flatten it in core, so we + * need to mirror that logic here too + * + * @param {any} rendered + * @returns {any} + */ +export function flattenTopLevelFragments(rendered) { + const isTopLevelFragment = + rendered != null && + rendered.type === Fragment && + rendered.key == null && + rendered.props.tpl == null && + rendered.props.dangerouslySetInnerHTML == null; + return isTopLevelFragment ? rendered.props.children : rendered; +} diff --git a/test/render.test.jsx b/test/render.test.jsx index 30437d6..5294c80 100644 --- a/test/render.test.jsx +++ b/test/render.test.jsx @@ -861,6 +861,30 @@ describe('render', () => { ); expect(rendered).to.equal('<div>foo</div>'); }); + + it('should accept dangerouslySetInnerHTML on Fragments', () => { + // some invalid HTML to make sure we're being flakey: + let html = '<a href="foo">asdf</a> some text <ul><li>foo<li>bar</ul>'; + let rendered = render( + <div> + <Fragment dangerouslySetInnerHTML={{ __html: html }} /> + </div> + ); + expect(rendered).to.equal(`<div>${html}</div>`); + }); + + it('should accept dangerouslySetInnerHTML on Fragments in Components', () => { + // some invalid HTML to make sure we're being flakey: + let html = '<a href="foo">asdf</a> some text <ul><li>foo<li>bar</ul>'; + const Foo = () => <Fragment dangerouslySetInnerHTML={{ __html: html }} />; + + let rendered = render( + <div> + <Foo /> + </div> + ); + expect(rendered).to.equal(`<div>${html}</div>`); + }); }); describe('className / class massaging', () => {