Skip to content

Commit 01abe3e

Browse files
committed
feat: Support dangerouslySetInnerHTML on Fragments
1 parent fd0c0f6 commit 01abe3e

File tree

3 files changed

+51
-24
lines changed

3 files changed

+51
-24
lines changed

src/index.js

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
HTML_LOWER_CASE,
77
HTML_ENUMERATED,
88
SVG_CAMEL_CASE,
9-
createComponent
9+
createComponent,
10+
flattenTopLevelFragments
1011
} from './lib/util.js';
1112
import { options, h, Fragment } from 'preact';
1213
import {
@@ -342,6 +343,8 @@ function _renderToString(
342343
// Fragments are the least used components of core that's why
343344
// branching here for comments has the least effect on perf.
344345
return '<!--' + encodeEntities(props.UNSTABLE_comment) + '-->';
346+
} else if ('dangerouslySetInnerHTML' in props) {
347+
return props.dangerouslySetInnerHTML.__html;
345348
}
346349

347350
rendered = props.children;
@@ -395,14 +398,7 @@ function _renderToString(
395398
options.errorBoundaries &&
396399
(type.getDerivedStateFromError || component.componentDidCatch)
397400
) {
398-
// When a component returns a Fragment node we flatten it in core, so we
399-
// need to mirror that logic here too
400-
let isTopLevelFragment =
401-
rendered != null &&
402-
rendered.type === Fragment &&
403-
rendered.key == null &&
404-
rendered.props.tpl == null;
405-
rendered = isTopLevelFragment ? rendered.props.children : rendered;
401+
rendered = flattenTopLevelFragments(rendered);
406402

407403
try {
408404
return _renderToString(
@@ -432,12 +428,7 @@ function _renderToString(
432428
context = assign({}, context, component.getChildContext());
433429
}
434430

435-
let isTopLevelFragment =
436-
rendered != null &&
437-
rendered.type === Fragment &&
438-
rendered.key == null &&
439-
rendered.props.tpl == null;
440-
rendered = isTopLevelFragment ? rendered.props.children : rendered;
431+
rendered = flattenTopLevelFragments(rendered);
441432

442433
return _renderToString(
443434
rendered,
@@ -459,14 +450,7 @@ function _renderToString(
459450
}
460451
}
461452

462-
// When a component returns a Fragment node we flatten it in core, so we
463-
// need to mirror that logic here too
464-
let isTopLevelFragment =
465-
rendered != null &&
466-
rendered.type === Fragment &&
467-
rendered.key == null &&
468-
rendered.props.tpl == null;
469-
rendered = isTopLevelFragment ? rendered.props.children : rendered;
453+
rendered = flattenTopLevelFragments(rendered);
470454

471455
try {
472456
// Recurse into children before invoking the after-diff hook
@@ -542,7 +526,7 @@ function _renderToString(
542526
: result;
543527
} catch (e) {
544528
if (!e || typeof e.then != 'function') throw e;
545-
529+
546530
return e.then(renderNestedChildren);
547531
}
548532
};

src/lib/util.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Fragment } from 'preact';
2+
13
export const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
24
export const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
35
export const NAMESPACE_REPLACE_REGEX = /^(xlink|xmlns|xml)([A-Z])/;
@@ -179,3 +181,20 @@ export class Deferred {
179181
});
180182
}
181183
}
184+
185+
/**
186+
* When a component returns a Fragment node we flatten it in core, so we
187+
* need to mirror that logic here too
188+
*
189+
* @param {any} rendered
190+
* @returns {any}
191+
*/
192+
export function flattenTopLevelFragments(rendered) {
193+
const isTopLevelFragment =
194+
rendered != null &&
195+
rendered.type === Fragment &&
196+
rendered.key == null &&
197+
rendered.props.tpl == null &&
198+
rendered.props.dangerouslySetInnerHTML == null;
199+
return isTopLevelFragment ? rendered.props.children : rendered;
200+
}

test/render.test.jsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,30 @@ describe('render', () => {
861861
);
862862
expect(rendered).to.equal('<div>foo</div>');
863863
});
864+
865+
it('should accept dangerouslySetInnerHTML on Fragments', () => {
866+
// some invalid HTML to make sure we're being flakey:
867+
let html = '<a href="foo">asdf</a> some text <ul><li>foo<li>bar</ul>';
868+
let rendered = render(
869+
<div>
870+
<Fragment dangerouslySetInnerHTML={{ __html: html }} />
871+
</div>
872+
);
873+
expect(rendered).to.equal(`<div>${html}</div>`);
874+
});
875+
876+
it('should accept dangerouslySetInnerHTML on Fragments in Components', () => {
877+
// some invalid HTML to make sure we're being flakey:
878+
let html = '<a href="foo">asdf</a> some text <ul><li>foo<li>bar</ul>';
879+
const Foo = () => <Fragment dangerouslySetInnerHTML={{ __html: html }} />;
880+
881+
let rendered = render(
882+
<div>
883+
<Foo />
884+
</div>
885+
);
886+
expect(rendered).to.equal(`<div>${html}</div>`);
887+
});
864888
});
865889

866890
describe('className / class massaging', () => {

0 commit comments

Comments
 (0)