diff --git a/.changeset/wise-lions-lay.md b/.changeset/wise-lions-lay.md new file mode 100644 index 000000000000..5718740feb11 --- /dev/null +++ b/.changeset/wise-lions-lay.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fix transition attributes on islands diff --git a/packages/astro/src/runtime/server/hydration.ts b/packages/astro/src/runtime/server/hydration.ts index 09f42a9b5ca4..8c56351055bc 100644 --- a/packages/astro/src/runtime/server/hydration.ts +++ b/packages/astro/src/runtime/server/hydration.ts @@ -12,13 +12,16 @@ export interface HydrationMetadata { directive: string; value: string; componentUrl: string; - componentExport: { value: string }; + componentExport: { value: string; }; } +type Props = Record; + interface ExtractedProps { isPage: boolean; hydration: HydrationMetadata | null; - props: Record; + props: Props; + propsWithoutTransitionAttributes: Props; } const transitionDirectivesToCopyOnIsland = Object.freeze([ @@ -26,16 +29,18 @@ const transitionDirectivesToCopyOnIsland = Object.freeze([ 'data-astro-transition-persist', ]); + // Used to extract the directives, aka `client:load` information about a component. // Finds these special props and removes them from what gets passed into the component. export function extractDirectives( - inputProps: Record, + inputProps: Props, clientDirectives: SSRResult['clientDirectives'] ): ExtractedProps { let extracted: ExtractedProps = { isPage: false, hydration: null, props: {}, + propsWithoutTransitionAttributes: {}, }; for (const [key, value] of Object.entries(inputProps)) { if (key.startsWith('server:')) { @@ -96,10 +101,14 @@ export function extractDirectives( } } else { extracted.props[key] = value; + if (!transitionDirectivesToCopyOnIsland.includes(key)) { + extracted.propsWithoutTransitionAttributes[key] = value; + } } } for (const sym of Object.getOwnPropertySymbols(inputProps)) { extracted.props[sym] = inputProps[sym]; + extracted.propsWithoutTransitionAttributes[sym] = inputProps[sym]; } return extracted; diff --git a/packages/astro/src/runtime/server/render/component.ts b/packages/astro/src/runtime/server/render/component.ts index 158a88a07897..91d74e8123b1 100644 --- a/packages/astro/src/runtime/server/render/component.ts +++ b/packages/astro/src/runtime/server/render/component.ts @@ -92,7 +92,7 @@ async function renderFrameworkComponent( displayName, }; - const { hydration, isPage, props } = extractDirectives(_props, clientDirectives); + const { hydration, isPage, props, propsWithoutTransitionAttributes } = extractDirectives(_props, clientDirectives); let html = ''; let attrs: Record | undefined = undefined; @@ -217,7 +217,7 @@ async function renderFrameworkComponent( ({ html, attrs } = await renderer.ssr.renderToStaticMarkup.call( { result }, Component, - props, + propsWithoutTransitionAttributes, children, metadata )); @@ -242,7 +242,7 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr ({ html, attrs } = await renderer.ssr.renderToStaticMarkup.call( { result }, Component, - props, + propsWithoutTransitionAttributes, children, metadata )); diff --git a/packages/astro/test/fixtures/view-transitions/astro.config.mjs b/packages/astro/test/fixtures/view-transitions/astro.config.mjs new file mode 100644 index 000000000000..8a6f1951c9c2 --- /dev/null +++ b/packages/astro/test/fixtures/view-transitions/astro.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'astro/config'; +import react from '@astrojs/react'; + +// https://astro.build/config +export default defineConfig({ + integrations: [react()], +}); diff --git a/packages/astro/test/fixtures/view-transitions/package.json b/packages/astro/test/fixtures/view-transitions/package.json index bc6790df4328..41cee5081cd4 100644 --- a/packages/astro/test/fixtures/view-transitions/package.json +++ b/packages/astro/test/fixtures/view-transitions/package.json @@ -3,6 +3,9 @@ "version": "0.0.0", "private": true, "dependencies": { - "astro": "workspace:*" + "astro": "workspace:*", + "@astrojs/react": "workspace:*", + "react": "^18.1.0", + "react-dom": "^18.1.0" } } diff --git a/packages/astro/test/fixtures/view-transitions/src/components/Island.css b/packages/astro/test/fixtures/view-transitions/src/components/Island.css new file mode 100644 index 000000000000..fb21044d78cc --- /dev/null +++ b/packages/astro/test/fixtures/view-transitions/src/components/Island.css @@ -0,0 +1,11 @@ +.counter { + display: grid; + font-size: 2em; + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-top: 2em; + place-items: center; +} + +.counter-message { + text-align: center; +} diff --git a/packages/astro/test/fixtures/view-transitions/src/components/Island.jsx b/packages/astro/test/fixtures/view-transitions/src/components/Island.jsx new file mode 100644 index 000000000000..cde38498028b --- /dev/null +++ b/packages/astro/test/fixtures/view-transitions/src/components/Island.jsx @@ -0,0 +1,19 @@ +import React, { useState } from 'react'; +import './Island.css'; + +export default function Counter({ children, count: initialCount, id }) { + const [count, setCount] = useState(initialCount); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( + <> +
+ +
{count}
+ +
+
{children}
+ + ); +} diff --git a/packages/astro/test/fixtures/view-transitions/src/pages/hasIsland.astro b/packages/astro/test/fixtures/view-transitions/src/pages/hasIsland.astro new file mode 100644 index 000000000000..378afb180c6c --- /dev/null +++ b/packages/astro/test/fixtures/view-transitions/src/pages/hasIsland.astro @@ -0,0 +1,10 @@ +--- +import Island from '../components/Island.jsx'; +--- + + + + + + + diff --git a/packages/astro/test/view-transitions.test.js b/packages/astro/test/view-transitions.test.js index 6d4d19cbbf16..008a6e09f73a 100644 --- a/packages/astro/test/view-transitions.test.js +++ b/packages/astro/test/view-transitions.test.js @@ -22,4 +22,14 @@ describe('View Transitions styles', () => { expect($('head style')).to.have.a.lengthOf(3); }); + + it('should not duplicate transition attributes on island contents', async () => { + let res = await fixture.fetch('/hasIsland'); + let html = await res.text(); + let $ = cheerio.load(html); + expect($('astro-island[data-astro-transition-persist]')).to.have.a.lengthOf(1); + expect( + $('astro-island[data-astro-transition-persist] > [data-astro-transition-persist]') + ).to.have.a.lengthOf(0); + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d70075993d32..c398755668fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3531,9 +3531,18 @@ importers: packages/astro/test/fixtures/view-transitions: dependencies: + '@astrojs/react': + specifier: workspace:* + version: link:../../../../integrations/react astro: specifier: workspace:* version: link:../../.. + react: + specifier: ^18.1.0 + version: 18.2.0 + react-dom: + specifier: ^18.1.0 + version: 18.2.0(react@18.2.0) packages/astro/test/fixtures/virtual-astro-file: dependencies: