From b958a76db4d3c346184817ba0d55b0418c561eca Mon Sep 17 00:00:00 2001 From: ncpa0cpl Date: Sat, 16 Sep 2023 11:41:10 +0200 Subject: [PATCH] fix: self closing tags --- .../render-to-html.test.tsx.snap | 80 +++++++++---------- src/html-parser/jsx-elem-to-html.ts | 40 ++++++++-- .../jsx-elem-to-strings.ts | 40 +++++----- src/utilities/self-closing-tag-list.ts | 16 ++++ 4 files changed, 113 insertions(+), 63 deletions(-) create mode 100644 src/utilities/self-closing-tag-list.ts diff --git a/__tests__/html-parser/__snapshots__/render-to-html.test.tsx.snap b/__tests__/html-parser/__snapshots__/render-to-html.test.tsx.snap index 302a49d..5144d30 100644 --- a/__tests__/html-parser/__snapshots__/render-to-html.test.tsx.snap +++ b/__tests__/html-parser/__snapshots__/render-to-html.test.tsx.snap @@ -3,16 +3,16 @@ exports[`renderToHTML ErrorBoundary should correctly render the tree that's inside an ErrorBoundary 1`] = ` " - - + + Page Title - - + +

Hello World!

- +
@@ -43,16 +43,16 @@ exports[`renderToHTML ErrorBoundary should render the fallback if the direct chi exports[`renderToHTML should correctly generate html from component base jsx structure 1`] = ` " - - + + Page Title - - + +

Hello World!

- +
@@ -64,23 +64,23 @@ exports[`renderToHTML should correctly generate html from simple jsx 1`] = `

Hello World

Prop Title

- + " `; exports[`renderToHTML should correctly parse async components 1`] = ` " - - + + Page Title - - + +

Async title

