From 16a3fdf93165a1a0404c1db0973871345b2c591b Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 16 Aug 2023 13:40:57 -0400 Subject: Add experimentalReactChildren option to React integration (#8082) * wip: support true react vnodes in renderer * Add new experimentalReactChildren option to React integration * Update the test * Add docs * Update packages/integrations/react/server.js Co-authored-by: Nate Moore * Update with a better test * Update .changeset/yellow-snakes-jam.md Co-authored-by: Sarah Rainsberger * Update packages/integrations/react/README.md Co-authored-by: Sarah Rainsberger * Update packages/integrations/react/README.md Co-authored-by: Sarah Rainsberger --------- Co-authored-by: Nate Moore Co-authored-by: Nate Moore Co-authored-by: Sarah Rainsberger --- packages/integrations/react/src/index.ts | 36 +++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) (limited to 'packages/integrations/react/src') diff --git a/packages/integrations/react/src/index.ts b/packages/integrations/react/src/index.ts index d7906fe4a..556062282 100644 --- a/packages/integrations/react/src/index.ts +++ b/packages/integrations/react/src/index.ts @@ -1,5 +1,6 @@ import type { AstroIntegration } from 'astro'; import { version as ReactVersion } from 'react-dom'; +import type * as vite from 'vite'; function getRenderer() { return { @@ -36,7 +37,29 @@ function getRenderer() { }; } -function getViteConfiguration() { +function optionsPlugin(experimentalReactChildren: boolean): vite.Plugin { + const virtualModule = 'astro:react:opts'; + const virtualModuleId = '\0' + virtualModule; + return { + name: '@astrojs/react:opts', + resolveId(id) { + if(id === virtualModule) { + return virtualModuleId; + } + }, + load(id) { + if(id === virtualModuleId) { + return { + code: `export default { + experimentalReactChildren: ${JSON.stringify(experimentalReactChildren)} + }` + }; + } + } + }; +} + +function getViteConfiguration(experimentalReactChildren: boolean) { return { optimizeDeps: { include: [ @@ -70,16 +93,23 @@ function getViteConfiguration() { 'use-immer', ], }, + plugins: [ + optionsPlugin(experimentalReactChildren) + ] }; } -export default function (): AstroIntegration { +export type ReactIntegrationOptions = { + experimentalReactChildren: boolean; +} + +export default function ({ experimentalReactChildren }: ReactIntegrationOptions = { experimentalReactChildren: false }): AstroIntegration { return { name: '@astrojs/react', hooks: { 'astro:config:setup': ({ addRenderer, updateConfig }) => { addRenderer(getRenderer()); - updateConfig({ vite: getViteConfiguration() }); + updateConfig({ vite: getViteConfiguration(experimentalReactChildren) }); }, }, }; -- cgit v1.2.3 From fb31ce55d943a88ff0d922cc2e4cca8d98a35eba Mon Sep 17 00:00:00 2001 From: matthewp Date: Wed, 16 Aug 2023 17:44:01 +0000 Subject: [ci] format --- packages/integrations/react/README.md | 8 +-- packages/integrations/react/server.js | 2 +- packages/integrations/react/src/index.ts | 22 ++++---- .../react/test/react-component.test.js | 8 +-- packages/integrations/react/vnode-children.js | 63 +++++++++++----------- 5 files changed, 52 insertions(+), 51 deletions(-) (limited to 'packages/integrations/react/src') diff --git a/packages/integrations/react/README.md b/packages/integrations/react/README.md index 7504490e0..8009972b3 100644 --- a/packages/integrations/react/README.md +++ b/packages/integrations/react/README.md @@ -80,9 +80,9 @@ import ReactComponent from './ReactComponent'; ``` -If you are using a library that *expects* more than one child element element to be passed, for example so that it can slot certain elements in different places, you might find this to be a blocker. +If you are using a library that _expects_ more than one child element element to be passed, for example so that it can slot certain elements in different places, you might find this to be a blocker. -You can set the experimental flag `experimentalReactChildren` to tell Astro to always pass children to React as React vnodes. There is some runtime cost to this, but it can help with compatibility. +You can set the experimental flag `experimentalReactChildren` to tell Astro to always pass children to React as React vnodes. There is some runtime cost to this, but it can help with compatibility. You can enable this option in the configuration for the React integration: @@ -96,8 +96,8 @@ export default defineConfig({ integrations: [ react({ experimentalReactChildren: true, - }) - ], + }), + ], }); ``` diff --git a/packages/integrations/react/server.js b/packages/integrations/react/server.js index 3f0d93e97..c2400accb 100644 --- a/packages/integrations/react/server.js +++ b/packages/integrations/react/server.js @@ -87,7 +87,7 @@ async function renderToStaticMarkup(Component, props, { default: children, ...sl }; const newChildren = children ?? props.children; if (children && opts.experimentalReactChildren) { - const convert = await import('./vnode-children.js').then(mod => mod.default); + const convert = await import('./vnode-children.js').then((mod) => mod.default); newProps.children = convert(children); } else if (newChildren != null) { newProps.children = React.createElement(StaticHtml, { diff --git a/packages/integrations/react/src/index.ts b/packages/integrations/react/src/index.ts index 556062282..d1cd7c4e6 100644 --- a/packages/integrations/react/src/index.ts +++ b/packages/integrations/react/src/index.ts @@ -39,23 +39,23 @@ function getRenderer() { function optionsPlugin(experimentalReactChildren: boolean): vite.Plugin { const virtualModule = 'astro:react:opts'; - const virtualModuleId = '\0' + virtualModule; - return { + const virtualModuleId = '\0' + virtualModule; + return { name: '@astrojs/react:opts', resolveId(id) { - if(id === virtualModule) { + if (id === virtualModule) { return virtualModuleId; } }, load(id) { - if(id === virtualModuleId) { + if (id === virtualModuleId) { return { code: `export default { experimentalReactChildren: ${JSON.stringify(experimentalReactChildren)} - }` + }`, }; } - } + }, }; } @@ -93,17 +93,17 @@ function getViteConfiguration(experimentalReactChildren: boolean) { 'use-immer', ], }, - plugins: [ - optionsPlugin(experimentalReactChildren) - ] + plugins: [optionsPlugin(experimentalReactChildren)], }; } export type ReactIntegrationOptions = { experimentalReactChildren: boolean; -} +}; -export default function ({ experimentalReactChildren }: ReactIntegrationOptions = { experimentalReactChildren: false }): AstroIntegration { +export default function ( + { experimentalReactChildren }: ReactIntegrationOptions = { experimentalReactChildren: false } +): AstroIntegration { return { name: '@astrojs/react', hooks: { diff --git a/packages/integrations/react/test/react-component.test.js b/packages/integrations/react/test/react-component.test.js index da7fa018a..43df1d9e4 100644 --- a/packages/integrations/react/test/react-component.test.js +++ b/packages/integrations/react/test/react-component.test.js @@ -51,7 +51,9 @@ describe('React Components', () => { // test 10: Should properly render children passed as props const islandsWithChildren = $('.with-children'); expect(islandsWithChildren).to.have.lengthOf(2); - expect($(islandsWithChildren[0]).html()).to.equal($(islandsWithChildren[1]).find('astro-slot').html()); + expect($(islandsWithChildren[0]).html()).to.equal( + $(islandsWithChildren[1]).find('astro-slot').html() + ); // test 11: Should generate unique React.useId per island const islandsWithId = $('.react-use-id'); @@ -103,8 +105,8 @@ describe('React Components', () => { it('Children are parsed as React components, can be manipulated', async () => { const html = await fixture.readFile('/children/index.html'); const $ = cheerioLoad(html); - expect($(".with-children-count").text()).to.equal('2'); - }) + expect($('.with-children-count').text()).to.equal('2'); + }); }); if (isWindows) return; diff --git a/packages/integrations/react/vnode-children.js b/packages/integrations/react/vnode-children.js index 0c9d5e08d..9c7abe644 100644 --- a/packages/integrations/react/vnode-children.js +++ b/packages/integrations/react/vnode-children.js @@ -1,38 +1,37 @@ -import { parse, walkSync, DOCUMENT_NODE, ELEMENT_NODE, TEXT_NODE } from 'ultrahtml' +import { parse, walkSync, DOCUMENT_NODE, ELEMENT_NODE, TEXT_NODE } from 'ultrahtml'; import { createElement, Fragment } from 'react'; export default function convert(children) { - const nodeMap = new WeakMap(); - let doc = parse(children.toString().trim()); - let root = createElement(Fragment, { children: [] }); + const nodeMap = new WeakMap(); + let doc = parse(children.toString().trim()); + let root = createElement(Fragment, { children: [] }); - walkSync(doc, (node, parent, index) => { - let newNode = {}; - if (node.type === DOCUMENT_NODE) { - nodeMap.set(node, root); - } else if (node.type === ELEMENT_NODE) { - const { class: className, ...props } = node.attributes; - newNode = createElement(node.name, { ...props, className, children: [] }); - nodeMap.set(node, newNode); - if (parent) { - const newParent = nodeMap.get(parent); - newParent.props.children[index] = newNode; - - } - } else if (node.type === TEXT_NODE) { - newNode = node.value.trim(); - if (newNode.trim()) { - if (parent) { - const newParent = nodeMap.get(parent); - if (parent.children.length === 1) { - newParent.props.children[0] = newNode; - } else { - newParent.props.children[index] = newNode; - } - } - } - } - }); + walkSync(doc, (node, parent, index) => { + let newNode = {}; + if (node.type === DOCUMENT_NODE) { + nodeMap.set(node, root); + } else if (node.type === ELEMENT_NODE) { + const { class: className, ...props } = node.attributes; + newNode = createElement(node.name, { ...props, className, children: [] }); + nodeMap.set(node, newNode); + if (parent) { + const newParent = nodeMap.get(parent); + newParent.props.children[index] = newNode; + } + } else if (node.type === TEXT_NODE) { + newNode = node.value.trim(); + if (newNode.trim()) { + if (parent) { + const newParent = nodeMap.get(parent); + if (parent.children.length === 1) { + newParent.props.children[0] = newNode; + } else { + newParent.props.children[index] = newNode; + } + } + } + } + }); - return root.props.children; + return root.props.children; } -- cgit v1.2.3