From 70356f113cfdc843aa6e0e0c5de6e504ba843bb5 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Sat, 21 Mar 2026 09:40:19 +0100 Subject: [PATCH 1/5] feat: support `dangerouslySetInnerHTML` on Fragments --- .changeset/neat-books-remain.md | 5 +++++ src/index.js | 4 ++++ test/render.test.jsx | 24 ++++++++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 .changeset/neat-books-remain.md diff --git a/.changeset/neat-books-remain.md b/.changeset/neat-books-remain.md new file mode 100644 index 00000000..267db823 --- /dev/null +++ b/.changeset/neat-books-remain.md @@ -0,0 +1,5 @@ +--- +'preact-render-to-string': minor +--- + +Add support for `dangerouslySetInnerHTML` on Fragments. This allows you to insert unsafe raw HTML content without a wrapping container element. diff --git a/src/index.js b/src/index.js index f1bf5b93..7fe28ade 100644 --- a/src/index.js +++ b/src/index.js @@ -346,6 +346,10 @@ function _renderToString( // Fragments are the least used components of core that's why // branching here for comments has the least effect on perf. return ''; + } else if ('dangerouslySetInnerHTML' in props) { + return ( + '' + props.dangerouslySetInnerHTML.__html + '' + ); } rendered = props.children; diff --git a/test/render.test.jsx b/test/render.test.jsx index 19dea6b0..e594b86d 100644 --- a/test/render.test.jsx +++ b/test/render.test.jsx @@ -857,6 +857,30 @@ describe('render', () => { ); expect(rendered).to.equal('
foo
'); }); + + it('should support dangerouslySetInnerHTML on Fragments', () => { + let rendered = render( +
+ foo + + baz +
+ ); + expect(rendered).to.equal('
foobarbaz
'); + }); + + it('should ignore children on Fragments with dangerouslySetInnerHTML', () => { + let rendered = render( +
+ foo + +

ignored

+
+ baz +
+ ); + expect(rendered).to.equal('
foobarbaz
'); + }); }); describe('className / class massaging', () => { From 0b2929c9ea5845539f912c83396527c15616e3b0 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Sat, 21 Mar 2026 09:47:43 +0100 Subject: [PATCH 2/5] fix: support falsy html --- src/index.js | 5 ++--- test/render.test.jsx | 13 +++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 7fe28ade..6ebd495d 100644 --- a/src/index.js +++ b/src/index.js @@ -347,9 +347,8 @@ function _renderToString( // branching here for comments has the least effect on perf. return ''; } else if ('dangerouslySetInnerHTML' in props) { - return ( - '' + props.dangerouslySetInnerHTML.__html + '' - ); + let html = props.dangerouslySetInnerHTML.__html ?? ''; + return '' + html + ''; } rendered = props.children; diff --git a/test/render.test.jsx b/test/render.test.jsx index e594b86d..ea1e56f8 100644 --- a/test/render.test.jsx +++ b/test/render.test.jsx @@ -881,6 +881,19 @@ describe('render', () => { ); expect(rendered).to.equal('
foobarbaz
'); }); + + it.only('should support falsy dangerouslySetInnerHTML on Fragments', () => { + let rendered = render( +
+ foo + +

ignored

+
+ baz +
+ ); + expect(rendered).to.equal('
foobaz
'); + }); }); describe('className / class massaging', () => { From fca7448a27b455bf671ca260dc66988e1aa8959c Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Sat, 21 Mar 2026 09:49:52 +0100 Subject: [PATCH 3/5] chore fix only --- test/render.test.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/render.test.jsx b/test/render.test.jsx index ea1e56f8..5b3904e1 100644 --- a/test/render.test.jsx +++ b/test/render.test.jsx @@ -882,7 +882,7 @@ describe('render', () => { expect(rendered).to.equal('
foobarbaz
'); }); - it.only('should support falsy dangerouslySetInnerHTML on Fragments', () => { + it('should support falsy dangerouslySetInnerHTML on Fragments', () => { let rendered = render(
foo From 86278625bc45767fc7a4f2e38affd3483dacd2ea Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Sat, 21 Mar 2026 09:55:49 +0100 Subject: [PATCH 4/5] fix always render comment for hydration --- src/index.js | 6 ++++-- test/render.test.jsx | 22 +++++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index 6ebd495d..fa81663b 100644 --- a/src/index.js +++ b/src/index.js @@ -346,8 +346,10 @@ function _renderToString( // Fragments are the least used components of core that's why // branching here for comments has the least effect on perf. return ''; - } else if ('dangerouslySetInnerHTML' in props) { - let html = props.dangerouslySetInnerHTML.__html ?? ''; + } + + if (props.dangerouslySetInnerHTML) { + let html = props.dangerouslySetInnerHTML.__html || ''; return '' + html + ''; } diff --git a/test/render.test.jsx b/test/render.test.jsx index 5b3904e1..39ce2f72 100644 --- a/test/render.test.jsx +++ b/test/render.test.jsx @@ -886,9 +886,25 @@ describe('render', () => { let rendered = render(
foo - -

ignored

-
+ + baz +
+ ); + expect(rendered).to.equal('
foobaz
'); + + rendered = render( +
+ foo + + baz +
+ ); + expect(rendered).to.equal('
foobaz
'); + + rendered = render( +
+ foo + baz
); From 5a5e83fb319dcc1273ab9e9638c485598405086d Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Sat, 21 Mar 2026 09:56:36 +0100 Subject: [PATCH 5/5] chore: formatting --- src/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index fa81663b..090c9dd6 100644 --- a/src/index.js +++ b/src/index.js @@ -346,9 +346,7 @@ function _renderToString( // Fragments are the least used components of core that's why // branching here for comments has the least effect on perf. return ''; - } - - if (props.dangerouslySetInnerHTML) { + } else if (props.dangerouslySetInnerHTML) { let html = props.dangerouslySetInnerHTML.__html || ''; return '' + html + ''; }