- +
@@ -147,7 +147,7 @@ exports[`renderToHTML should properly handle context data correctly handles enca
- +
@@ -182,11 +182,11 @@ exports[`renderToHTML should properly handle context data should correctly drill exports[`renderToHTML should properly handle context data should correctly handle provider pattern 1`] = ` " - - + + Page Title - - + +
@@ -201,15 +201,15 @@ exports[`renderToHTML should properly handle context data should correctly handl exports[`renderToHTML should properly handle context data should correctly override existing context data 1`] = ` " - - + + Page Title - - + +

This title was overridden

- + " `; @@ -217,11 +217,11 @@ exports[`renderToHTML should properly handle context data should correctly overr exports[`renderToHTML should properly handle context data should correctly render jsx with context data 1`] = ` " - - + + Page Title - - + +
@@ -236,17 +236,17 @@ exports[`renderToHTML should properly handle context data should correctly rende exports[`renderToHTML should properly handle context data should correctly render jsx with context data and arrays in between elements 1`] = ` " - - + + Page Title - - + +

This title is set via the context

- + " @@ -255,17 +255,17 @@ exports[`renderToHTML should properly handle context data should correctly rende exports[`renderToHTML should properly handle context data should correctly render jsx with context data and async components 1`] = ` " - - + + Page Title - - + +

This title is set via the context

- + " diff --git a/src/html-parser/jsx-elem-to-html.ts b/src/html-parser/jsx-elem-to-html.ts index bc24ec8..470d414 100644 --- a/src/html-parser/jsx-elem-to-html.ts +++ b/src/html-parser/jsx-elem-to-html.ts @@ -1,6 +1,7 @@ import { ComponentApi } from "../component-api/component-api"; import { ErrorBoundary } from "../error-boundary/error-boundary"; import { join } from "../utilities/join"; +import { SELF_CLOSING_TAG_LIST } from "../utilities/self-closing-tag-list"; import { mapAttributesToHtmlTagString } from "./attribute-to-html-tag-string"; import { getHTMLStruct } from "./get-html-struct"; @@ -104,6 +105,9 @@ export const jsxElemToHtmlSync = ( } return join(results); } else { + const isSelfClosingTag = + htmlStruct.children.length === 0 && + SELF_CLOSING_TAG_LIST.includes(htmlStruct.tag); const inlineTag = htmlStruct.children.length === 0 || htmlStruct.children.every(isTextNode); @@ -113,9 +117,22 @@ export const jsxElemToHtmlSync = ( mapAttributesToHtmlTagString(htmlStruct.attributes), " ", ); + + const separator = attrString.length ? " " : ""; + + if (isSelfClosingTag) { + return ( + `${indentPadding}<${htmlStruct.tag}` + + separator + + join(mapAttributesToHtmlTagString(htmlStruct.attributes), " ") + + separator + + "/>" + ); + } + const startTag = `${indentPadding}<${htmlStruct.tag}` + - (attrString.length ? " " : "") + + separator + join(mapAttributesToHtmlTagString(htmlStruct.attributes), " ") + ">"; const endTag = `${inlineTag ? "" : indentPadding}`; @@ -214,6 +231,9 @@ export const jsxElemToHtmlAsync = async ( } return join(results); } else { + const isSelfClosingTag = + htmlStruct.children.length === 0 && + SELF_CLOSING_TAG_LIST.includes(htmlStruct.tag); const inlineTag = htmlStruct.children.length === 0 || htmlStruct.children.every(isTextNode); @@ -223,11 +243,21 @@ export const jsxElemToHtmlAsync = async ( mapAttributesToHtmlTagString(htmlStruct.attributes), " ", ); + + const separator = attrString.length ? " " : ""; + + if (isSelfClosingTag) { + return ( + `${indentPadding}<${htmlStruct.tag}` + + separator + + join(mapAttributesToHtmlTagString(htmlStruct.attributes), " ") + + separator + + "/>" + ); + } + const startTag = - `${indentPadding}<${htmlStruct.tag}` + - (attrString.length ? " " : "") + - attrString + - ">"; + `${indentPadding}<${htmlStruct.tag}` + separator + attrString + ">"; const endTag = `${inlineTag ? "" : indentPadding}`; const children: string[] = []; diff --git a/src/string-template-parser/jsx-elem-to-strings.ts b/src/string-template-parser/jsx-elem-to-strings.ts index 433d49d..ede3bb7 100644 --- a/src/string-template-parser/jsx-elem-to-strings.ts +++ b/src/string-template-parser/jsx-elem-to-strings.ts @@ -1,6 +1,7 @@ import { ComponentApi } from "../component-api/component-api"; import { ErrorBoundary } from "../error-boundary/error-boundary"; import { createElement } from "../jsx-runtime"; +import { SELF_CLOSING_TAG_LIST } from "../utilities/self-closing-tag-list"; import { Interpolate, InterpolateTag } from "./interpolate"; import { mapAttributeName } from "./map-attribute-name"; import type { StringTemplateParserOptions } from "./render-to-string-template-tag"; @@ -136,13 +137,12 @@ export const jsxElemToTagFuncArgsSync = ( return results; } else { - const results: TagFunctionArgs = [[], []]; + const isSelfClosingTag = + children.length === 0 && SELF_CLOSING_TAG_LIST.includes(element.tag); - const part1 = `<${element.tag}`; - const part2 = ">"; - const part3 = ``; + const results: TagFunctionArgs = [[], []]; - results[0].push(part1); + results[0].push(`<${element.tag}`); const attrList = Object.entries(attributes); for (let index = 0; index < attrList.length; index++) { @@ -161,24 +161,28 @@ export const jsxElemToTagFuncArgsSync = ( results[0].push('"'); } - concatToLastStringOrPush(results, part2); + if (isSelfClosingTag) { + concatToLastStringOrPush(results, "/>"); + } else { + concatToLastStringOrPush(results, ">"); + + for (let i = 0; i < children.length; i++) { + const child = children[i]!; + const [[first, ...strings], tagParams] = jsxElemToTagFuncArgsSync( + child, + options, + componentApi, + ); - for (let i = 0; i < children.length; i++) { - const child = children[i]!; - const [[first, ...strings], tagParams] = jsxElemToTagFuncArgsSync( - child, - options, - componentApi, - ); + concatToLastStringOrPush(results, first); - concatToLastStringOrPush(results, first); + results[0] = results[0].concat(strings); + results[1] = results[1].concat(tagParams); + } - results[0] = results[0].concat(strings); - results[1] = results[1].concat(tagParams); + concatToLastStringOrPush(results, ``); } - concatToLastStringOrPush(results, part3); - return results; } } diff --git a/src/utilities/self-closing-tag-list.ts b/src/utilities/self-closing-tag-list.ts new file mode 100644 index 0000000..9192f8a --- /dev/null +++ b/src/utilities/self-closing-tag-list.ts @@ -0,0 +1,16 @@ +export const SELF_CLOSING_TAG_LIST = [ + "area", + "base", + "br", + "col", + "embed", + "hr", + "img", + "input", + "link", + "meta", + "param", + "source", + "track", + "wbr", +];