From cbf2ea161b05e85eb5aaffc77c06b76f0eb2c5c5 Mon Sep 17 00:00:00 2001 From: lilnasy <69170106+lilnasy@users.noreply.github.com> Date: Mon, 20 Nov 2023 16:35:50 +0000 Subject: [PATCH 1/4] add test script --- packages/integrations/react/package.json | 6 ++++-- pnpm-lock.yaml | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/integrations/react/package.json b/packages/integrations/react/package.json index 49d357a946f7..100ac3baa5e8 100644 --- a/packages/integrations/react/package.json +++ b/packages/integrations/react/package.json @@ -42,7 +42,8 @@ "scripts": { "build": "astro-scripts build \"src/**/*.ts\" && tsc", "build:ci": "astro-scripts build \"src/**/*.ts\"", - "dev": "astro-scripts dev \"src/**/*.ts\"" + "dev": "astro-scripts dev \"src/**/*.ts\"", + "test": "mocha --exit --timeout 20000" }, "dependencies": { "@vitejs/plugin-react": "^4.0.4", @@ -57,7 +58,8 @@ "cheerio": "1.0.0-rc.12", "react": "^18.1.0", "react-dom": "^18.1.0", - "vite": "^4.4.9" + "vite": "^4.4.9", + "mocha": "^10.2.0" }, "peerDependencies": { "@types/react": "^17.0.50 || ^18.0.21", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68aed44e57be..11df35436aa6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4473,6 +4473,9 @@ importers: cheerio: specifier: 1.0.0-rc.12 version: 1.0.0-rc.12 + mocha: + specifier: ^10.2.0 + version: 10.2.0 react: specifier: ^18.1.0 version: 18.2.0 From 28911b789a3cd670c7d172c03a0ee669ddbc5e7e Mon Sep 17 00:00:00 2001 From: lilnasy <69170106+lilnasy@users.noreply.github.com> Date: Mon, 20 Nov 2023 16:36:26 +0000 Subject: [PATCH 2/4] make children `undefined` with self-closing tags --- .../react/test/parsed-react-children.test.js | 15 +++++++++++++++ packages/integrations/react/vnode-children.js | 7 +++++++ 2 files changed, 22 insertions(+) create mode 100644 packages/integrations/react/test/parsed-react-children.test.js diff --git a/packages/integrations/react/test/parsed-react-children.test.js b/packages/integrations/react/test/parsed-react-children.test.js new file mode 100644 index 000000000000..876897c95f74 --- /dev/null +++ b/packages/integrations/react/test/parsed-react-children.test.js @@ -0,0 +1,15 @@ +import { expect } from 'chai'; +import convert from "../vnode-children.js"; + +describe('experimental react children', () => { + it('has undefined as children for direct children', () => { + const [ imgVNode ] = convert(''); + expect(imgVNode.props).to.deep.include({ children: undefined }); + }) + + it('has undefined as children for nested children', () => { + const [ divVNode ] = convert('
'); + const [ imgVNode ] = divVNode.props.children; + expect(imgVNode.props).to.deep.include({ children: undefined }); + }) +}) diff --git a/packages/integrations/react/vnode-children.js b/packages/integrations/react/vnode-children.js index cc8ec351090c..0b5738c2cce6 100644 --- a/packages/integrations/react/vnode-children.js +++ b/packages/integrations/react/vnode-children.js @@ -1,6 +1,9 @@ import { parse, DOCUMENT_NODE, ELEMENT_NODE, TEXT_NODE } from 'ultrahtml'; import { createElement, Fragment } from 'react'; +// https://github.com/facebook/react/blob/bbb9cb116dbf7b6247721aa0c4bcb6ec249aa8af/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js#L3593-L3617 +const selfClosingTags = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'keygen', 'meta', 'param', 'source', 'track', 'wbr'] + let ids = 0; export default function convert(children) { let doc = parse(children.toString().trim()); @@ -8,6 +11,10 @@ export default function convert(children) { let key = 0; function createReactElementFromNode(node) { + if (node.isSelfClosingTag === true && Array.isArray(node.children) && node.children.length === 0) { + node.children = undefined; + } + const childVnodes = Array.isArray(node.children) ? node.children .map((child) => { From b8bc05936e3836c038edd23aeb4d2d98a878c893 Mon Sep 17 00:00:00 2001 From: lilnasy <69170106+lilnasy@users.noreply.github.com> Date: Mon, 20 Nov 2023 16:40:43 +0000 Subject: [PATCH 3/4] add changeset --- .changeset/three-timers-arrive.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/three-timers-arrive.md diff --git a/.changeset/three-timers-arrive.md b/.changeset/three-timers-arrive.md new file mode 100644 index 000000000000..619e7d815903 --- /dev/null +++ b/.changeset/three-timers-arrive.md @@ -0,0 +1,5 @@ +--- +'@astrojs/react': patch +--- + +Fixes an issue where slotting self-closing elements (img, br, hr) into react components with `experimentalReactChildren` enabled led to an error. From fc44ec26fba6d80127c82e2ae41e5bc6d4d3623b Mon Sep 17 00:00:00 2001 From: lilnasy <69170106+lilnasy@users.noreply.github.com> Date: Mon, 20 Nov 2023 17:07:45 +0000 Subject: [PATCH 4/4] refactor: simplify --- packages/integrations/react/vnode-children.js | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/packages/integrations/react/vnode-children.js b/packages/integrations/react/vnode-children.js index 0b5738c2cce6..5fd421e67f71 100644 --- a/packages/integrations/react/vnode-children.js +++ b/packages/integrations/react/vnode-children.js @@ -1,9 +1,6 @@ import { parse, DOCUMENT_NODE, ELEMENT_NODE, TEXT_NODE } from 'ultrahtml'; import { createElement, Fragment } from 'react'; -// https://github.com/facebook/react/blob/bbb9cb116dbf7b6247721aa0c4bcb6ec249aa8af/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js#L3593-L3617 -const selfClosingTags = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'keygen', 'meta', 'param', 'source', 'track', 'wbr'] - let ids = 0; export default function convert(children) { let doc = parse(children.toString().trim()); @@ -11,21 +8,10 @@ export default function convert(children) { let key = 0; function createReactElementFromNode(node) { - if (node.isSelfClosingTag === true && Array.isArray(node.children) && node.children.length === 0) { - node.children = undefined; - } - - const childVnodes = Array.isArray(node.children) + const childVnodes = Array.isArray(node.children) && node.children.length ? node.children - .map((child) => { - if (child.type === ELEMENT_NODE) { - return createReactElementFromNode(child); - } else if (child.type === TEXT_NODE) { - // 0-length text gets omitted in JSX - return child.value.trim() ? child.value : undefined; - } - }) - .filter((n) => !!n) + .map((child) => createReactElementFromNode(child)) + .filter(Boolean) : undefined; if (node.type === DOCUMENT_NODE) { @@ -33,6 +19,9 @@ export default function convert(children) { } else if (node.type === ELEMENT_NODE) { const { class: className, ...props } = node.attributes; return createElement(node.name, { ...props, className, key: `${id}-${key++}` }, childVnodes); + } else if (node.type === TEXT_NODE) { + // 0-length text gets omitted in JSX + return node.value.trim() ? node.value : undefined; } }