From 3d525efc95cfb2deb5d9e04856d02965d66901c9 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 17 May 2023 10:18:04 -0400 Subject: [PATCH] Prevent removal of nested slots within islands (#7093) * Prevent removal of nested slots within islands * Fix build errors --- .changeset/unlucky-lamps-remember.md | 24 +++++++ packages/astro/src/@types/astro.ts | 2 + .../src/runtime/server/render/component.ts | 20 +++++- packages/astro/src/vite-plugin-jsx/index.ts | 2 +- .../astro/test/astro-slots-nested.test.js | 35 +++++++++++ .../astro-slots-nested/astro.config.mjs | 12 +++- .../fixtures/astro-slots-nested/package.json | 10 ++- .../src/components/Inner.tsx | 2 + .../src/components/PassesChildren.tsx | 5 ++ .../src/components/PassesChildrenP.tsx | 5 ++ .../src/components/PassesChildrenS.tsx | 5 ++ .../src/components/PassesChildrenSv.svelte | 3 + .../src/components/PassesChildrenV.vue | 5 ++ .../src/pages/server-component-nested.astro | 62 +++++++++++++++++++ packages/integrations/preact/src/server.ts | 24 +++++-- .../integrations/preact/src/static-html.ts | 11 +++- packages/integrations/react/server-v17.js | 7 ++- packages/integrations/react/server.js | 17 ++++- packages/integrations/react/static-html.js | 5 +- packages/integrations/solid/src/server.ts | 7 ++- packages/integrations/svelte/server.js | 11 +++- packages/integrations/vue/server.js | 10 ++- packages/integrations/vue/static-html.js | 6 +- pnpm-lock.yaml | 24 +++++++ 24 files changed, 288 insertions(+), 26 deletions(-) create mode 100644 .changeset/unlucky-lamps-remember.md create mode 100644 packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildren.tsx create mode 100644 packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenP.tsx create mode 100644 packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenS.tsx create mode 100644 packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenSv.svelte create mode 100644 packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenV.vue create mode 100644 packages/astro/test/fixtures/astro-slots-nested/src/pages/server-component-nested.astro diff --git a/.changeset/unlucky-lamps-remember.md b/.changeset/unlucky-lamps-remember.md new file mode 100644 index 000000000000..90d0825bd5e8 --- /dev/null +++ b/.changeset/unlucky-lamps-remember.md @@ -0,0 +1,24 @@ +--- +'@astrojs/preact': minor +'@astrojs/svelte': minor +'@astrojs/react': minor +'@astrojs/solid-js': minor +'@astrojs/vue': minor +'astro': minor +--- + +Prevent removal of nested slots within islands + +This change introduces a new flag that renderers can add called `supportsAstroStaticSlot`. What this does is let Astro know that the render is sending `` as placeholder values for static (non-hydrated) slots which Astro will then remove. + +This change is completely backwards compatible, but fixes bugs caused by combining ssr-only and client-side framework components like so: + +```astro + +
+ + Nested + +
+
+``` diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 7f2974cc0356..70ac9046ccca 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -94,6 +94,7 @@ export interface AstroComponentMetadata { hydrateArgs?: any; componentUrl?: string; componentExport?: { value: string; namespace?: boolean }; + astroStaticSlot: true; } /** The flags supported by the Astro CLI */ @@ -1718,6 +1719,7 @@ export interface SSRLoadedRenderer extends AstroRenderer { html: string; attrs?: Record; }>; + supportsAstroStaticSlot?: boolean; }; } diff --git a/packages/astro/src/runtime/server/render/component.ts b/packages/astro/src/runtime/server/render/component.ts index afedd8858b1c..a4254e4d3482 100644 --- a/packages/astro/src/runtime/server/render/component.ts +++ b/packages/astro/src/runtime/server/render/component.ts @@ -54,6 +54,13 @@ function isHTMLComponent(Component: unknown) { return Component && typeof Component === 'object' && (Component as any)['astro:html']; } +const ASTRO_SLOT_EXP = /\<\/?astro-slot\b[^>]*>/g; +const ASTRO_STATIC_SLOT_EXP = /\<\/?astro-static-slot\b[^>]*>/g; +function removeStaticAstroSlot(html: string, supportsAstroStaticSlot: boolean) { + const exp = supportsAstroStaticSlot ? ASTRO_STATIC_SLOT_EXP : ASTRO_SLOT_EXP; + return html.replace(exp, ''); +} + async function renderFrameworkComponent( result: SSRResult, displayName: string, @@ -68,7 +75,10 @@ async function renderFrameworkComponent( } const { renderers, clientDirectives } = result._metadata; - const metadata: AstroComponentMetadata = { displayName }; + const metadata: AstroComponentMetadata = { + astroStaticSlot: true, + displayName + }; const { hydration, isPage, props } = extractDirectives(_props, clientDirectives); let html = ''; @@ -263,7 +273,7 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr if (isPage || renderer?.name === 'astro:jsx') { yield html; } else if (html && html.length > 0) { - yield markHTMLString(html.replace(/\<\/?astro-slot\b[^>]*>/g, '')); + yield markHTMLString(removeStaticAstroSlot(html, renderer?.ssr?.supportsAstroStaticSlot ?? false)); } else { yield ''; } @@ -288,7 +298,11 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr if (html) { if (Object.keys(children).length > 0) { for (const key of Object.keys(children)) { - if (!html.includes(key === 'default' ? `` : ``)) { + let tagName = renderer?.ssr?.supportsAstroStaticSlot ? + !!metadata.hydrate ? 'astro-slot' : 'astro-static-slot' + : 'astro-slot'; + let expectedHTML = key === 'default' ? `<${tagName}>` : `<${tagName} name="${key}">`; + if (!html.includes(expectedHTML)) { unrenderedSlots.push(key); } } diff --git a/packages/astro/src/vite-plugin-jsx/index.ts b/packages/astro/src/vite-plugin-jsx/index.ts index 8cab3db9f75e..91aa63909d2d 100644 --- a/packages/astro/src/vite-plugin-jsx/index.ts +++ b/packages/astro/src/vite-plugin-jsx/index.ts @@ -202,7 +202,7 @@ export default function jsx({ settings, logging }: AstroPluginJSXOptions): Plugi Unable to resolve a renderer that handles this file! With more than one renderer enabled, you should include an import or use a pragma comment. Add ${colors.cyan( IMPORT_STATEMENTS[defaultRendererName] || `import '${defaultRendererName}';` - )} or ${colors.cyan(`/* jsxImportSource: ${defaultRendererName} */`)} to this file. + )} or ${colors.cyan(`/** @jsxImportSource: ${defaultRendererName} */`)} to this file. ` ); return null; diff --git a/packages/astro/test/astro-slots-nested.test.js b/packages/astro/test/astro-slots-nested.test.js index 9e02388cea11..07d746292088 100644 --- a/packages/astro/test/astro-slots-nested.test.js +++ b/packages/astro/test/astro-slots-nested.test.js @@ -3,6 +3,7 @@ import * as cheerio from 'cheerio'; import { loadFixture } from './test-utils.js'; describe('Nested Slots', () => { + /** @type {import('./test-utils').Fixture} */ let fixture; before(async () => { @@ -23,4 +24,38 @@ describe('Nested Slots', () => { const $ = cheerio.load(html); expect($('script')).to.have.a.lengthOf(1, 'script rendered'); }); + + describe('Client components nested inside server-only framework components', () => { + /** @type {cheerio.CheerioAPI} */ + let $; + before(async () => { + const html = await fixture.readFile('/server-component-nested/index.html'); + $ = cheerio.load(html); + }); + + it('react', () => { + expect($('#react astro-slot')).to.have.a.lengthOf(1); + expect($('#react astro-static-slot')).to.have.a.lengthOf(0); + }); + + it('vue', () => { + expect($('#vue astro-slot')).to.have.a.lengthOf(1); + expect($('#vue astro-static-slot')).to.have.a.lengthOf(0); + }); + + it('preact', () => { + expect($('#preact astro-slot')).to.have.a.lengthOf(1); + expect($('#preact astro-static-slot')).to.have.a.lengthOf(0); + }); + + it('solid', () => { + expect($('#solid astro-slot')).to.have.a.lengthOf(1); + expect($('#solid astro-static-slot')).to.have.a.lengthOf(0); + }); + + it('svelte', () => { + expect($('#svelte astro-slot')).to.have.a.lengthOf(1); + expect($('#svelte astro-static-slot')).to.have.a.lengthOf(0); + }); + }); }); diff --git a/packages/astro/test/fixtures/astro-slots-nested/astro.config.mjs b/packages/astro/test/fixtures/astro-slots-nested/astro.config.mjs index 9cdfde2d7f6e..4a8807ed05e2 100644 --- a/packages/astro/test/fixtures/astro-slots-nested/astro.config.mjs +++ b/packages/astro/test/fixtures/astro-slots-nested/astro.config.mjs @@ -1,6 +1,16 @@ import { defineConfig } from 'astro/config'; import react from '@astrojs/react'; +import preact from '@astrojs/preact'; +import solid from '@astrojs/solid-js'; +import svelte from '@astrojs/svelte'; +import vue from '@astrojs/vue'; export default defineConfig({ - integrations: [react()] + integrations: [ + react(), + preact(), + solid(), + svelte(), + vue() + ] }); diff --git a/packages/astro/test/fixtures/astro-slots-nested/package.json b/packages/astro/test/fixtures/astro-slots-nested/package.json index 01d48eb6ef50..7e57372ec3bb 100644 --- a/packages/astro/test/fixtures/astro-slots-nested/package.json +++ b/packages/astro/test/fixtures/astro-slots-nested/package.json @@ -3,9 +3,17 @@ "version": "0.0.0", "private": true, "dependencies": { + "@astrojs/preact": "workspace:*", "@astrojs/react": "workspace:*", + "@astrojs/vue": "workspace:*", + "@astrojs/solid-js": "workspace:*", + "@astrojs/svelte": "workspace:*", "astro": "workspace:*", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "solid-js": "^1.7.4", + "svelte": "^3.58.0", + "vue": "^3.2.47", + "preact": "^10.13.2" } } diff --git a/packages/astro/test/fixtures/astro-slots-nested/src/components/Inner.tsx b/packages/astro/test/fixtures/astro-slots-nested/src/components/Inner.tsx index d902a0aefa68..b7cfe16a2a67 100644 --- a/packages/astro/test/fixtures/astro-slots-nested/src/components/Inner.tsx +++ b/packages/astro/test/fixtures/astro-slots-nested/src/components/Inner.tsx @@ -1,3 +1,5 @@ +import React from 'react'; + export default function Inner() { return Inner; } diff --git a/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildren.tsx b/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildren.tsx new file mode 100644 index 000000000000..e764d5867d68 --- /dev/null +++ b/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildren.tsx @@ -0,0 +1,5 @@ +import React, { Fragment } from 'react'; + +export default function PassesChildren({ children }) { + return { children }; +} diff --git a/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenP.tsx b/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenP.tsx new file mode 100644 index 000000000000..ec89ed15cbee --- /dev/null +++ b/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenP.tsx @@ -0,0 +1,5 @@ +import { h, Fragment } from 'preact'; + +export default function PassesChildren({ children }) { + return { children }; +} diff --git a/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenS.tsx b/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenS.tsx new file mode 100644 index 000000000000..d539c55dcd97 --- /dev/null +++ b/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenS.tsx @@ -0,0 +1,5 @@ +/** @jsxImportSource solid-js */ + +export default function PassesChildren({ children }) { + return <>{ children }; +} diff --git a/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenSv.svelte b/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenSv.svelte new file mode 100644 index 000000000000..5ae6ee0566a0 --- /dev/null +++ b/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenSv.svelte @@ -0,0 +1,3 @@ +
+ +
diff --git a/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenV.vue b/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenV.vue new file mode 100644 index 000000000000..27567d7fd174 --- /dev/null +++ b/packages/astro/test/fixtures/astro-slots-nested/src/components/PassesChildrenV.vue @@ -0,0 +1,5 @@ + diff --git a/packages/astro/test/fixtures/astro-slots-nested/src/pages/server-component-nested.astro b/packages/astro/test/fixtures/astro-slots-nested/src/pages/server-component-nested.astro new file mode 100644 index 000000000000..b5a3d72a0140 --- /dev/null +++ b/packages/astro/test/fixtures/astro-slots-nested/src/pages/server-component-nested.astro @@ -0,0 +1,62 @@ +--- +import PassesChildren from '../components/PassesChildren.jsx'; +import PassesChildrenP from '../components/PassesChildrenP.jsx'; +import PassesChildrenS from '../components/PassesChildrenS.jsx'; +import PassesChildrenSv from '../components/PassesChildrenSv.svelte'; +import PassesChildrenV from '../components/PassesChildrenV.vue'; +--- + + + + Testing + + +
+
+ +
+ + Inner children + +
+
+
+
+ +
+ + Inner children + +
+
+
+
+ +
+ + Inner children + +
+
+
+
+ +
+ + Inner children + +
+
+
+
+ +
+ + Inner children + +
+
+
+
+ + diff --git a/packages/integrations/preact/src/server.ts b/packages/integrations/preact/src/server.ts index 212e183cf8c1..57e08c945d39 100644 --- a/packages/integrations/preact/src/server.ts +++ b/packages/integrations/preact/src/server.ts @@ -1,3 +1,4 @@ +import type { AstroComponentMetadata } from 'astro'; import { Component as BaseComponent, h } from 'preact'; import render from 'preact-render-to-string'; import { getContext } from './context.js'; @@ -10,7 +11,7 @@ const slotName = (str: string) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w let originalConsoleError: typeof console.error; let consoleFilterRefs = 0; -function check(this: RendererContext, Component: any, props: Record, children: any) { +function check(this: RendererContext, Component: any, props: Record, children: any, ) { if (typeof Component !== 'function') return false; if (Component.prototype != null && typeof Component.prototype.render === 'function') { @@ -21,7 +22,7 @@ function check(this: RendererContext, Component: any, props: Record try { try { - const { html } = renderToStaticMarkup.call(this, Component, props, children); + const { html } = renderToStaticMarkup.call(this, Component, props, children, undefined); if (typeof html !== 'string') { return false; } @@ -38,18 +39,28 @@ function check(this: RendererContext, Component: any, props: Record } } +function shouldHydrate(metadata: AstroComponentMetadata | undefined) { + // Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot` + return metadata?.astroStaticSlot ? !!metadata.hydrate : true; +} + function renderToStaticMarkup( this: RendererContext, Component: any, props: Record, - { default: children, ...slotted }: Record + { default: children, ...slotted }: Record, + metadata: AstroComponentMetadata | undefined, ) { const ctx = getContext(this.result); const slots: Record> = {}; for (const [key, value] of Object.entries(slotted)) { const name = slotName(key); - slots[name] = h(StaticHtml, { value, name }); + slots[name] = h(StaticHtml, { + hydrate: shouldHydrate(metadata), + value, + name + }); } // Restore signals back onto props so that they will be passed as-is to components @@ -61,7 +72,9 @@ function renderToStaticMarkup( serializeSignals(ctx, props, attrs, propsMap); const html = render( - h(Component, newProps, children != null ? h(StaticHtml, { value: children }) : children) + h(Component, newProps, children != null ? h(StaticHtml, { + hydrate: shouldHydrate(metadata), + value: children}) : children) ); return { attrs, @@ -127,4 +140,5 @@ function filteredConsoleError(msg: string, ...rest: any[]) { export default { check, renderToStaticMarkup, + supportsAstroStaticSlot: true, }; diff --git a/packages/integrations/preact/src/static-html.ts b/packages/integrations/preact/src/static-html.ts index e1127d226a5e..c1e44515f8cd 100644 --- a/packages/integrations/preact/src/static-html.ts +++ b/packages/integrations/preact/src/static-html.ts @@ -1,5 +1,11 @@ import { h } from 'preact'; +type Props = { + value: string; + name?: string; + hydrate?: boolean; +} + /** * Astro passes `children` as a string of HTML, so we need * a wrapper `div` to render that content as VNodes. @@ -7,9 +13,10 @@ import { h } from 'preact'; * As a bonus, we can signal to Preact that this subtree is * entirely static and will never change via `shouldComponentUpdate`. */ -const StaticHtml = ({ value, name }: { value: string; name?: string }) => { +const StaticHtml = ({ value, name, hydrate }: Props) => { if (!value) return null; - return h('astro-slot', { name, dangerouslySetInnerHTML: { __html: value } }); + const tagName = hydrate === false ? 'astro-static-slot' : 'astro-slot'; + return h(tagName, { name, dangerouslySetInnerHTML: { __html: value } }); }; /** diff --git a/packages/integrations/react/server-v17.js b/packages/integrations/react/server-v17.js index ab5b06350bb6..551b350d59be 100644 --- a/packages/integrations/react/server-v17.js +++ b/packages/integrations/react/server-v17.js @@ -65,7 +65,11 @@ function renderToStaticMarkup(Component, props, { default: children, ...slotted }; const newChildren = children ?? props.children; if (newChildren != null) { - newProps.children = React.createElement(StaticHtml, { value: newChildren }); + newProps.children = React.createElement(StaticHtml, { + // Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot` + hydrate: metadata.astroStaticSlot ? !!metadata.hydrate : true, + value: newChildren + }); } const vnode = React.createElement(Component, newProps); let html; @@ -80,4 +84,5 @@ function renderToStaticMarkup(Component, props, { default: children, ...slotted export default { check, renderToStaticMarkup, + supportsAstroStaticSlot: true, }; diff --git a/packages/integrations/react/server.js b/packages/integrations/react/server.js index 3f7e786acf74..e84b8bf272c6 100644 --- a/packages/integrations/react/server.js +++ b/packages/integrations/react/server.js @@ -58,6 +58,11 @@ async function getNodeWritable() { return Writable; } +function needsHydration(metadata) { + // Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot` + return metadata.astroStaticSlot ? !!metadata.hydrate : true; +} + async function renderToStaticMarkup(Component, props, { default: children, ...slotted }, metadata) { let prefix; if (this && this.result) { @@ -69,7 +74,11 @@ async function renderToStaticMarkup(Component, props, { default: children, ...sl const slots = {}; for (const [key, value] of Object.entries(slotted)) { const name = slotName(key); - slots[name] = React.createElement(StaticHtml, { value, name }); + slots[name] = React.createElement(StaticHtml, { + hydrate: needsHydration(metadata), + value, + name + }); } // Note: create newProps to avoid mutating `props` before they are serialized const newProps = { @@ -78,7 +87,10 @@ async function renderToStaticMarkup(Component, props, { default: children, ...sl }; const newChildren = children ?? props.children; if (newChildren != null) { - newProps.children = React.createElement(StaticHtml, { value: newChildren }); + newProps.children = React.createElement(StaticHtml, { + hydrate: needsHydration(metadata), + value: newChildren + }); } const vnode = React.createElement(Component, newProps); const renderOptions = { @@ -182,4 +194,5 @@ async function renderToReadableStreamAsync(vnode, options) { export default { check, renderToStaticMarkup, + supportsAstroStaticSlot: true, }; diff --git a/packages/integrations/react/static-html.js b/packages/integrations/react/static-html.js index 9589aaed888b..37fda1983ff0 100644 --- a/packages/integrations/react/static-html.js +++ b/packages/integrations/react/static-html.js @@ -7,9 +7,10 @@ import { createElement as h } from 'react'; * As a bonus, we can signal to React that this subtree is * entirely static and will never change via `shouldComponentUpdate`. */ -const StaticHtml = ({ value, name }) => { +const StaticHtml = ({ value, name, hydrate }) => { if (!value) return null; - return h('astro-slot', { + const tagName = hydrate ? 'astro-slot' : 'astro-static-slot'; + return h(tagName, { name, suppressHydrationWarning: true, dangerouslySetInnerHTML: { __html: value }, diff --git a/packages/integrations/solid/src/server.ts b/packages/integrations/solid/src/server.ts index a4626d7523d9..df2c2fce2ab5 100644 --- a/packages/integrations/solid/src/server.ts +++ b/packages/integrations/solid/src/server.ts @@ -18,20 +18,22 @@ function renderToStaticMarkup( metadata?: undefined | Record ) { const renderId = metadata?.hydrate ? incrementId(getContext(this.result)) : ''; + const needsHydrate = metadata?.astroStaticSlot ? !!metadata.hydrate : true; + const tagName = needsHydrate ? 'astro-slot' : 'astro-static-slot'; const html = renderToString( () => { const slots: Record = {}; for (const [key, value] of Object.entries(slotted)) { const name = slotName(key); - slots[name] = ssr(`${value}`); + slots[name] = ssr(`<${tagName} name="${name}">${value}`); } // Note: create newProps to avoid mutating `props` before they are serialized const newProps = { ...props, ...slots, // In Solid SSR mode, `ssr` creates the expected structure for `children`. - children: children != null ? ssr(`${children}`) : children, + children: children != null ? ssr(`<${tagName}>${children}`) : children, }; return createComponent(Component, newProps); @@ -51,4 +53,5 @@ function renderToStaticMarkup( export default { check, renderToStaticMarkup, + supportsAstroStaticSlot: true, }; diff --git a/packages/integrations/svelte/server.js b/packages/integrations/svelte/server.js index 98ece3314e70..9878d3b59dcf 100644 --- a/packages/integrations/svelte/server.js +++ b/packages/integrations/svelte/server.js @@ -2,11 +2,17 @@ function check(Component) { return Component['render'] && Component['$$render']; } -async function renderToStaticMarkup(Component, props, slotted) { +function needsHydration(metadata) { + // Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot` + return metadata.astroStaticSlot ? !!metadata.hydrate : true; +} + +async function renderToStaticMarkup(Component, props, slotted, metadata) { + const tagName = needsHydration(metadata) ? 'astro-slot' : 'astro-static-slot'; const slots = {}; for (const [key, value] of Object.entries(slotted)) { slots[key] = () => - `${value}
`; + `<${tagName}${key === 'default' ? '' : ` name="${key}"`}>${value}`; } const { html } = Component.render(props, { $$slots: slots }); return { html }; @@ -15,4 +21,5 @@ async function renderToStaticMarkup(Component, props, slotted) { export default { check, renderToStaticMarkup, + supportsAstroStaticSlot: true, }; diff --git a/packages/integrations/vue/server.js b/packages/integrations/vue/server.js index 0b148d23451b..6f7cdce681e9 100644 --- a/packages/integrations/vue/server.js +++ b/packages/integrations/vue/server.js @@ -7,10 +7,15 @@ function check(Component) { return !!Component['ssrRender'] || !!Component['__ssrInlineRender']; } -async function renderToStaticMarkup(Component, props, slotted) { +async function renderToStaticMarkup(Component, props, slotted, metadata) { const slots = {}; for (const [key, value] of Object.entries(slotted)) { - slots[key] = () => h(StaticHtml, { value, name: key === 'default' ? undefined : key }); + slots[key] = () => h(StaticHtml, { + value, + name: key === 'default' ? undefined : key, + // Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot` + hydrate: metadata.astroStaticSlot ? !!metadata.hydrate : true, + }); } const app = createSSRApp({ render: () => h(Component, props, slots) }); await setup(app); @@ -21,4 +26,5 @@ async function renderToStaticMarkup(Component, props, slotted) { export default { check, renderToStaticMarkup, + supportsAstroStaticSlot: true, }; diff --git a/packages/integrations/vue/static-html.js b/packages/integrations/vue/static-html.js index a7f09eacef3e..34740f88ff06 100644 --- a/packages/integrations/vue/static-html.js +++ b/packages/integrations/vue/static-html.js @@ -10,10 +10,12 @@ const StaticHtml = defineComponent({ props: { value: String, name: String, + hydrate: Boolean, }, - setup({ name, value }) { + setup({ name, value, hydrate }) { if (!value) return () => null; - return () => h('astro-slot', { name, innerHTML: value }); + let tagName = hydrate ? 'astro-slot' : 'astro-static-slot'; + return () => h(tagName, { name, innerHTML: value }); }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc7a6d5f9b2a..6d12a75535f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2194,18 +2194,42 @@ importers: packages/astro/test/fixtures/astro-slots-nested: dependencies: + '@astrojs/preact': + specifier: workspace:* + version: link:../../../../integrations/preact '@astrojs/react': specifier: workspace:* version: link:../../../../integrations/react + '@astrojs/solid-js': + specifier: workspace:* + version: link:../../../../integrations/solid + '@astrojs/svelte': + specifier: workspace:* + version: link:../../../../integrations/svelte + '@astrojs/vue': + specifier: workspace:* + version: link:../../../../integrations/vue astro: specifier: workspace:* version: link:../../.. + preact: + specifier: ^10.13.2 + version: 10.13.2 react: specifier: ^18.2.0 version: 18.2.0 react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + solid-js: + specifier: ^1.7.4 + version: 1.7.4 + svelte: + specifier: ^3.58.0 + version: 3.58.0 + vue: + specifier: ^3.2.47 + version: 3.2.47 packages/astro/test/fixtures/before-hydration: dependencies: