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 --- .../test/fixtures/react-component/astro.config.mjs | 10 ++ .../test/fixtures/react-component/package.json | 13 ++ .../src/components/ArrowFunction.jsx | 5 + .../src/components/CloneElement.jsx | 6 + .../src/components/ForgotImport.jsx | 3 + .../react-component/src/components/GetSearch.jsx | 7 + .../react-component/src/components/Goodbye.vue | 11 ++ .../react-component/src/components/Hello.jsx | 5 + .../src/components/ImportsThrowsAnError.jsx | 7 + .../src/components/LazyComponent.jsx | 9 ++ .../src/components/PragmaComment.jsx | 5 + .../src/components/PragmaCommentTypeScript.tsx | 5 + .../react-component/src/components/PropsSpread.jsx | 5 + .../react-component/src/components/Pure.jsx | 13 ++ .../react-component/src/components/Research.jsx | 7 + .../react-component/src/components/Suspense.jsx | 14 ++ .../src/components/ThrowsAnError.jsx | 15 ++ .../src/components/TypeScriptComponent.tsx | 5 + .../src/components/WithChildren.jsx | 10 ++ .../react-component/src/components/WithId.jsx | 6 + .../react-component/src/pages/children.astro | 14 ++ .../src/pages/error-rendering.astro | 11 ++ .../fixtures/react-component/src/pages/index.astro | 41 +++++ .../react-component/src/pages/pragma-comment.astro | 14 ++ .../react-component/src/pages/suspense.astro | 17 ++ .../src/skipped-pages/forgot-import.astro | 12 ++ .../react-component/src/skipped-pages/window.astro | 8 + .../react/test/react-component.test.js | 171 +++++++++++++++++++++ 28 files changed, 449 insertions(+) create mode 100644 packages/integrations/react/test/fixtures/react-component/astro.config.mjs create mode 100644 packages/integrations/react/test/fixtures/react-component/package.json create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/ArrowFunction.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/CloneElement.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/ForgotImport.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/GetSearch.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/Goodbye.vue create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/Hello.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/ImportsThrowsAnError.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/LazyComponent.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/PragmaComment.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/PragmaCommentTypeScript.tsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/PropsSpread.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/Pure.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/Research.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/Suspense.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/ThrowsAnError.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/TypeScriptComponent.tsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/WithChildren.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/components/WithId.jsx create mode 100644 packages/integrations/react/test/fixtures/react-component/src/pages/children.astro create mode 100644 packages/integrations/react/test/fixtures/react-component/src/pages/error-rendering.astro create mode 100644 packages/integrations/react/test/fixtures/react-component/src/pages/index.astro create mode 100644 packages/integrations/react/test/fixtures/react-component/src/pages/pragma-comment.astro create mode 100644 packages/integrations/react/test/fixtures/react-component/src/pages/suspense.astro create mode 100644 packages/integrations/react/test/fixtures/react-component/src/skipped-pages/forgot-import.astro create mode 100644 packages/integrations/react/test/fixtures/react-component/src/skipped-pages/window.astro create mode 100644 packages/integrations/react/test/react-component.test.js (limited to 'packages/integrations/react/test') diff --git a/packages/integrations/react/test/fixtures/react-component/astro.config.mjs b/packages/integrations/react/test/fixtures/react-component/astro.config.mjs new file mode 100644 index 000000000..cd54d60f8 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/astro.config.mjs @@ -0,0 +1,10 @@ +import { defineConfig } from 'astro/config'; +import react from '@astrojs/react'; +import vue from '@astrojs/vue'; + +// https://astro.build/config +export default defineConfig({ + integrations: [react({ + experimentalReactChildren: true, + }), vue()], +}); diff --git a/packages/integrations/react/test/fixtures/react-component/package.json b/packages/integrations/react/test/fixtures/react-component/package.json new file mode 100644 index 000000000..cf7b2b057 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/package.json @@ -0,0 +1,13 @@ +{ + "name": "@test/react-component", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/react": "workspace:*", + "@astrojs/vue": "workspace:*", + "astro": "workspace:*", + "react": "^18.1.0", + "react-dom": "^18.1.0", + "vue": "^3.3.4" + } +} diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/ArrowFunction.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/ArrowFunction.jsx new file mode 100644 index 000000000..16fac5bb6 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/ArrowFunction.jsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export default () => { + return
; +} diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/CloneElement.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/CloneElement.jsx new file mode 100644 index 000000000..809ac4aa4 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/CloneElement.jsx @@ -0,0 +1,6 @@ +import { cloneElement } from 'react'; + +const ClonedWithProps = (element) => (props) => + cloneElement(element, props); + +export default ClonedWithProps(
Cloned With Props
); diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/ForgotImport.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/ForgotImport.jsx new file mode 100644 index 000000000..9ee27faca --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/ForgotImport.jsx @@ -0,0 +1,3 @@ +export default function ({}) { + return

oops

; +} diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/GetSearch.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/GetSearch.jsx new file mode 100644 index 000000000..d3fee2f9a --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/GetSearch.jsx @@ -0,0 +1,7 @@ +import React from 'react'; + +function GetSearch() { + return (
{window.location.search}
); +} + +export default GetSearch diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/Goodbye.vue b/packages/integrations/react/test/fixtures/react-component/src/components/Goodbye.vue new file mode 100644 index 000000000..430dfdb71 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/Goodbye.vue @@ -0,0 +1,11 @@ + + + diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/Hello.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/Hello.jsx new file mode 100644 index 000000000..4c241162d --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/Hello.jsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export default function ({ name, unused }) { + return

Hello {name}!

; +} diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/ImportsThrowsAnError.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/ImportsThrowsAnError.jsx new file mode 100644 index 000000000..d6ff21dc3 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/ImportsThrowsAnError.jsx @@ -0,0 +1,7 @@ +import ThrowsAnError from "./ThrowsAnError"; + +export default function() { + return <> + + +} diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/LazyComponent.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/LazyComponent.jsx new file mode 100644 index 000000000..b43aa36be --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/LazyComponent.jsx @@ -0,0 +1,9 @@ +import React from 'react'; + +export const LazyComponent = () => { + return ( + inner content + ); +}; + +export default LazyComponent; diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/PragmaComment.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/PragmaComment.jsx new file mode 100644 index 000000000..d8ea77810 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/PragmaComment.jsx @@ -0,0 +1,5 @@ +/** @jsxImportSource react */ + +export default function() { + return
Hello world
; +} diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/PragmaCommentTypeScript.tsx b/packages/integrations/react/test/fixtures/react-component/src/components/PragmaCommentTypeScript.tsx new file mode 100644 index 000000000..9f2256fbf --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/PragmaCommentTypeScript.tsx @@ -0,0 +1,5 @@ +/** @jsxImportSource react */ + +export default function({}: object) { + return
Hello world
; +} diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/PropsSpread.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/PropsSpread.jsx new file mode 100644 index 000000000..044c2a019 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/PropsSpread.jsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export default (props) => { + return
{props.text}
; +} diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/Pure.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/Pure.jsx new file mode 100644 index 000000000..6fae8613b --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/Pure.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +export default class StaticComponent extends React.PureComponent { + + render() { + return ( +
+

Static component

+
+ ) + } + +} \ No newline at end of file diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/Research.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/Research.jsx new file mode 100644 index 000000000..9ab83e5f3 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/Research.jsx @@ -0,0 +1,7 @@ +import * as React from 'react' + +export function Research2() { + const [value] = React.useState(1) + + return
foo bar {value}
+} \ No newline at end of file diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/Suspense.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/Suspense.jsx new file mode 100644 index 000000000..87dc82625 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/Suspense.jsx @@ -0,0 +1,14 @@ +import React, { Suspense } from 'react'; +const LazyComponent = React.lazy(() => import('./LazyComponent.jsx')); + +export const ParentComponent = () => { + return ( +
+ + + +
+ ); +}; + +export default ParentComponent; diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/ThrowsAnError.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/ThrowsAnError.jsx new file mode 100644 index 000000000..cf970e38c --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/ThrowsAnError.jsx @@ -0,0 +1,15 @@ +import { useState } from 'react'; + +export default function() { + let player = undefined; + // This is tested in dev mode, so make it work during the build to prevent + // breaking other tests. + if(import.meta.env.MODE === 'production') { + player = {}; + } + const [] = useState(player.currentTime || null); + + return ( +
Should have thrown
+ ) +} diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/TypeScriptComponent.tsx b/packages/integrations/react/test/fixtures/react-component/src/components/TypeScriptComponent.tsx new file mode 100644 index 000000000..bde96da84 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/TypeScriptComponent.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export default function({}) { + return
Hello world
; +} diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/WithChildren.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/WithChildren.jsx new file mode 100644 index 000000000..500c0c694 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/WithChildren.jsx @@ -0,0 +1,10 @@ +import React from 'react'; + +export default function ({ children }) { + return ( +
+
{children}
+
{children.length}
+
+ ); +} diff --git a/packages/integrations/react/test/fixtures/react-component/src/components/WithId.jsx b/packages/integrations/react/test/fixtures/react-component/src/components/WithId.jsx new file mode 100644 index 000000000..0abe91c72 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/components/WithId.jsx @@ -0,0 +1,6 @@ +import React from 'react'; + +export default function () { + const id = React.useId(); + return

{id}

; +} diff --git a/packages/integrations/react/test/fixtures/react-component/src/pages/children.astro b/packages/integrations/react/test/fixtures/react-component/src/pages/children.astro new file mode 100644 index 000000000..59595c266 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/pages/children.astro @@ -0,0 +1,14 @@ +--- +import WithChildren from '../components/WithChildren'; +--- + + + + + + + +
child 1
child 2
+
+ + diff --git a/packages/integrations/react/test/fixtures/react-component/src/pages/error-rendering.astro b/packages/integrations/react/test/fixtures/react-component/src/pages/error-rendering.astro new file mode 100644 index 000000000..6984a6da5 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/pages/error-rendering.astro @@ -0,0 +1,11 @@ +--- +import ImportsThrowsAnError from '../components/ImportsThrowsAnError'; +--- + + + Testing + + + + + diff --git a/packages/integrations/react/test/fixtures/react-component/src/pages/index.astro b/packages/integrations/react/test/fixtures/react-component/src/pages/index.astro new file mode 100644 index 000000000..3afd8233f --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/pages/index.astro @@ -0,0 +1,41 @@ +--- +import Hello from '../components/Hello.jsx'; +import Later from '../components/Goodbye.vue'; +import ArrowFunction from '../components/ArrowFunction.jsx'; +import PropsSpread from '../components/PropsSpread.jsx'; +import {Research2} from '../components/Research.jsx'; +import Pure from '../components/Pure.jsx'; +import TypeScriptComponent from '../components/TypeScriptComponent'; +import CloneElement from '../components/CloneElement'; +import WithChildren from '../components/WithChildren'; +import WithId from '../components/WithId'; + +const someProps = { + text: 'Hello world!', +}; +--- + + + + + + + + + + + + + + + + + + + + test + + + + + diff --git a/packages/integrations/react/test/fixtures/react-component/src/pages/pragma-comment.astro b/packages/integrations/react/test/fixtures/react-component/src/pages/pragma-comment.astro new file mode 100644 index 000000000..b3ddba639 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/pages/pragma-comment.astro @@ -0,0 +1,14 @@ +--- +import PragmaComponent from '../components/PragmaComment.jsx'; +import PragmaComponentTypeScript from '../components/PragmaCommentTypeScript.tsx'; +--- + + + + React component works with Pragma comment + + + + + + diff --git a/packages/integrations/react/test/fixtures/react-component/src/pages/suspense.astro b/packages/integrations/react/test/fixtures/react-component/src/pages/suspense.astro new file mode 100644 index 000000000..5a9d15644 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/pages/suspense.astro @@ -0,0 +1,17 @@ +--- +import Suspense from '../components/Suspense.jsx'; +--- + + + + + + +
+ +
+
+ +
+ + diff --git a/packages/integrations/react/test/fixtures/react-component/src/skipped-pages/forgot-import.astro b/packages/integrations/react/test/fixtures/react-component/src/skipped-pages/forgot-import.astro new file mode 100644 index 000000000..de5d319d9 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/skipped-pages/forgot-import.astro @@ -0,0 +1,12 @@ +--- +import ForgotImport from '../components/ForgotImport.jsx'; +--- + + + + Here we are + + + + + \ No newline at end of file diff --git a/packages/integrations/react/test/fixtures/react-component/src/skipped-pages/window.astro b/packages/integrations/react/test/fixtures/react-component/src/skipped-pages/window.astro new file mode 100644 index 000000000..e780f3c44 --- /dev/null +++ b/packages/integrations/react/test/fixtures/react-component/src/skipped-pages/window.astro @@ -0,0 +1,8 @@ +--- +import GetSearch from '../components/GetSearch.jsx'; +--- + + + + + diff --git a/packages/integrations/react/test/react-component.test.js b/packages/integrations/react/test/react-component.test.js new file mode 100644 index 000000000..da7fa018a --- /dev/null +++ b/packages/integrations/react/test/react-component.test.js @@ -0,0 +1,171 @@ +import { expect } from 'chai'; +import { load as cheerioLoad } from 'cheerio'; +import { isWindows, loadFixture } from '../../../astro/test/test-utils.js'; + +let fixture; + +describe('React Components', () => { + before(async () => { + fixture = await loadFixture({ + root: new URL('./fixtures/react-component/', import.meta.url), + }); + }); + + describe('build', () => { + before(async () => { + await fixture.build(); + }); + + it('Can load React', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerioLoad(html); + + // test 1: basic component renders + expect($('#react-static').text()).to.equal('Hello static!'); + + // test 2: no reactroot + expect($('#react-static').attr('data-reactroot')).to.equal(undefined); + + // test 3: Can use function components + expect($('#arrow-fn-component')).to.have.lengthOf(1); + + // test 4: Can use spread for components + expect($('#component-spread-props')).to.have.lengthOf(1); + + // test 5: spread props renders + expect($('#component-spread-props').text(), 'Hello world!'); + + // test 6: Can use TS components + expect($('.ts-component')).to.have.lengthOf(1); + + // test 7: Can use Pure components + expect($('#pure')).to.have.lengthOf(1); + + // test 8: Check number of islands + expect($('astro-island[uid]')).to.have.lengthOf(9); + + // test 9: Check island deduplication + const uniqueRootUIDs = new Set($('astro-island').map((i, el) => $(el).attr('uid'))); + expect(uniqueRootUIDs.size).to.equal(8); + + // 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()); + + // test 11: Should generate unique React.useId per island + const islandsWithId = $('.react-use-id'); + expect(islandsWithId).to.have.lengthOf(2); + expect($(islandsWithId[0]).attr('id')).to.not.equal($(islandsWithId[1]).attr('id')); + }); + + it('Can load Vue', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerioLoad(html); + expect($('#vue-h2').text()).to.equal('Hasta la vista, baby'); + }); + + it('Can use a pragma comment', async () => { + const html = await fixture.readFile('/pragma-comment/index.html'); + const $ = cheerioLoad(html); + + // test 1: rendered the PragmaComment component + expect($('.pragma-comment')).to.have.lengthOf(2); + }); + + // TODO: is this still a relevant test? + it.skip('Includes reactroot on hydrating components', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerioLoad(html); + + const div = $('#research'); + + // test 1: has the hydration attr + expect(div.attr('data-reactroot')).to.be.ok; + + // test 2: renders correctly + expect(div.html()).to.equal('foo bar 1'); + }); + + it('Can load Suspense-using components', async () => { + const html = await fixture.readFile('/suspense/index.html'); + const $ = cheerioLoad(html); + expect($('#client #lazy')).to.have.lengthOf(1); + expect($('#server #lazy')).to.have.lengthOf(1); + }); + + it('Can pass through props with cloneElement', async () => { + const html = await fixture.readFile('/index.html'); + const $ = cheerioLoad(html); + expect($('#cloned').text()).to.equal('Cloned With Props'); + }); + + 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'); + }) + }); + + if (isWindows) return; + + describe('dev', () => { + /** @type {import('../../../astro/test/test-utils.js').Fixture} */ + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('scripts proxy correctly', async () => { + const html = await fixture.fetch('/').then((res) => res.text()); + const $ = cheerioLoad(html); + + for (const script of $('script').toArray()) { + const { src } = script.attribs; + if (!src) continue; + expect((await fixture.fetch(src)).status, `404: ${src}`).to.equal(200); + } + }); + + // TODO: move this to separate dev test? + it.skip('Throws helpful error message on window SSR', async () => { + const html = await fixture.fetch('/window/index.html'); + expect(html).to.include( + `[/window] + The window object is not available during server-side rendering (SSR). + Try using \`import.meta.env.SSR\` to write SSR-friendly code. + https://docs.astro.build/reference/api-reference/#importmeta` + ); + }); + + // In moving over to Vite, the jsx-runtime import is now obscured. TODO: update the method of finding this. + it.skip('uses the new JSX transform', async () => { + const html = await fixture.fetch('/index.html'); + + // Grab the imports + const exp = /import\("(.+?)"\)/g; + let match, componentUrl; + while ((match = exp.exec(html))) { + if (match[1].includes('Research.js')) { + componentUrl = match[1]; + break; + } + } + const component = await fixture.readFile(componentUrl); + const jsxRuntime = component.imports.filter((i) => i.specifier.includes('jsx-runtime')); + + // test 1: react/jsx-runtime is used for the component + expect(jsxRuntime).to.be.ok; + }); + + it('When a nested component throws it does not crash the server', async () => { + const res = await fixture.fetch('/error-rendering'); + await res.arrayBuffer(); + }); + }); +}); -- 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/test') 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