diff --git a/src/create-element.ts b/src/create-element.ts index d46308d..d2d4398 100644 --- a/src/create-element.ts +++ b/src/create-element.ts @@ -54,6 +54,15 @@ export function createElement( } const attrs: Record = {}; for (const [key, value] of Object.entries(props || {})) { + if (key === "dangerouslySetInnerHTML") { + if (children.length > 0) { + throw new Error( + 'An element with children may not have a "dangerouslySetInnerHTML" prop since children would be overriden', + ); + } + attrs[key] = value.__html; + continue; + } if ( typeof value !== "string" && typeof value !== "number" && diff --git a/src/jsx.ts b/src/jsx.ts index 55d69a9..d450c4b 100644 --- a/src/jsx.ts +++ b/src/jsx.ts @@ -25,7 +25,14 @@ export interface MicrodataAttributes { itemRef?: string; } -export interface HTMLAttributes extends RdfaAttributes, MicrodataAttributes { +export interface SetInnerHtmlAttributes { + dangerouslySetInnerHTML?: { __html: string }; +} + +export interface HTMLAttributes + extends RdfaAttributes, + MicrodataAttributes, + SetInnerHtmlAttributes { accept?: string; acceptCharset?: string; accessKey?: string; diff --git a/src/render.ts b/src/render.ts index 4566d70..60cdb73 100644 --- a/src/render.ts +++ b/src/render.ts @@ -34,8 +34,12 @@ const renderElement = (elem: Element): string => { let output = ""; output += startTag(elem); - for (const child of elem.children) { - output += renderElement(child); + if (elem.attributes.dangerouslySetInnerHTML != null) { + output += elem.attributes.dangerouslySetInnerHTML; + } else { + for (const child of elem.children) { + output += renderElement(child); + } } output += endTag(elem); return output; @@ -55,6 +59,11 @@ const startTag = (elem: HTMLElement): string => { continue; } + // Ignore the special attr for setting raw inner HTML. + if (attrName === "dangerouslySetInnerHTML") { + continue; + } + let normalizedAttrName = escapeHtml(attrName.toLowerCase()); if (normalizedAttrName === "classname") { normalizedAttrName = "class"; diff --git a/test/test-suites/rendering.tsx b/test/test-suites/rendering.tsx index 7caa8b2..ed3dfe5 100644 --- a/test/test-suites/rendering.tsx +++ b/test/test-suites/rendering.tsx @@ -117,4 +117,37 @@ testSuite("renderPage", ({ test, expect }) => { "
test of
fragments
", ); }); + + test("renders unescaped HTML via dangerouslySetInnerHTML", () => { + const html = renderPage( + +
red alert!
", + }} + /> + , + ); + expect(html).toEqual( + "
red alert!
", + ); + }); + + test("throws error when both dangerouslySetInnerHTML and children prop present", () => { + expect(() => + renderPage( + +
set phasers to kill
", + }} + > +
set phasers to stun
+ + , + ), + ).toThrowErrorMatching( + 'An element with children may not have a "dangerouslySetInnerHTML" prop since children would be overriden', + ); + }); });