From 9cd2681bdf30149f275f3e8cfcefb3cb88adb4f9 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 13 May 2022 09:30:33 -0600 Subject: [PATCH 01/15] wip: fix nested islands --- packages/astro/src/runtime/client/idle.ts | 56 ++++++++------- packages/astro/src/runtime/client/load.ts | 60 ++++++++++------ packages/astro/src/runtime/client/media.ts | 60 ++++++++-------- packages/astro/src/runtime/client/only.ts | 43 ++++++----- packages/astro/src/runtime/client/visible.ts | 76 +++++++++++--------- packages/astro/src/runtime/server/index.ts | 11 ++- packages/integrations/react/client.js | 13 ++++ 7 files changed, 192 insertions(+), 127 deletions(-) diff --git a/packages/astro/src/runtime/client/idle.ts b/packages/astro/src/runtime/client/idle.ts index e1e1c5b2f7ab..a372e6b3dc7f 100644 --- a/packages/astro/src/runtime/client/idle.ts +++ b/packages/astro/src/runtime/client/idle.ts @@ -9,35 +9,39 @@ export default async function onIdle( options: HydrateOptions, getHydrateCallback: GetHydrateCallback ) { - const cb = async () => { - const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`); - if (roots.length === 0) { - throw new Error(`Unable to find the root for the component ${options.name}`); - } - + async function idle() { + window.addEventListener('astro:hydrate', idle, { once: true }); let innerHTML: string | null = null; - let fragment = roots[0].querySelector(`astro-fragment`); - if (fragment == null && roots[0].hasAttribute('tmpl')) { - // If there is no child fragment, check to see if there is a template. - // This happens if children were passed but the client component did not render any. - let template = roots[0].querySelector(`template[data-astro-template]`); - if (template) { - innerHTML = template.innerHTML; - template.remove(); + const cb = async () => { + const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); + if (roots.length === 0) return; + if (typeof innerHTML !== 'string') { + let fragment = roots[0].querySelector(`astro-fragment`); + if (fragment == null && roots[0].hasAttribute('tmpl')) { + // If there is no child fragment, check to see if there is a template. + // This happens if children were passed but the client component did not render any. + let template = roots[0].querySelector(`template[data-astro-template]`); + if (template) { + innerHTML = template.innerHTML; + template.remove(); + } + } else if (fragment) { + innerHTML = fragment.innerHTML; + } } - } else if (fragment) { - innerHTML = fragment.innerHTML; - } - const hydrate = await getHydrateCallback(); + const hydrate = await getHydrateCallback(); - for (const root of roots) { - hydrate(root, innerHTML); - } - }; + for (const root of roots) { + hydrate(root, innerHTML); + root.removeAttribute('ssr'); + } + }; - if ('requestIdleCallback' in window) { - (window as any).requestIdleCallback(cb); - } else { - setTimeout(cb, 200); + if ('requestIdleCallback' in window) { + (window as any).requestIdleCallback(cb); + } else { + setTimeout(cb, 200); + } } + idle(); } diff --git a/packages/astro/src/runtime/client/load.ts b/packages/astro/src/runtime/client/load.ts index d969b4061b6e..72b59322db12 100644 --- a/packages/astro/src/runtime/client/load.ts +++ b/packages/astro/src/runtime/client/load.ts @@ -1,5 +1,13 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro'; +function debounce(func, timeout){ + let timer; + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => { func.apply(this, args); }, timeout); + }; +} + /** * Hydrate this component immediately! */ @@ -8,29 +16,41 @@ export default async function onLoad( options: HydrateOptions, getHydrateCallback: GetHydrateCallback ) { - const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`); - if (roots.length === 0) { - throw new Error(`Unable to find the root for the component ${options.name}`); - } - let innerHTML: string | null = null; - let fragment = roots[0].querySelector(`astro-fragment`); - if (fragment == null && roots[0].hasAttribute('tmpl')) { - // If there is no child fragment, check to see if there is a template. - // This happens if children were passed but the client component did not render any. - let template = roots[0].querySelector(`template[data-astro-template]`); - if (template) { - innerHTML = template.innerHTML; - template.remove(); + const load = debounce(async () => { + window.addEventListener('astro:hydrate', load, { once: true }); + const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); + if (roots.length === 0) return; + if (typeof innerHTML !== 'string') { + let fragment = roots[0].querySelector(`astro-fragment`); + if (fragment == null && roots[0].hasAttribute('tmpl')) { + // If there is no child fragment, check to see if there is a template. + // This happens if children were passed but the client component did not render any. + let template = roots[0].querySelector(`template[data-astro-template]`); + if (template) { + innerHTML = template.innerHTML; + template.remove(); + } + } else if (fragment) { + innerHTML = fragment.innerHTML; + } } - } else if (fragment) { - innerHTML = fragment.innerHTML; - } - //const innerHTML = roots[0].querySelector(`astro-fragment`)?.innerHTML ?? null; - const hydrate = await getHydrateCallback(); - for (const root of roots) { - hydrate(root, innerHTML); + const hydrate = await getHydrateCallback(); + for (const root of roots) { + if (root.parentElement?.closest('astro-root[ssr]')) continue; + await hydrate(root, innerHTML); + root.removeAttribute('ssr'); + } + notify(); + }, 1); + + load(); +} + +const notify = () => { + if (document.querySelector('astro-root[ssr]')) { + window.dispatchEvent(new CustomEvent('astro:hydrate')); } } diff --git a/packages/astro/src/runtime/client/media.ts b/packages/astro/src/runtime/client/media.ts index edaa9a433864..9003547c9c26 100644 --- a/packages/astro/src/runtime/client/media.ts +++ b/packages/astro/src/runtime/client/media.ts @@ -8,38 +8,42 @@ export default async function onMedia( options: HydrateOptions, getHydrateCallback: GetHydrateCallback ) { - const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`); - if (roots.length === 0) { - throw new Error(`Unable to find the root for the component ${options.name}`); - } - let innerHTML: string | null = null; - let fragment = roots[0].querySelector(`astro-fragment`); - if (fragment == null && roots[0].hasAttribute('tmpl')) { - // If there is no child fragment, check to see if there is a template. - // This happens if children were passed but the client component did not render any. - let template = roots[0].querySelector(`template[data-astro-template]`); - if (template) { - innerHTML = template.innerHTML; - template.remove(); + async function media() { + window.addEventListener('astro:hydrate', media, { once: true }) + const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); + if (roots.length === 0) return; + if (typeof innerHTML !== 'string') { + let fragment = roots[0].querySelector(`astro-fragment`); + if (fragment == null && roots[0].hasAttribute('tmpl')) { + // If there is no child fragment, check to see if there is a template. + // This happens if children were passed but the client component did not render any. + let template = roots[0].querySelector(`template[data-astro-template]`); + if (template) { + innerHTML = template.innerHTML; + template.remove(); + } + } else if (fragment) { + innerHTML = fragment.innerHTML; + } } - } else if (fragment) { - innerHTML = fragment.innerHTML; - } - const cb = async () => { - const hydrate = await getHydrateCallback(); - for (const root of roots) { - hydrate(root, innerHTML); - } - }; + const cb = async () => { + const hydrate = await getHydrateCallback(); + for (const root of roots) { + hydrate(root, innerHTML); + + } + }; - if (options.value) { - const mql = matchMedia(options.value); - if (mql.matches) { - cb(); - } else { - mql.addEventListener('change', cb, { once: true }); + if (options.value) { + const mql = matchMedia(options.value); + if (mql.matches) { + cb(); + } else { + mql.addEventListener('change', cb, { once: true }); + } } } + media(); } diff --git a/packages/astro/src/runtime/client/only.ts b/packages/astro/src/runtime/client/only.ts index 04937c6084fd..99a24776b0c9 100644 --- a/packages/astro/src/runtime/client/only.ts +++ b/packages/astro/src/runtime/client/only.ts @@ -8,27 +8,32 @@ export default async function onOnly( options: HydrateOptions, getHydrateCallback: GetHydrateCallback ) { - const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`); - if (roots.length === 0) { - throw new Error(`Unable to find the root for the component ${options.name}`); - } - let innerHTML: string | null = null; - let fragment = roots[0].querySelector(`astro-fragment`); - if (fragment == null && roots[0].hasAttribute('tmpl')) { - // If there is no child fragment, check to see if there is a template. - // This happens if children were passed but the client component did not render any. - let template = roots[0].querySelector(`template[data-astro-template]`); - if (template) { - innerHTML = template.innerHTML; - template.remove(); + async function only() { + window.addEventListener('astro:hydrate', only, { once: true }) + const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); + if (roots.length === 0) return; + + if (typeof innerHTML !== 'string') { + let fragment = roots[0].querySelector(`astro-fragment`); + if (fragment == null && roots[0].hasAttribute('tmpl')) { + // If there is no child fragment, check to see if there is a template. + // This happens if children were passed but the client component did not render any. + let template = roots[0].querySelector(`template[data-astro-template]`); + if (template) { + innerHTML = template.innerHTML; + template.remove(); + } + } else if (fragment) { + innerHTML = fragment.innerHTML; + } } - } else if (fragment) { - innerHTML = fragment.innerHTML; - } - const hydrate = await getHydrateCallback(); + const hydrate = await getHydrateCallback(); - for (const root of roots) { - hydrate(root, innerHTML); + for (const root of roots) { + hydrate(root, innerHTML); + root.removeAttribute('ssr'); + } } + only() } diff --git a/packages/astro/src/runtime/client/visible.ts b/packages/astro/src/runtime/client/visible.ts index e9c3e3310963..f0bd19e78fe7 100644 --- a/packages/astro/src/runtime/client/visible.ts +++ b/packages/astro/src/runtime/client/visible.ts @@ -10,46 +10,56 @@ export default async function onVisible( options: HydrateOptions, getHydrateCallback: GetHydrateCallback ) { - const roots = document.querySelectorAll(`astro-root[uid="${astroId}"]`); - if (roots.length === 0) { - throw new Error(`Unable to find the root for the component ${options.name}`); - } - + let io: IntersectionObserver; let innerHTML: string | null = null; - let fragment = roots[0].querySelector(`astro-fragment`); - if (fragment == null && roots[0].hasAttribute('tmpl')) { - // If there is no child fragment, check to see if there is a template. - // This happens if children were passed but the client component did not render any. - let template = roots[0].querySelector(`template[data-astro-template]`); - if (template) { - innerHTML = template.innerHTML; - template.remove(); + async function visible() { + window.addEventListener('astro:hydrate', visible, { once: true }) + const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); + if (roots.length === 0) return; + if (typeof innerHTML !== 'string') { + let fragment = roots[0].querySelector(`astro-fragment`); + if (fragment == null && roots[0].hasAttribute('tmpl')) { + // If there is no child fragment, check to see if there is a template. + // This happens if children were passed but the client component did not render any. + let template = roots[0].querySelector(`template[data-astro-template]`); + if (template) { + innerHTML = template.innerHTML; + template.remove(); + } + } else if (fragment) { + innerHTML = fragment.innerHTML; + } } - } else if (fragment) { - innerHTML = fragment.innerHTML; - } - const cb = async () => { - const hydrate = await getHydrateCallback(); - for (const root of roots) { - hydrate(root, innerHTML); - } - }; + const cb = async () => { + const hydrate = await getHydrateCallback(); + for (const root of roots) { + hydrate(root, innerHTML); + root.removeAttribute('ssr'); + } + }; - const io = new IntersectionObserver((entries) => { - for (const entry of entries) { - if (!entry.isIntersecting) continue; - // As soon as we hydrate, disconnect this IntersectionObserver for every `astro-root` + if (io) { io.disconnect(); - cb(); - break; // break loop on first match } - }); - for (const root of roots) { - for (let i = 0; i < root.children.length; i++) { - const child = root.children[i]; - io.observe(child); + io = new IntersectionObserver((entries) => { + for (const entry of entries) { + if (!entry.isIntersecting) continue; + // As soon as we hydrate, disconnect this IntersectionObserver for every `astro-root` + io.disconnect(); + cb(); + break; // break loop on first match + } + }); + + for (const root of roots) { + for (let i = 0; i < root.children.length; i++) { + const child = root.children[i]; + io.observe(child); + } } } + + visible(); } diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index 762e764ce7de..91cb9fab1e96 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -330,7 +330,7 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr const template = needsAstroTemplate ? `` : ''; return markHTMLString( - `${ + `${ html ?? '' }${template}` ); @@ -629,6 +629,15 @@ export async function renderHead(result: SSRResult): Promise { return renderElement('script', script); }); if (needsHydrationStyles) { + scripts.push(renderElement('script', { + children: `const notify = () => { + if (document.querySelector('astro-root[ssr]')) { + window.dispatchEvent(new CustomEvent('astro:hydrate')); + } +}; +new MutationObserver(() => notify()).observe(document.body, { subtree: true, childList: true })`, + props: { type: 'module' } + })); styles.push( renderElement('style', { props: {}, diff --git a/packages/integrations/react/client.js b/packages/integrations/react/client.js index fc952522be98..d5d84884745b 100644 --- a/packages/integrations/react/client.js +++ b/packages/integrations/react/client.js @@ -2,6 +2,14 @@ import { createElement } from 'react'; import { createRoot, hydrateRoot } from 'react-dom/client'; import StaticHtml from './static-html.js'; +function isAlreadyHydrated(element) { + for (const key in element) { + if (key.startsWith('__reactContainer')) { + return key; + } + } +} + export default (element) => (Component, props, children, { client }) => { const componentEl = createElement( @@ -12,5 +20,10 @@ export default (element) => if (client === 'only') { return createRoot(element).render(componentEl); } + const rootKey = isAlreadyHydrated(element); + // HACK: delete internal react marker for nested components to suppress agressive warnings + if (rootKey) { + delete element[rootKey]; + } return hydrateRoot(element, componentEl); }; From 40b487e514e02bcc32f5d00092323cc57f8d2f1d Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 26 May 2022 13:58:11 -0500 Subject: [PATCH 02/15] fix: improve hydration for dynamic content --- packages/astro/src/@types/astro.ts | 2 +- packages/astro/src/runtime/client/events.ts | 24 ++++++++++ packages/astro/src/runtime/client/idle.ts | 18 +++++--- packages/astro/src/runtime/client/load.ts | 32 ++++--------- packages/astro/src/runtime/client/media.ts | 48 +++++++++++--------- packages/astro/src/runtime/client/only.ts | 17 ++++--- packages/astro/src/runtime/client/visible.ts | 44 ++++++++++-------- packages/astro/src/runtime/server/index.ts | 9 ---- 8 files changed, 110 insertions(+), 84 deletions(-) create mode 100644 packages/astro/src/runtime/client/events.ts diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 31f9e98f3c39..c48f2e2a7903 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -763,7 +763,7 @@ export interface MarkdownInstance> { } export type GetHydrateCallback = () => Promise< - (element: Element, innerHTML: string | null) => void + (element: Element, innerHTML: string | null) => void | Promise >; /** diff --git a/packages/astro/src/runtime/client/events.ts b/packages/astro/src/runtime/client/events.ts new file mode 100644 index 000000000000..3c84dff36318 --- /dev/null +++ b/packages/astro/src/runtime/client/events.ts @@ -0,0 +1,24 @@ +const HYDRATE_KEY = `astro:hydrate`; +function debounce any>(cb: T, wait = 20) { + let h = 0; + let callable = (...args: any) => { + clearTimeout(h); + h = setTimeout(() => cb(...args), wait) as unknown as number; + }; + return callable as T; +} + +export const notify = debounce(() => { + if (document.querySelector('astro-root[ssr]')) { + window.dispatchEvent(new CustomEvent(HYDRATE_KEY)); + } +}); + +export const listen = (cb: (...args: any[]) => any) => window.addEventListener(HYDRATE_KEY, cb, { once: true }); + +if (!(window as any)[HYDRATE_KEY]) { + if ('MutationObserver' in window) { + new MutationObserver(() => notify()).observe(document.body, { subtree: true, childList: true }); + } + (window as any)[HYDRATE_KEY] = true; +} diff --git a/packages/astro/src/runtime/client/idle.ts b/packages/astro/src/runtime/client/idle.ts index a372e6b3dc7f..ff37585e6aa9 100644 --- a/packages/astro/src/runtime/client/idle.ts +++ b/packages/astro/src/runtime/client/idle.ts @@ -1,7 +1,8 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro'; +import { notify, listen } from './events'; /** - * Hydrate this component as soon as the main thread is free! + * Hydrate this component as soon as the main thread is free * (or after a short delay, if `requestIdleCallback`) isn't supported */ export default async function onIdle( @@ -9,9 +10,11 @@ export default async function onIdle( options: HydrateOptions, getHydrateCallback: GetHydrateCallback ) { + let innerHTML: string | null = null; + let hydrate: Awaited>; + async function idle() { - window.addEventListener('astro:hydrate', idle, { once: true }); - let innerHTML: string | null = null; + listen(idle) const cb = async () => { const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); if (roots.length === 0) return; @@ -29,12 +32,15 @@ export default async function onIdle( innerHTML = fragment.innerHTML; } } - const hydrate = await getHydrateCallback(); - + if (!hydrate) { + hydrate = await getHydrateCallback(); + } for (const root of roots) { - hydrate(root, innerHTML); + if (root.parentElement?.closest('astro-root[ssr]')) continue; + await hydrate(root, innerHTML); root.removeAttribute('ssr'); } + notify(); }; if ('requestIdleCallback' in window) { diff --git a/packages/astro/src/runtime/client/load.ts b/packages/astro/src/runtime/client/load.ts index 72b59322db12..80a1f4d51534 100644 --- a/packages/astro/src/runtime/client/load.ts +++ b/packages/astro/src/runtime/client/load.ts @@ -1,15 +1,8 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro'; - -function debounce(func, timeout){ - let timer; - return (...args) => { - clearTimeout(timer); - timer = setTimeout(() => { func.apply(this, args); }, timeout); - }; -} +import { notify, listen } from './events'; /** - * Hydrate this component immediately! + * Hydrate this component immediately */ export default async function onLoad( astroId: string, @@ -17,8 +10,10 @@ export default async function onLoad( getHydrateCallback: GetHydrateCallback ) { let innerHTML: string | null = null; - const load = debounce(async () => { - window.addEventListener('astro:hydrate', load, { once: true }); + let hydrate: Awaited>; + + async function load() { + listen(load); const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); if (roots.length === 0) return; if (typeof innerHTML !== 'string') { @@ -35,22 +30,15 @@ export default async function onLoad( innerHTML = fragment.innerHTML; } } - - - const hydrate = await getHydrateCallback(); + if (!hydrate) { + hydrate = await getHydrateCallback(); + } for (const root of roots) { if (root.parentElement?.closest('astro-root[ssr]')) continue; await hydrate(root, innerHTML); root.removeAttribute('ssr'); } notify(); - }, 1); - - load(); -} - -const notify = () => { - if (document.querySelector('astro-root[ssr]')) { - window.dispatchEvent(new CustomEvent('astro:hydrate')); } + load(); } diff --git a/packages/astro/src/runtime/client/media.ts b/packages/astro/src/runtime/client/media.ts index 9003547c9c26..cd44158790ab 100644 --- a/packages/astro/src/runtime/client/media.ts +++ b/packages/astro/src/runtime/client/media.ts @@ -1,7 +1,8 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro'; +import { notify, listen } from './events'; /** - * Hydrate this component when a matching media query is found! + * Hydrate this component when a matching media query is found */ export default async function onMedia( astroId: string, @@ -9,31 +10,36 @@ export default async function onMedia( getHydrateCallback: GetHydrateCallback ) { let innerHTML: string | null = null; + let hydrate: Awaited>; + async function media() { - window.addEventListener('astro:hydrate', media, { once: true }) - const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); - if (roots.length === 0) return; - if (typeof innerHTML !== 'string') { - let fragment = roots[0].querySelector(`astro-fragment`); - if (fragment == null && roots[0].hasAttribute('tmpl')) { - // If there is no child fragment, check to see if there is a template. - // This happens if children were passed but the client component did not render any. - let template = roots[0].querySelector(`template[data-astro-template]`); - if (template) { - innerHTML = template.innerHTML; - template.remove(); + listen(media) + const cb = async () => { + const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); + if (roots.length === 0) return; + if (typeof innerHTML !== 'string') { + let fragment = roots[0].querySelector(`astro-fragment`); + if (fragment == null && roots[0].hasAttribute('tmpl')) { + // If there is no child fragment, check to see if there is a template. + // This happens if children were passed but the client component did not render any. + let template = roots[0].querySelector(`template[data-astro-template]`); + if (template) { + innerHTML = template.innerHTML; + template.remove(); + } + } else if (fragment) { + innerHTML = fragment.innerHTML; } - } else if (fragment) { - innerHTML = fragment.innerHTML; } - } - - const cb = async () => { - const hydrate = await getHydrateCallback(); + if (!hydrate) { + hydrate = await getHydrateCallback(); + } for (const root of roots) { - hydrate(root, innerHTML); - + if (root.parentElement?.closest('astro-root[ssr]')) continue; + await hydrate(root, innerHTML); + root.removeAttribute('ssr'); } + notify(); }; if (options.value) { diff --git a/packages/astro/src/runtime/client/only.ts b/packages/astro/src/runtime/client/only.ts index 99a24776b0c9..65ea02bd7649 100644 --- a/packages/astro/src/runtime/client/only.ts +++ b/packages/astro/src/runtime/client/only.ts @@ -1,4 +1,5 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro'; +import { listen, notify } from './events'; /** * Hydrate this component only on the client @@ -9,11 +10,12 @@ export default async function onOnly( getHydrateCallback: GetHydrateCallback ) { let innerHTML: string | null = null; + let hydrate: Awaited>; + async function only() { - window.addEventListener('astro:hydrate', only, { once: true }) + listen(only); const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); if (roots.length === 0) return; - if (typeof innerHTML !== 'string') { let fragment = roots[0].querySelector(`astro-fragment`); if (fragment == null && roots[0].hasAttribute('tmpl')) { @@ -28,12 +30,15 @@ export default async function onOnly( innerHTML = fragment.innerHTML; } } - const hydrate = await getHydrateCallback(); - + if (!hydrate) { + hydrate = await getHydrateCallback(); + } for (const root of roots) { - hydrate(root, innerHTML); + if (root.parentElement?.closest('astro-root[ssr]')) continue; + await hydrate(root, innerHTML); root.removeAttribute('ssr'); } + notify(); } - only() + only(); } diff --git a/packages/astro/src/runtime/client/visible.ts b/packages/astro/src/runtime/client/visible.ts index f0bd19e78fe7..9202d8c7260f 100644 --- a/packages/astro/src/runtime/client/visible.ts +++ b/packages/astro/src/runtime/client/visible.ts @@ -1,7 +1,8 @@ import type { GetHydrateCallback, HydrateOptions } from '../../@types/astro'; +import { notify, listen } from './events'; /** - * Hydrate this component when one of it's children becomes visible! + * Hydrate this component when one of it's children becomes visible * We target the children because `astro-root` is set to `display: contents` * which doesn't work with IntersectionObserver */ @@ -12,31 +13,36 @@ export default async function onVisible( ) { let io: IntersectionObserver; let innerHTML: string | null = null; + let hydrate: Awaited>; + async function visible() { - window.addEventListener('astro:hydrate', visible, { once: true }) + listen(visible) const roots = document.querySelectorAll(`astro-root[ssr][uid="${astroId}"]`); - if (roots.length === 0) return; - if (typeof innerHTML !== 'string') { - let fragment = roots[0].querySelector(`astro-fragment`); - if (fragment == null && roots[0].hasAttribute('tmpl')) { - // If there is no child fragment, check to see if there is a template. - // This happens if children were passed but the client component did not render any. - let template = roots[0].querySelector(`template[data-astro-template]`); - if (template) { - innerHTML = template.innerHTML; - template.remove(); + const cb = async () => { + if (roots.length === 0) return; + if (typeof innerHTML !== 'string') { + let fragment = roots[0].querySelector(`astro-fragment`); + if (fragment == null && roots[0].hasAttribute('tmpl')) { + // If there is no child fragment, check to see if there is a template. + // This happens if children were passed but the client component did not render any. + let template = roots[0].querySelector(`template[data-astro-template]`); + if (template) { + innerHTML = template.innerHTML; + template.remove(); + } + } else if (fragment) { + innerHTML = fragment.innerHTML; } - } else if (fragment) { - innerHTML = fragment.innerHTML; } - } - - const cb = async () => { - const hydrate = await getHydrateCallback(); + if (!hydrate) { + hydrate = await getHydrateCallback(); + } for (const root of roots) { - hydrate(root, innerHTML); + if (root.parentElement?.closest('astro-root[ssr]')) continue; + await hydrate(root, innerHTML); root.removeAttribute('ssr'); } + notify(); }; if (io) { diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index 91cb9fab1e96..e3c2806422dc 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -629,15 +629,6 @@ export async function renderHead(result: SSRResult): Promise { return renderElement('script', script); }); if (needsHydrationStyles) { - scripts.push(renderElement('script', { - children: `const notify = () => { - if (document.querySelector('astro-root[ssr]')) { - window.dispatchEvent(new CustomEvent('astro:hydrate')); - } -}; -new MutationObserver(() => notify()).observe(document.body, { subtree: true, childList: true })`, - props: { type: 'module' } - })); styles.push( renderElement('style', { props: {}, From d1ceaa2fea8e1e142124d6cc30add6278a73f269 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 26 May 2022 14:00:32 -0500 Subject: [PATCH 03/15] chore: fix bundle-size script for new files --- .github/scripts/bundle-size.mjs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/scripts/bundle-size.mjs b/.github/scripts/bundle-size.mjs index 4166735a9f27..19c806f8aace 100644 --- a/.github/scripts/bundle-size.mjs +++ b/.github/scripts/bundle-size.mjs @@ -1,4 +1,5 @@ import { build } from 'esbuild'; +import { existsSync } from 'fs'; const CLIENT_RUNTIME_PATH = 'packages/astro/src/runtime/client/'; @@ -57,8 +58,9 @@ ${table.join('\n')}`, } async function bundle(files) { + const { metafile } = await build({ - entryPoints: [...files.map(({ filename }) => filename), ...files.map(({ filename }) => `main/${filename}`)], + entryPoints: [...files.map(({ filename }) => filename), ...files.map(({ filename }) => `main/${filename}`).filter(f => existsSync(f))], bundle: true, minify: true, sourcemap: false, @@ -72,10 +74,10 @@ async function bundle(files) { if (filename.startsWith('main/')) { filename = filename.slice('main/'.length).replace(CLIENT_RUNTIME_PATH, '').replace('.js', ''); const oldSize = info.bytes; - return Object.assign(acc, { [filename]: Object.assign(acc[filename] ?? {}, { oldSize }) }); + return Object.assign(acc, { [filename]: Object.assign(acc[filename] ?? { oldSize: 0, newSize: 0 }, { oldSize }) }); } filename = filename.replace(CLIENT_RUNTIME_PATH, '').replace('.js', ''); const newSize = info.bytes; - return Object.assign(acc, { [filename]: Object.assign(acc[filename] ?? {}, { newSize, sourceFile: Object.keys(info.inputs).find(src => src.endsWith('.ts')) }) }); + return Object.assign(acc, { [filename]: Object.assign(acc[filename] ?? { oldSize: 0, newSize: 0 }, { newSize, sourceFile: Object.keys(info.inputs).find(src => src.endsWith('.ts')) }) }); }, {}); } From d2fd73a034d8be9979a583d601725d3a89d8d559 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 26 May 2022 14:04:34 -0500 Subject: [PATCH 04/15] chore: allow-list client:* directive files --- .github/scripts/bundle-size.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/bundle-size.mjs b/.github/scripts/bundle-size.mjs index 19c806f8aace..618d6e8cb546 100644 --- a/.github/scripts/bundle-size.mjs +++ b/.github/scripts/bundle-size.mjs @@ -33,7 +33,7 @@ export default async function checkBundleSize({ github, context }) { const output = await bundle(clientRuntimeFiles); for (let [filename, { oldSize, newSize, sourceFile }] of Object.entries(output)) { - filename = filename !== 'hmr' ? `client:${filename}` : filename; + filename = ['idle', 'load', 'media', 'only', 'visible'].includes(filename) ? `client:${filename}` : filename; const prefix = (newSize - oldSize) === 0 ? '' : (newSize - oldSize) > 0 ? '+ ' : '- '; const change = `${prefix}${formatBytes(newSize - oldSize)}`; table.push(`| [\`${filename}\`](https://github.com/${context.repo.owner}/${context.repo.repo}/tree/${context.payload.pull_request.head.ref}/${sourceFile}) | ${formatBytes(oldSize)} | ${formatBytes(newSize)} | ${change} |`); From ef76b4dff090d3cbf528a68e874f4dfeabfc8691 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 26 May 2022 14:35:41 -0500 Subject: [PATCH 05/15] fix(#3362): fix client:only behavior for React, Vue, Solid --- packages/integrations/react/client.js | 7 ++++--- packages/integrations/solid/client.js | 14 ++++++++++---- packages/integrations/svelte/client.js | 5 +++-- packages/integrations/vue/client.js | 15 +++++++++++---- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/packages/integrations/react/client.js b/packages/integrations/react/client.js index d5d84884745b..2828d2cbe3ac 100644 --- a/packages/integrations/react/client.js +++ b/packages/integrations/react/client.js @@ -12,18 +12,19 @@ function isAlreadyHydrated(element) { export default (element) => (Component, props, children, { client }) => { + if (!element.hasAttribute('ssr')) return; const componentEl = createElement( Component, props, children != null ? createElement(StaticHtml, { value: children }) : children ); - if (client === 'only') { - return createRoot(element).render(componentEl); - } const rootKey = isAlreadyHydrated(element); // HACK: delete internal react marker for nested components to suppress agressive warnings if (rootKey) { delete element[rootKey]; } + if (client === 'only') { + return createRoot(element).render(componentEl); + } return hydrateRoot(element, componentEl); }; diff --git a/packages/integrations/solid/client.js b/packages/integrations/solid/client.js index d31c5cecd1ec..005f3c980376 100644 --- a/packages/integrations/solid/client.js +++ b/packages/integrations/solid/client.js @@ -1,21 +1,27 @@ import { sharedConfig } from 'solid-js'; -import { hydrate, createComponent } from 'solid-js/web'; +import { hydrate, render, createComponent } from 'solid-js/web'; -export default (element) => (Component, props, childHTML) => { +export default (element) => (Component, props, childHTML, { client }) => { // Prepare global object expected by Solid's hydration logic if (!window._$HY) { window._$HY = { events: [], completed: new WeakSet(), r: {} }; } + if (!element.hasAttribute('ssr')) return; + + const fn = client === 'only' ? render : hydrate; + // Perform actual hydration let children; - hydrate( + fn( () => createComponent(Component, { ...props, get children() { if (childHTML != null) { // hydrating - if (sharedConfig.context) children = element.querySelector('astro-fragment'); + if (sharedConfig.context) { + children = element.querySelector('astro-fragment'); + } if (children == null) { children = document.createElement('astro-fragment'); diff --git a/packages/integrations/svelte/client.js b/packages/integrations/svelte/client.js index c10c7afa01b0..3f401b544f94 100644 --- a/packages/integrations/svelte/client.js +++ b/packages/integrations/svelte/client.js @@ -1,13 +1,14 @@ import SvelteWrapper from './Wrapper.svelte'; export default (target) => { - return (component, props, children) => { + return (component, props, children, { client }) => { + if (!target.hasAttribute('ssr')) return; delete props['class']; try { new SvelteWrapper({ target, props: { __astro_component: component, __astro_children: children, ...props }, - hydrate: true, + hydrate: client !== 'only', }); } catch (e) {} }; diff --git a/packages/integrations/vue/client.js b/packages/integrations/vue/client.js index 0ba4e81063ab..4832a984760b 100644 --- a/packages/integrations/vue/client.js +++ b/packages/integrations/vue/client.js @@ -1,14 +1,21 @@ -import { h, createSSRApp } from 'vue'; +import { h, createSSRApp, createApp } from 'vue'; import StaticHtml from './static-html.js'; -export default (element) => (Component, props, children) => { +export default (element) => (Component, props, children, { client }) => { delete props['class']; + if (!element.hasAttribute('ssr')) return; + // Expose name on host component for Vue devtools const name = Component.name ? `${Component.name} Host` : undefined; const slots = {}; if (children != null) { slots.default = () => h(StaticHtml, { value: children }); } - const app = createSSRApp({ name, render: () => h(Component, props, slots) }); - app.mount(element, true); + if (client === 'only') { + const app = createApp({ name, render: () => h(Component, props, slots) }); + app.mount(element, false); + } else { + const app = createSSRApp({ name, render: () => h(Component, props, slots) }); + app.mount(element, true); + } }; From 51591d262f90bffd22da9743c7a5a3f33319da42 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 26 May 2022 15:11:57 -0500 Subject: [PATCH 06/15] test: add client-only e2e test --- packages/astro/e2e/client-only.test.js | 111 ++++++++++++++++++ .../e2e/fixtures/client-only/astro.config.mjs | 12 ++ .../e2e/fixtures/client-only/package.json | 24 ++++ .../src/components/PreactCounter.tsx | 19 +++ .../src/components/ReactCounter.jsx | 19 +++ .../src/components/SolidCounter.tsx | 19 +++ .../src/components/SvelteCounter.svelte | 29 +++++ .../client-only/src/components/VueCounter.vue | 34 ++++++ .../client-only/src/pages/index.astro | 41 +++++++ 9 files changed, 308 insertions(+) create mode 100644 packages/astro/e2e/client-only.test.js create mode 100644 packages/astro/e2e/fixtures/client-only/astro.config.mjs create mode 100644 packages/astro/e2e/fixtures/client-only/package.json create mode 100644 packages/astro/e2e/fixtures/client-only/src/components/PreactCounter.tsx create mode 100644 packages/astro/e2e/fixtures/client-only/src/components/ReactCounter.jsx create mode 100644 packages/astro/e2e/fixtures/client-only/src/components/SolidCounter.tsx create mode 100644 packages/astro/e2e/fixtures/client-only/src/components/SvelteCounter.svelte create mode 100644 packages/astro/e2e/fixtures/client-only/src/components/VueCounter.vue create mode 100644 packages/astro/e2e/fixtures/client-only/src/pages/index.astro diff --git a/packages/astro/e2e/client-only.test.js b/packages/astro/e2e/client-only.test.js new file mode 100644 index 000000000000..a4ec8a9c8cc3 --- /dev/null +++ b/packages/astro/e2e/client-only.test.js @@ -0,0 +1,111 @@ +import { test as base, expect } from '@playwright/test'; +import { loadFixture } from './test-utils.js'; + +const test = base.extend({ + astro: async ({}, use) => { + const fixture = await loadFixture({ root: './fixtures/client-only/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeEach(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterEach(async () => { + await devServer.stop(); +}); + +test.describe('Client only', () => { + test('React counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#react-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('pre'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const children = await counter.locator('h1'); + await expect(children, 'children exist').toHaveText('react'); + + const increment = await counter.locator('.increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Preact counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#preact-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('pre'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const children = await counter.locator('h1'); + await expect(children, 'children exist').toHaveText('preact'); + + const increment = await counter.locator('.increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Solid counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#solid-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('pre'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const children = await counter.locator('h1'); + await expect(children, 'children exist').toHaveText('solid'); + + const increment = await counter.locator('.increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Vue counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#vue-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('pre'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const children = await counter.locator('h1'); + await expect(children, 'children exist').toHaveText('vue'); + + const increment = await counter.locator('.increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Svelte counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#svelte-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('pre'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const children = await counter.locator('h1'); + await expect(children, 'children exist').toHaveText('svelte'); + + const increment = await counter.locator('.increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); +}); diff --git a/packages/astro/e2e/fixtures/client-only/astro.config.mjs b/packages/astro/e2e/fixtures/client-only/astro.config.mjs new file mode 100644 index 000000000000..4b50887cd70c --- /dev/null +++ b/packages/astro/e2e/fixtures/client-only/astro.config.mjs @@ -0,0 +1,12 @@ +import { defineConfig } from 'astro/config'; +import preact from '@astrojs/preact'; +import react from '@astrojs/react'; +import svelte from '@astrojs/svelte'; +import vue from '@astrojs/vue'; +import solid from '@astrojs/solid-js'; + +// https://astro.build/config +export default defineConfig({ + // Enable many frameworks to support all different kinds of components. + integrations: [preact(), react(), svelte(), vue(), solid()], +}); diff --git a/packages/astro/e2e/fixtures/client-only/package.json b/packages/astro/e2e/fixtures/client-only/package.json new file mode 100644 index 000000000000..a0a8ce22968d --- /dev/null +++ b/packages/astro/e2e/fixtures/client-only/package.json @@ -0,0 +1,24 @@ +{ + "name": "@e2e/multiple-frameworks", + "version": "0.0.0", + "private": true, + "devDependencies": { + "@astrojs/lit": "^0.1.3", + "@astrojs/preact": "^0.1.2", + "@astrojs/react": "^0.1.2", + "@astrojs/solid-js": "^0.1.2", + "@astrojs/svelte": "^0.1.3", + "@astrojs/vue": "^0.1.4", + "astro": "^1.0.0-beta.32" + }, + "dependencies": { + "@webcomponents/template-shadowroot": "^0.1.0", + "lit": "^2.2.4", + "preact": "^10.7.2", + "react": "^18.1.0", + "react-dom": "^18.1.0", + "solid-js": "^1.4.2", + "svelte": "^3.48.0", + "vue": "^3.2.36" + } +} diff --git a/packages/astro/e2e/fixtures/client-only/src/components/PreactCounter.tsx b/packages/astro/e2e/fixtures/client-only/src/components/PreactCounter.tsx new file mode 100644 index 000000000000..af2258fdfe8f --- /dev/null +++ b/packages/astro/e2e/fixtures/client-only/src/components/PreactCounter.tsx @@ -0,0 +1,19 @@ +import { useState } from 'preact/hooks'; + +/** a counter written in Preact */ +export function PreactCounter({ children, id }) { + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( + <> +
+ +
{count}
+ +
+
{children}
+ + ); +} diff --git a/packages/astro/e2e/fixtures/client-only/src/components/ReactCounter.jsx b/packages/astro/e2e/fixtures/client-only/src/components/ReactCounter.jsx new file mode 100644 index 000000000000..02eb1953907d --- /dev/null +++ b/packages/astro/e2e/fixtures/client-only/src/components/ReactCounter.jsx @@ -0,0 +1,19 @@ +import { useState } from 'react'; + +/** a counter written in React */ +export function Counter({ children, id }) { + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( + <> +
+ +
{count}
+ +
+
{children}
+ + ); +} diff --git a/packages/astro/e2e/fixtures/client-only/src/components/SolidCounter.tsx b/packages/astro/e2e/fixtures/client-only/src/components/SolidCounter.tsx new file mode 100644 index 000000000000..689c5222ce97 --- /dev/null +++ b/packages/astro/e2e/fixtures/client-only/src/components/SolidCounter.tsx @@ -0,0 +1,19 @@ +import { createSignal } from 'solid-js'; + +/** a counter written with Solid */ +export default function SolidCounter({ children, id }) { + const [count, setCount] = createSignal(0); + const add = () => setCount(count() + 1); + const subtract = () => setCount(count() - 1); + + return ( + <> +
+ +
{count()}
+ +
+
{children}
+ + ); +} diff --git a/packages/astro/e2e/fixtures/client-only/src/components/SvelteCounter.svelte b/packages/astro/e2e/fixtures/client-only/src/components/SvelteCounter.svelte new file mode 100644 index 000000000000..ab13b9c71f6b --- /dev/null +++ b/packages/astro/e2e/fixtures/client-only/src/components/SvelteCounter.svelte @@ -0,0 +1,29 @@ + + + +
+ +
{ count }
+ +
+
+ +
+ + diff --git a/packages/astro/e2e/fixtures/client-only/src/components/VueCounter.vue b/packages/astro/e2e/fixtures/client-only/src/components/VueCounter.vue new file mode 100644 index 000000000000..4861511c8c8a --- /dev/null +++ b/packages/astro/e2e/fixtures/client-only/src/components/VueCounter.vue @@ -0,0 +1,34 @@ + + + diff --git a/packages/astro/e2e/fixtures/client-only/src/pages/index.astro b/packages/astro/e2e/fixtures/client-only/src/pages/index.astro new file mode 100644 index 000000000000..708ba1582dc9 --- /dev/null +++ b/packages/astro/e2e/fixtures/client-only/src/pages/index.astro @@ -0,0 +1,41 @@ +--- +import * as react from '../components/ReactCounter.jsx'; +import { PreactCounter } from '../components/PreactCounter.tsx'; +import SolidCounter from '../components/SolidCounter.tsx'; +import VueCounter from '../components/VueCounter.vue'; +import SvelteCounter from '../components/SvelteCounter.svelte'; + +// Full Astro Component Syntax: +// https://docs.astro.build/core-concepts/astro-components/ +--- + + + + + + + + +
+ +

react

+
+ + +

preact

+
+ + +

solid

+
+ + +

vue

+
+ + +

svelte

+
+
+ + From cef1acbcaddde4e04eb8cd83a225310252b20e68 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 26 May 2022 15:26:07 -0500 Subject: [PATCH 07/15] chore: update lockfile --- pnpm-lock.yaml | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0091f0eca7c3..82e19b8fcf35 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -667,6 +667,41 @@ importers: dependencies: astro: link:../../.. + packages/astro/e2e/fixtures/client-only: + specifiers: + '@astrojs/lit': ^0.1.3 + '@astrojs/preact': ^0.1.2 + '@astrojs/react': ^0.1.2 + '@astrojs/solid-js': ^0.1.2 + '@astrojs/svelte': ^0.1.3 + '@astrojs/vue': ^0.1.4 + '@webcomponents/template-shadowroot': ^0.1.0 + astro: ^1.0.0-beta.32 + lit: ^2.2.4 + preact: ^10.7.2 + react: ^18.1.0 + react-dom: ^18.1.0 + solid-js: ^1.4.2 + svelte: ^3.48.0 + vue: ^3.2.36 + dependencies: + '@webcomponents/template-shadowroot': 0.1.0 + lit: 2.2.4 + preact: 10.7.2 + react: 18.1.0 + react-dom: 18.1.0_react@18.1.0 + solid-js: 1.4.2 + svelte: 3.48.0 + vue: 3.2.36 + devDependencies: + '@astrojs/lit': link:../../../../integrations/lit + '@astrojs/preact': link:../../../../integrations/preact + '@astrojs/react': link:../../../../integrations/react + '@astrojs/solid-js': link:../../../../integrations/solid + '@astrojs/svelte': link:../../../../integrations/svelte + '@astrojs/vue': link:../../../../integrations/vue + astro: link:../../.. + packages/astro/e2e/fixtures/lit-component: specifiers: '@astrojs/lit': workspace:* @@ -7882,6 +7917,11 @@ packages: /debug/3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.1.3 dev: false @@ -10784,6 +10824,8 @@ packages: debug: 3.2.7 iconv-lite: 0.4.24 sax: 1.2.4 + transitivePeerDependencies: + - supports-color dev: false /netmask/2.0.2: @@ -10867,6 +10909,8 @@ packages: rimraf: 2.7.1 semver: 5.7.1 tar: 4.4.19 + transitivePeerDependencies: + - supports-color dev: false /node-releases/2.0.5: From 5bbd04c4d04be771b64d71fa208c47e20c608ec5 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 26 May 2022 15:56:21 -0500 Subject: [PATCH 08/15] test: fix e2e tests --- packages/astro/e2e/client-only.test.js | 10 +++++----- .../client-only/src/components/PreactCounter.tsx | 14 ++++++-------- .../client-only/src/components/ReactCounter.jsx | 14 ++++++-------- .../client-only/src/components/SolidCounter.tsx | 4 +--- .../src/components/SvelteCounter.svelte | 6 +++--- .../client-only/src/components/VueCounter.vue | 6 +++--- 6 files changed, 24 insertions(+), 30 deletions(-) diff --git a/packages/astro/e2e/client-only.test.js b/packages/astro/e2e/client-only.test.js index a4ec8a9c8cc3..78a06fdbe31b 100644 --- a/packages/astro/e2e/client-only.test.js +++ b/packages/astro/e2e/client-only.test.js @@ -28,7 +28,7 @@ test.describe('Client only', () => { const count = await counter.locator('pre'); await expect(count, 'initial count is 0').toHaveText('0'); - const children = await counter.locator('h1'); + const children = await counter.locator('.children'); await expect(children, 'children exist').toHaveText('react'); const increment = await counter.locator('.increment'); @@ -46,7 +46,7 @@ test.describe('Client only', () => { const count = await counter.locator('pre'); await expect(count, 'initial count is 0').toHaveText('0'); - const children = await counter.locator('h1'); + const children = await counter.locator('.children'); await expect(children, 'children exist').toHaveText('preact'); const increment = await counter.locator('.increment'); @@ -64,7 +64,7 @@ test.describe('Client only', () => { const count = await counter.locator('pre'); await expect(count, 'initial count is 0').toHaveText('0'); - const children = await counter.locator('h1'); + const children = await counter.locator('.children'); await expect(children, 'children exist').toHaveText('solid'); const increment = await counter.locator('.increment'); @@ -82,7 +82,7 @@ test.describe('Client only', () => { const count = await counter.locator('pre'); await expect(count, 'initial count is 0').toHaveText('0'); - const children = await counter.locator('h1'); + const children = await counter.locator('.children'); await expect(children, 'children exist').toHaveText('vue'); const increment = await counter.locator('.increment'); @@ -100,7 +100,7 @@ test.describe('Client only', () => { const count = await counter.locator('pre'); await expect(count, 'initial count is 0').toHaveText('0'); - const children = await counter.locator('h1'); + const children = await counter.locator('.children'); await expect(children, 'children exist').toHaveText('svelte'); const increment = await counter.locator('.increment'); diff --git a/packages/astro/e2e/fixtures/client-only/src/components/PreactCounter.tsx b/packages/astro/e2e/fixtures/client-only/src/components/PreactCounter.tsx index af2258fdfe8f..b0570046c4f8 100644 --- a/packages/astro/e2e/fixtures/client-only/src/components/PreactCounter.tsx +++ b/packages/astro/e2e/fixtures/client-only/src/components/PreactCounter.tsx @@ -7,13 +7,11 @@ export function PreactCounter({ children, id }) { const subtract = () => setCount((i) => i - 1); return ( - <> -
- -
{count}
- -
-
{children}
- +
+ +
{count}
+ +
{children}
+
); } diff --git a/packages/astro/e2e/fixtures/client-only/src/components/ReactCounter.jsx b/packages/astro/e2e/fixtures/client-only/src/components/ReactCounter.jsx index 02eb1953907d..30d804199bbd 100644 --- a/packages/astro/e2e/fixtures/client-only/src/components/ReactCounter.jsx +++ b/packages/astro/e2e/fixtures/client-only/src/components/ReactCounter.jsx @@ -7,13 +7,11 @@ export function Counter({ children, id }) { const subtract = () => setCount((i) => i - 1); return ( - <> -
- -
{count}
- -
-
{children}
- +
+ +
{count}
+ +
{children}
+
); } diff --git a/packages/astro/e2e/fixtures/client-only/src/components/SolidCounter.tsx b/packages/astro/e2e/fixtures/client-only/src/components/SolidCounter.tsx index 689c5222ce97..fbbb9850b5e4 100644 --- a/packages/astro/e2e/fixtures/client-only/src/components/SolidCounter.tsx +++ b/packages/astro/e2e/fixtures/client-only/src/components/SolidCounter.tsx @@ -7,13 +7,11 @@ export default function SolidCounter({ children, id }) { const subtract = () => setCount(count() - 1); return ( - <>
{count()}
+
{children}
-
{children}
- ); } diff --git a/packages/astro/e2e/fixtures/client-only/src/components/SvelteCounter.svelte b/packages/astro/e2e/fixtures/client-only/src/components/SvelteCounter.svelte index ab13b9c71f6b..cc9fe8c93c60 100644 --- a/packages/astro/e2e/fixtures/client-only/src/components/SvelteCounter.svelte +++ b/packages/astro/e2e/fixtures/client-only/src/components/SvelteCounter.svelte @@ -17,9 +17,9 @@
{ count }
- -
- +
+ +
diff --git a/packages/astro/e2e/fixtures/nested-in-preact/src/components/VueCounter.vue b/packages/astro/e2e/fixtures/nested-in-preact/src/components/VueCounter.vue new file mode 100644 index 000000000000..d404cc965c52 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-preact/src/components/VueCounter.vue @@ -0,0 +1,34 @@ + + + diff --git a/packages/astro/e2e/fixtures/nested-in-preact/src/pages/index.astro b/packages/astro/e2e/fixtures/nested-in-preact/src/pages/index.astro new file mode 100644 index 000000000000..619e8cccd0ae --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-preact/src/pages/index.astro @@ -0,0 +1,28 @@ +--- +import ReactCounter from '../components/ReactCounter.jsx'; +import { PreactCounter } from '../components/PreactCounter.tsx'; +import SolidCounter from '../components/SolidCounter.tsx'; +import VueCounter from '../components/VueCounter.vue'; +import SvelteCounter from '../components/SvelteCounter.svelte'; + +// Full Astro Component Syntax: +// https://docs.astro.build/core-concepts/astro-components/ +--- + + + + + + + + +
+ + + + + + +
+ + diff --git a/packages/astro/e2e/fixtures/nested-in-react/astro.config.mjs b/packages/astro/e2e/fixtures/nested-in-react/astro.config.mjs new file mode 100644 index 000000000000..4b50887cd70c --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-react/astro.config.mjs @@ -0,0 +1,12 @@ +import { defineConfig } from 'astro/config'; +import preact from '@astrojs/preact'; +import react from '@astrojs/react'; +import svelte from '@astrojs/svelte'; +import vue from '@astrojs/vue'; +import solid from '@astrojs/solid-js'; + +// https://astro.build/config +export default defineConfig({ + // Enable many frameworks to support all different kinds of components. + integrations: [preact(), react(), svelte(), vue(), solid()], +}); diff --git a/packages/astro/e2e/fixtures/nested-in-react/package.json b/packages/astro/e2e/fixtures/nested-in-react/package.json new file mode 100644 index 000000000000..fae7b3e2b8c1 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-react/package.json @@ -0,0 +1,21 @@ +{ + "name": "@e2e/nested-in-react", + "version": "0.0.0", + "private": true, + "devDependencies": { + "@astrojs/preact": "^0.1.2", + "@astrojs/react": "^0.1.2", + "@astrojs/solid-js": "^0.1.2", + "@astrojs/svelte": "^0.1.3", + "@astrojs/vue": "^0.1.4", + "astro": "^1.0.0-beta.32" + }, + "dependencies": { + "preact": "^10.7.2", + "react": "^18.1.0", + "react-dom": "^18.1.0", + "solid-js": "^1.4.2", + "svelte": "^3.48.0", + "vue": "^3.2.36" + } +} diff --git a/packages/astro/e2e/fixtures/nested-in-react/src/components/PreactCounter.tsx b/packages/astro/e2e/fixtures/nested-in-react/src/components/PreactCounter.tsx new file mode 100644 index 000000000000..5f20f560d1f6 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-react/src/components/PreactCounter.tsx @@ -0,0 +1,17 @@ +import { useState } from 'preact/hooks'; + +/** a counter written in Preact */ +export function PreactCounter({ children, id }) { + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/packages/astro/e2e/fixtures/nested-in-react/src/components/ReactCounter.jsx b/packages/astro/e2e/fixtures/nested-in-react/src/components/ReactCounter.jsx new file mode 100644 index 000000000000..0dc0deb47b38 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-react/src/components/ReactCounter.jsx @@ -0,0 +1,17 @@ +import { useState } from 'react'; + +/** a counter written in React */ +export default function Counter({ children, id }) { + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/packages/astro/e2e/fixtures/nested-in-react/src/components/SolidCounter.tsx b/packages/astro/e2e/fixtures/nested-in-react/src/components/SolidCounter.tsx new file mode 100644 index 000000000000..afabe43b9e81 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-react/src/components/SolidCounter.tsx @@ -0,0 +1,17 @@ +import { createSignal } from 'solid-js'; + +/** a counter written with Solid */ +export default function SolidCounter({ children, id }) { + const [count, setCount] = createSignal(0); + const add = () => setCount(count() + 1); + const subtract = () => setCount(count() - 1); + + return ( +
+ +
{count()}
+ +
{children}
+
+ ); +} diff --git a/packages/astro/e2e/fixtures/nested-in-react/src/components/SvelteCounter.svelte b/packages/astro/e2e/fixtures/nested-in-react/src/components/SvelteCounter.svelte new file mode 100644 index 000000000000..733f58076a24 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-react/src/components/SvelteCounter.svelte @@ -0,0 +1,29 @@ + + + +
+ +
{ count }
+ +
+ +
+
+ + diff --git a/packages/astro/e2e/fixtures/nested-in-react/src/components/VueCounter.vue b/packages/astro/e2e/fixtures/nested-in-react/src/components/VueCounter.vue new file mode 100644 index 000000000000..d404cc965c52 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-react/src/components/VueCounter.vue @@ -0,0 +1,34 @@ + + + diff --git a/packages/astro/e2e/fixtures/nested-in-react/src/pages/index.astro b/packages/astro/e2e/fixtures/nested-in-react/src/pages/index.astro new file mode 100644 index 000000000000..0b3b23d9d8e8 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-react/src/pages/index.astro @@ -0,0 +1,28 @@ +--- +import ReactCounter from '../components/ReactCounter.jsx'; +import { PreactCounter } from '../components/PreactCounter.tsx'; +import SolidCounter from '../components/SolidCounter.tsx'; +import VueCounter from '../components/VueCounter.vue'; +import SvelteCounter from '../components/SvelteCounter.svelte'; + +// Full Astro Component Syntax: +// https://docs.astro.build/core-concepts/astro-components/ +--- + + + + + + + + +
+ + + + + + +
+ + diff --git a/packages/astro/e2e/fixtures/nested-in-solid/astro.config.mjs b/packages/astro/e2e/fixtures/nested-in-solid/astro.config.mjs new file mode 100644 index 000000000000..4b50887cd70c --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-solid/astro.config.mjs @@ -0,0 +1,12 @@ +import { defineConfig } from 'astro/config'; +import preact from '@astrojs/preact'; +import react from '@astrojs/react'; +import svelte from '@astrojs/svelte'; +import vue from '@astrojs/vue'; +import solid from '@astrojs/solid-js'; + +// https://astro.build/config +export default defineConfig({ + // Enable many frameworks to support all different kinds of components. + integrations: [preact(), react(), svelte(), vue(), solid()], +}); diff --git a/packages/astro/e2e/fixtures/nested-in-solid/package.json b/packages/astro/e2e/fixtures/nested-in-solid/package.json new file mode 100644 index 000000000000..412561a6de94 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-solid/package.json @@ -0,0 +1,21 @@ +{ + "name": "@e2e/nested-in-solid", + "version": "0.0.0", + "private": true, + "devDependencies": { + "@astrojs/preact": "^0.1.2", + "@astrojs/react": "^0.1.2", + "@astrojs/solid-js": "^0.1.2", + "@astrojs/svelte": "^0.1.3", + "@astrojs/vue": "^0.1.4", + "astro": "^1.0.0-beta.32" + }, + "dependencies": { + "preact": "^10.7.2", + "react": "^18.1.0", + "react-dom": "^18.1.0", + "solid-js": "^1.4.2", + "svelte": "^3.48.0", + "vue": "^3.2.36" + } +} diff --git a/packages/astro/e2e/fixtures/nested-in-solid/src/components/PreactCounter.tsx b/packages/astro/e2e/fixtures/nested-in-solid/src/components/PreactCounter.tsx new file mode 100644 index 000000000000..5f20f560d1f6 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-solid/src/components/PreactCounter.tsx @@ -0,0 +1,17 @@ +import { useState } from 'preact/hooks'; + +/** a counter written in Preact */ +export function PreactCounter({ children, id }) { + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/packages/astro/e2e/fixtures/nested-in-solid/src/components/ReactCounter.jsx b/packages/astro/e2e/fixtures/nested-in-solid/src/components/ReactCounter.jsx new file mode 100644 index 000000000000..c7197a072b85 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-solid/src/components/ReactCounter.jsx @@ -0,0 +1,17 @@ +import { useState } from 'react'; + +/** a counter written in React */ +export function Counter({ children, id }) { + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/packages/astro/e2e/fixtures/nested-in-solid/src/components/SolidCounter.tsx b/packages/astro/e2e/fixtures/nested-in-solid/src/components/SolidCounter.tsx new file mode 100644 index 000000000000..afabe43b9e81 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-solid/src/components/SolidCounter.tsx @@ -0,0 +1,17 @@ +import { createSignal } from 'solid-js'; + +/** a counter written with Solid */ +export default function SolidCounter({ children, id }) { + const [count, setCount] = createSignal(0); + const add = () => setCount(count() + 1); + const subtract = () => setCount(count() - 1); + + return ( +
+ +
{count()}
+ +
{children}
+
+ ); +} diff --git a/packages/astro/e2e/fixtures/nested-in-solid/src/components/SvelteCounter.svelte b/packages/astro/e2e/fixtures/nested-in-solid/src/components/SvelteCounter.svelte new file mode 100644 index 000000000000..733f58076a24 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-solid/src/components/SvelteCounter.svelte @@ -0,0 +1,29 @@ + + + +
+ +
{ count }
+ +
+ +
+
+ + diff --git a/packages/astro/e2e/fixtures/nested-in-solid/src/components/VueCounter.vue b/packages/astro/e2e/fixtures/nested-in-solid/src/components/VueCounter.vue new file mode 100644 index 000000000000..d404cc965c52 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-solid/src/components/VueCounter.vue @@ -0,0 +1,34 @@ + + + diff --git a/packages/astro/e2e/fixtures/nested-in-solid/src/pages/index.astro b/packages/astro/e2e/fixtures/nested-in-solid/src/pages/index.astro new file mode 100644 index 000000000000..0feb5ba60035 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-solid/src/pages/index.astro @@ -0,0 +1,28 @@ +--- +import { Counter as ReactCounter } from '../components/ReactCounter.jsx'; +import { PreactCounter } from '../components/PreactCounter.tsx'; +import SolidCounter from '../components/SolidCounter.tsx'; +import VueCounter from '../components/VueCounter.vue'; +import SvelteCounter from '../components/SvelteCounter.svelte'; + +// Full Astro Component Syntax: +// https://docs.astro.build/core-concepts/astro-components/ +--- + + + + + + + + +
+ + + + + + +
+ + diff --git a/packages/astro/e2e/fixtures/nested-in-svelte/astro.config.mjs b/packages/astro/e2e/fixtures/nested-in-svelte/astro.config.mjs new file mode 100644 index 000000000000..4b50887cd70c --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-svelte/astro.config.mjs @@ -0,0 +1,12 @@ +import { defineConfig } from 'astro/config'; +import preact from '@astrojs/preact'; +import react from '@astrojs/react'; +import svelte from '@astrojs/svelte'; +import vue from '@astrojs/vue'; +import solid from '@astrojs/solid-js'; + +// https://astro.build/config +export default defineConfig({ + // Enable many frameworks to support all different kinds of components. + integrations: [preact(), react(), svelte(), vue(), solid()], +}); diff --git a/packages/astro/e2e/fixtures/nested-in-svelte/package.json b/packages/astro/e2e/fixtures/nested-in-svelte/package.json new file mode 100644 index 000000000000..44c3018cf0ed --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-svelte/package.json @@ -0,0 +1,21 @@ +{ + "name": "@e2e/nested-in-svelte", + "version": "0.0.0", + "private": true, + "devDependencies": { + "@astrojs/preact": "^0.1.2", + "@astrojs/react": "^0.1.2", + "@astrojs/solid-js": "^0.1.2", + "@astrojs/svelte": "^0.1.3", + "@astrojs/vue": "^0.1.4", + "astro": "^1.0.0-beta.32" + }, + "dependencies": { + "preact": "^10.7.2", + "react": "^18.1.0", + "react-dom": "^18.1.0", + "solid-js": "^1.4.2", + "svelte": "^3.48.0", + "vue": "^3.2.36" + } +} diff --git a/packages/astro/e2e/fixtures/nested-in-svelte/src/components/PreactCounter.tsx b/packages/astro/e2e/fixtures/nested-in-svelte/src/components/PreactCounter.tsx new file mode 100644 index 000000000000..5f20f560d1f6 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-svelte/src/components/PreactCounter.tsx @@ -0,0 +1,17 @@ +import { useState } from 'preact/hooks'; + +/** a counter written in Preact */ +export function PreactCounter({ children, id }) { + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/packages/astro/e2e/fixtures/nested-in-svelte/src/components/ReactCounter.jsx b/packages/astro/e2e/fixtures/nested-in-svelte/src/components/ReactCounter.jsx new file mode 100644 index 000000000000..c7197a072b85 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-svelte/src/components/ReactCounter.jsx @@ -0,0 +1,17 @@ +import { useState } from 'react'; + +/** a counter written in React */ +export function Counter({ children, id }) { + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/packages/astro/e2e/fixtures/nested-in-svelte/src/components/SolidCounter.tsx b/packages/astro/e2e/fixtures/nested-in-svelte/src/components/SolidCounter.tsx new file mode 100644 index 000000000000..afabe43b9e81 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-svelte/src/components/SolidCounter.tsx @@ -0,0 +1,17 @@ +import { createSignal } from 'solid-js'; + +/** a counter written with Solid */ +export default function SolidCounter({ children, id }) { + const [count, setCount] = createSignal(0); + const add = () => setCount(count() + 1); + const subtract = () => setCount(count() - 1); + + return ( +
+ +
{count()}
+ +
{children}
+
+ ); +} diff --git a/packages/astro/e2e/fixtures/nested-in-svelte/src/components/SvelteCounter.svelte b/packages/astro/e2e/fixtures/nested-in-svelte/src/components/SvelteCounter.svelte new file mode 100644 index 000000000000..733f58076a24 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-svelte/src/components/SvelteCounter.svelte @@ -0,0 +1,29 @@ + + + +
+ +
{ count }
+ +
+ +
+
+ + diff --git a/packages/astro/e2e/fixtures/nested-in-svelte/src/components/VueCounter.vue b/packages/astro/e2e/fixtures/nested-in-svelte/src/components/VueCounter.vue new file mode 100644 index 000000000000..d404cc965c52 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-svelte/src/components/VueCounter.vue @@ -0,0 +1,34 @@ + + + diff --git a/packages/astro/e2e/fixtures/nested-in-svelte/src/pages/index.astro b/packages/astro/e2e/fixtures/nested-in-svelte/src/pages/index.astro new file mode 100644 index 000000000000..764ebfaf702e --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-svelte/src/pages/index.astro @@ -0,0 +1,28 @@ +--- +import { Counter as ReactCounter } from '../components/ReactCounter.jsx'; +import { PreactCounter } from '../components/PreactCounter.tsx'; +import SolidCounter from '../components/SolidCounter.tsx'; +import VueCounter from '../components/VueCounter.vue'; +import SvelteCounter from '../components/SvelteCounter.svelte'; + +// Full Astro Component Syntax: +// https://docs.astro.build/core-concepts/astro-components/ +--- + + + + + + + + +
+ + + + + + +
+ + diff --git a/packages/astro/e2e/fixtures/nested-in-vue/astro.config.mjs b/packages/astro/e2e/fixtures/nested-in-vue/astro.config.mjs new file mode 100644 index 000000000000..4b50887cd70c --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-vue/astro.config.mjs @@ -0,0 +1,12 @@ +import { defineConfig } from 'astro/config'; +import preact from '@astrojs/preact'; +import react from '@astrojs/react'; +import svelte from '@astrojs/svelte'; +import vue from '@astrojs/vue'; +import solid from '@astrojs/solid-js'; + +// https://astro.build/config +export default defineConfig({ + // Enable many frameworks to support all different kinds of components. + integrations: [preact(), react(), svelte(), vue(), solid()], +}); diff --git a/packages/astro/e2e/fixtures/nested-in-vue/package.json b/packages/astro/e2e/fixtures/nested-in-vue/package.json new file mode 100644 index 000000000000..b25b3e0b1dc7 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-vue/package.json @@ -0,0 +1,21 @@ +{ + "name": "@e2e/nested-in-vue", + "version": "0.0.0", + "private": true, + "devDependencies": { + "@astrojs/preact": "^0.1.2", + "@astrojs/react": "^0.1.2", + "@astrojs/solid-js": "^0.1.2", + "@astrojs/svelte": "^0.1.3", + "@astrojs/vue": "^0.1.4", + "astro": "^1.0.0-beta.32" + }, + "dependencies": { + "preact": "^10.7.2", + "react": "^18.1.0", + "react-dom": "^18.1.0", + "solid-js": "^1.4.2", + "svelte": "^3.48.0", + "vue": "^3.2.36" + } +} diff --git a/packages/astro/e2e/fixtures/nested-in-vue/src/components/PreactCounter.tsx b/packages/astro/e2e/fixtures/nested-in-vue/src/components/PreactCounter.tsx new file mode 100644 index 000000000000..5f20f560d1f6 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-vue/src/components/PreactCounter.tsx @@ -0,0 +1,17 @@ +import { useState } from 'preact/hooks'; + +/** a counter written in Preact */ +export function PreactCounter({ children, id }) { + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/packages/astro/e2e/fixtures/nested-in-vue/src/components/ReactCounter.jsx b/packages/astro/e2e/fixtures/nested-in-vue/src/components/ReactCounter.jsx new file mode 100644 index 000000000000..c7197a072b85 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-vue/src/components/ReactCounter.jsx @@ -0,0 +1,17 @@ +import { useState } from 'react'; + +/** a counter written in React */ +export function Counter({ children, id }) { + const [count, setCount] = useState(0); + const add = () => setCount((i) => i + 1); + const subtract = () => setCount((i) => i - 1); + + return ( +
+ +
{count}
+ +
{children}
+
+ ); +} diff --git a/packages/astro/e2e/fixtures/nested-in-vue/src/components/SolidCounter.tsx b/packages/astro/e2e/fixtures/nested-in-vue/src/components/SolidCounter.tsx new file mode 100644 index 000000000000..afabe43b9e81 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-vue/src/components/SolidCounter.tsx @@ -0,0 +1,17 @@ +import { createSignal } from 'solid-js'; + +/** a counter written with Solid */ +export default function SolidCounter({ children, id }) { + const [count, setCount] = createSignal(0); + const add = () => setCount(count() + 1); + const subtract = () => setCount(count() - 1); + + return ( +
+ +
{count()}
+ +
{children}
+
+ ); +} diff --git a/packages/astro/e2e/fixtures/nested-in-vue/src/components/SvelteCounter.svelte b/packages/astro/e2e/fixtures/nested-in-vue/src/components/SvelteCounter.svelte new file mode 100644 index 000000000000..733f58076a24 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-vue/src/components/SvelteCounter.svelte @@ -0,0 +1,29 @@ + + + +
+ +
{ count }
+ +
+ +
+
+ + diff --git a/packages/astro/e2e/fixtures/nested-in-vue/src/components/VueCounter.vue b/packages/astro/e2e/fixtures/nested-in-vue/src/components/VueCounter.vue new file mode 100644 index 000000000000..d404cc965c52 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-vue/src/components/VueCounter.vue @@ -0,0 +1,34 @@ + + + diff --git a/packages/astro/e2e/fixtures/nested-in-vue/src/pages/index.astro b/packages/astro/e2e/fixtures/nested-in-vue/src/pages/index.astro new file mode 100644 index 000000000000..5e4d47d768b3 --- /dev/null +++ b/packages/astro/e2e/fixtures/nested-in-vue/src/pages/index.astro @@ -0,0 +1,28 @@ +--- +import { Counter as ReactCounter } from '../components/ReactCounter.jsx'; +import { PreactCounter } from '../components/PreactCounter.tsx'; +import SolidCounter from '../components/SolidCounter.tsx'; +import VueCounter from '../components/VueCounter.vue'; +import SvelteCounter from '../components/SvelteCounter.svelte'; + +// Full Astro Component Syntax: +// https://docs.astro.build/core-concepts/astro-components/ +--- + + + + + + + + +
+ + + + + + +
+ + diff --git a/packages/astro/e2e/nested-in-preact.test.js b/packages/astro/e2e/nested-in-preact.test.js new file mode 100644 index 000000000000..ab4d3c6ba298 --- /dev/null +++ b/packages/astro/e2e/nested-in-preact.test.js @@ -0,0 +1,96 @@ +import { test as base, expect } from '@playwright/test'; +import { loadFixture } from './test-utils.js'; + +const test = base.extend({ + astro: async ({}, use) => { + const fixture = await loadFixture({ root: './fixtures/nested-in-preact/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeEach(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterEach(async () => { + await devServer.stop(); +}); + +test.describe('Nested Frameworks in Preact', () => { + test('React counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#react-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#react-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#react-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Preact counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#preact-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#preact-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#preact-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Solid counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#solid-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#solid-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#solid-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Vue counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#vue-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#vue-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#vue-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Svelte counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#svelte-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#svelte-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#svelte-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); +}); diff --git a/packages/astro/e2e/nested-in-react.test.js b/packages/astro/e2e/nested-in-react.test.js new file mode 100644 index 000000000000..5b7a0d18b01e --- /dev/null +++ b/packages/astro/e2e/nested-in-react.test.js @@ -0,0 +1,96 @@ +import { test as base, expect } from '@playwright/test'; +import { loadFixture } from './test-utils.js'; + +const test = base.extend({ + astro: async ({}, use) => { + const fixture = await loadFixture({ root: './fixtures/nested-in-react/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeEach(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterEach(async () => { + await devServer.stop(); +}); + +test.describe('Nested Frameworks in React', () => { + test('React counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#react-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#react-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#react-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Preact counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#preact-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#preact-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#preact-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Solid counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#solid-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#solid-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#solid-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Vue counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#vue-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#vue-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#vue-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Svelte counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#svelte-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#svelte-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#svelte-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); +}); diff --git a/packages/astro/e2e/nested-in-solid.test.js b/packages/astro/e2e/nested-in-solid.test.js new file mode 100644 index 000000000000..11f97f9d8f0c --- /dev/null +++ b/packages/astro/e2e/nested-in-solid.test.js @@ -0,0 +1,97 @@ +import { test as base, expect } from '@playwright/test'; +import { loadFixture } from './test-utils.js'; + +const test = base.extend({ + astro: async ({}, use) => { + const fixture = await loadFixture({ root: './fixtures/nested-in-solid/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeEach(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterEach(async () => { + await devServer.stop(); +}); + +test.describe('Nested Frameworks in Solid', () => { + test('React counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#react-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#react-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#react-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Preact counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#preact-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#preact-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#preact-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Solid counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#solid-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#solid-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#solid-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Vue counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#vue-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#vue-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#vue-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Svelte counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#svelte-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#svelte-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#svelte-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); +}); + diff --git a/packages/astro/e2e/nested-in-svelte.test.js b/packages/astro/e2e/nested-in-svelte.test.js new file mode 100644 index 000000000000..f951854c7f43 --- /dev/null +++ b/packages/astro/e2e/nested-in-svelte.test.js @@ -0,0 +1,96 @@ +import { test as base, expect } from '@playwright/test'; +import { loadFixture } from './test-utils.js'; + +const test = base.extend({ + astro: async ({}, use) => { + const fixture = await loadFixture({ root: './fixtures/nested-in-svelte/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeEach(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterEach(async () => { + await devServer.stop(); +}); + +test.describe('Nested Frameworks in Svelte', () => { + test('React counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#react-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#react-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#react-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Preact counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#preact-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#preact-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#preact-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Solid counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#solid-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#solid-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#solid-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Vue counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#vue-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#vue-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#vue-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Svelte counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#svelte-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#svelte-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#svelte-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); +}); diff --git a/packages/astro/e2e/nested-in-vue.test.js b/packages/astro/e2e/nested-in-vue.test.js new file mode 100644 index 000000000000..c3fa9a20379c --- /dev/null +++ b/packages/astro/e2e/nested-in-vue.test.js @@ -0,0 +1,96 @@ +import { test as base, expect } from '@playwright/test'; +import { loadFixture } from './test-utils.js'; + +const test = base.extend({ + astro: async ({}, use) => { + const fixture = await loadFixture({ root: './fixtures/nested-in-vue/' }); + await use(fixture); + }, +}); + +let devServer; + +test.beforeEach(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterEach(async () => { + await devServer.stop(); +}); + +test.describe('Nested Frameworks in Vue', () => { + test('React counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#react-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#react-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#react-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Preact counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#preact-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#preact-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#preact-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Solid counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#solid-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#solid-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#solid-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Vue counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#vue-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#vue-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#vue-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); + + test('Svelte counter', async ({ astro, page }) => { + await page.goto('/'); + + const counter = await page.locator('#svelte-counter'); + await expect(counter, 'component is visible').toBeVisible(); + + const count = await counter.locator('#svelte-counter-count'); + await expect(count, 'initial count is 0').toHaveText('0'); + + const increment = await counter.locator('#svelte-counter-increment'); + await increment.click(); + + await expect(count, 'count incremented by 1').toHaveText('1'); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82e19b8fcf35..3909975671b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -669,15 +669,12 @@ importers: packages/astro/e2e/fixtures/client-only: specifiers: - '@astrojs/lit': ^0.1.3 '@astrojs/preact': ^0.1.2 '@astrojs/react': ^0.1.2 '@astrojs/solid-js': ^0.1.2 '@astrojs/svelte': ^0.1.3 '@astrojs/vue': ^0.1.4 - '@webcomponents/template-shadowroot': ^0.1.0 astro: ^1.0.0-beta.32 - lit: ^2.2.4 preact: ^10.7.2 react: ^18.1.0 react-dom: ^18.1.0 @@ -685,8 +682,6 @@ importers: svelte: ^3.48.0 vue: ^3.2.36 dependencies: - '@webcomponents/template-shadowroot': 0.1.0 - lit: 2.2.4 preact: 10.7.2 react: 18.1.0 react-dom: 18.1.0_react@18.1.0 @@ -694,7 +689,6 @@ importers: svelte: 3.48.0 vue: 3.2.36 devDependencies: - '@astrojs/lit': link:../../../../integrations/lit '@astrojs/preact': link:../../../../integrations/preact '@astrojs/react': link:../../../../integrations/react '@astrojs/solid-js': link:../../../../integrations/solid @@ -749,6 +743,151 @@ importers: '@astrojs/vue': link:../../../../integrations/vue astro: link:../../.. + packages/astro/e2e/fixtures/nested-in-preact: + specifiers: + '@astrojs/preact': ^0.1.2 + '@astrojs/react': ^0.1.2 + '@astrojs/solid-js': ^0.1.2 + '@astrojs/svelte': ^0.1.3 + '@astrojs/vue': ^0.1.4 + astro: ^1.0.0-beta.32 + preact: ^10.7.2 + react: ^18.1.0 + react-dom: ^18.1.0 + solid-js: ^1.4.2 + svelte: ^3.48.0 + vue: ^3.2.36 + dependencies: + preact: 10.7.2 + react: 18.1.0 + react-dom: 18.1.0_react@18.1.0 + solid-js: 1.4.2 + svelte: 3.48.0 + vue: 3.2.36 + devDependencies: + '@astrojs/preact': link:../../../../integrations/preact + '@astrojs/react': link:../../../../integrations/react + '@astrojs/solid-js': link:../../../../integrations/solid + '@astrojs/svelte': link:../../../../integrations/svelte + '@astrojs/vue': link:../../../../integrations/vue + astro: link:../../.. + + packages/astro/e2e/fixtures/nested-in-react: + specifiers: + '@astrojs/preact': ^0.1.2 + '@astrojs/react': ^0.1.2 + '@astrojs/solid-js': ^0.1.2 + '@astrojs/svelte': ^0.1.3 + '@astrojs/vue': ^0.1.4 + astro: ^1.0.0-beta.32 + preact: ^10.7.2 + react: ^18.1.0 + react-dom: ^18.1.0 + solid-js: ^1.4.2 + svelte: ^3.48.0 + vue: ^3.2.36 + dependencies: + preact: 10.7.2 + react: 18.1.0 + react-dom: 18.1.0_react@18.1.0 + solid-js: 1.4.2 + svelte: 3.48.0 + vue: 3.2.36 + devDependencies: + '@astrojs/preact': link:../../../../integrations/preact + '@astrojs/react': link:../../../../integrations/react + '@astrojs/solid-js': link:../../../../integrations/solid + '@astrojs/svelte': link:../../../../integrations/svelte + '@astrojs/vue': link:../../../../integrations/vue + astro: link:../../.. + + packages/astro/e2e/fixtures/nested-in-solid: + specifiers: + '@astrojs/preact': ^0.1.2 + '@astrojs/react': ^0.1.2 + '@astrojs/solid-js': ^0.1.2 + '@astrojs/svelte': ^0.1.3 + '@astrojs/vue': ^0.1.4 + astro: ^1.0.0-beta.32 + preact: ^10.7.2 + react: ^18.1.0 + react-dom: ^18.1.0 + solid-js: ^1.4.2 + svelte: ^3.48.0 + vue: ^3.2.36 + dependencies: + preact: 10.7.2 + react: 18.1.0 + react-dom: 18.1.0_react@18.1.0 + solid-js: 1.4.2 + svelte: 3.48.0 + vue: 3.2.36 + devDependencies: + '@astrojs/preact': link:../../../../integrations/preact + '@astrojs/react': link:../../../../integrations/react + '@astrojs/solid-js': link:../../../../integrations/solid + '@astrojs/svelte': link:../../../../integrations/svelte + '@astrojs/vue': link:../../../../integrations/vue + astro: link:../../.. + + packages/astro/e2e/fixtures/nested-in-svelte: + specifiers: + '@astrojs/preact': ^0.1.2 + '@astrojs/react': ^0.1.2 + '@astrojs/solid-js': ^0.1.2 + '@astrojs/svelte': ^0.1.3 + '@astrojs/vue': ^0.1.4 + astro: ^1.0.0-beta.32 + preact: ^10.7.2 + react: ^18.1.0 + react-dom: ^18.1.0 + solid-js: ^1.4.2 + svelte: ^3.48.0 + vue: ^3.2.36 + dependencies: + preact: 10.7.2 + react: 18.1.0 + react-dom: 18.1.0_react@18.1.0 + solid-js: 1.4.2 + svelte: 3.48.0 + vue: 3.2.36 + devDependencies: + '@astrojs/preact': link:../../../../integrations/preact + '@astrojs/react': link:../../../../integrations/react + '@astrojs/solid-js': link:../../../../integrations/solid + '@astrojs/svelte': link:../../../../integrations/svelte + '@astrojs/vue': link:../../../../integrations/vue + astro: link:../../.. + + packages/astro/e2e/fixtures/nested-in-vue: + specifiers: + '@astrojs/preact': ^0.1.2 + '@astrojs/react': ^0.1.2 + '@astrojs/solid-js': ^0.1.2 + '@astrojs/svelte': ^0.1.3 + '@astrojs/vue': ^0.1.4 + astro: ^1.0.0-beta.32 + preact: ^10.7.2 + react: ^18.1.0 + react-dom: ^18.1.0 + solid-js: ^1.4.2 + svelte: ^3.48.0 + vue: ^3.2.36 + dependencies: + preact: 10.7.2 + react: 18.1.0 + react-dom: 18.1.0_react@18.1.0 + solid-js: 1.4.2 + svelte: 3.48.0 + vue: 3.2.36 + devDependencies: + '@astrojs/preact': link:../../../../integrations/preact + '@astrojs/react': link:../../../../integrations/react + '@astrojs/solid-js': link:../../../../integrations/solid + '@astrojs/svelte': link:../../../../integrations/svelte + '@astrojs/vue': link:../../../../integrations/vue + astro: link:../../.. + packages/astro/e2e/fixtures/nested-styles: specifiers: astro: workspace:* From 0a785629ea1ed98a00e66c7385998e7eb8e28e3e Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 27 May 2022 13:35:32 -0500 Subject: [PATCH 10/15] Update packages/astro/src/runtime/client/events.ts Co-authored-by: Matthew Phillips --- packages/astro/src/runtime/client/events.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/runtime/client/events.ts b/packages/astro/src/runtime/client/events.ts index 3c84dff36318..aa8e92736350 100644 --- a/packages/astro/src/runtime/client/events.ts +++ b/packages/astro/src/runtime/client/events.ts @@ -18,7 +18,7 @@ export const listen = (cb: (...args: any[]) => any) => window.addEventListener(H if (!(window as any)[HYDRATE_KEY]) { if ('MutationObserver' in window) { - new MutationObserver(() => notify()).observe(document.body, { subtree: true, childList: true }); + new MutationObserver(notify).observe(document.body, { subtree: true, childList: true }); } (window as any)[HYDRATE_KEY] = true; } From 16de67faff9e6800594f03fd173b786c370e5fdf Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 27 May 2022 14:14:14 -0500 Subject: [PATCH 11/15] chore: add changeset --- .changeset/unlucky-gorillas-beg.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/unlucky-gorillas-beg.md diff --git a/.changeset/unlucky-gorillas-beg.md b/.changeset/unlucky-gorillas-beg.md new file mode 100644 index 000000000000..5e667a06233f --- /dev/null +++ b/.changeset/unlucky-gorillas-beg.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Implements improved hydration event system, meaning hydration for client:only and nested frameworks should be see significant stability improvements From 95e560ec41359cfd2b10d8ea80bbd15993e5e7c9 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 27 May 2022 14:14:28 -0500 Subject: [PATCH 12/15] fix(preact): ignore hydrate roots --- packages/integrations/preact/client.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/integrations/preact/client.js b/packages/integrations/preact/client.js index 5ece5ddb2b81..6ff40d2ae1c5 100644 --- a/packages/integrations/preact/client.js +++ b/packages/integrations/preact/client.js @@ -1,8 +1,10 @@ import { h, render } from 'preact'; import StaticHtml from './static-html.js'; -export default (element) => (Component, props, children) => +export default (element) => (Component, props, children) => { + if (!element.hasAttribute('ssr')) return; render( h(Component, props, children != null ? h(StaticHtml, { value: children }) : children), element ); +} From ba27eaae5514701f4b7bb6259f682fe82821a23d Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 27 May 2022 14:51:11 -0500 Subject: [PATCH 13/15] chore: remove `ssr` check in integrations --- packages/integrations/preact/client.js | 1 - packages/integrations/react/client.js | 1 - packages/integrations/solid/client.js | 1 - packages/integrations/svelte/client.js | 1 - packages/integrations/vue/client.js | 1 - 5 files changed, 5 deletions(-) diff --git a/packages/integrations/preact/client.js b/packages/integrations/preact/client.js index 6ff40d2ae1c5..c3c5d1d0d912 100644 --- a/packages/integrations/preact/client.js +++ b/packages/integrations/preact/client.js @@ -2,7 +2,6 @@ import { h, render } from 'preact'; import StaticHtml from './static-html.js'; export default (element) => (Component, props, children) => { - if (!element.hasAttribute('ssr')) return; render( h(Component, props, children != null ? h(StaticHtml, { value: children }) : children), element diff --git a/packages/integrations/react/client.js b/packages/integrations/react/client.js index 2828d2cbe3ac..493a146ab19e 100644 --- a/packages/integrations/react/client.js +++ b/packages/integrations/react/client.js @@ -12,7 +12,6 @@ function isAlreadyHydrated(element) { export default (element) => (Component, props, children, { client }) => { - if (!element.hasAttribute('ssr')) return; const componentEl = createElement( Component, props, diff --git a/packages/integrations/solid/client.js b/packages/integrations/solid/client.js index 005f3c980376..7b688d1c48de 100644 --- a/packages/integrations/solid/client.js +++ b/packages/integrations/solid/client.js @@ -6,7 +6,6 @@ export default (element) => (Component, props, childHTML, { client }) => { if (!window._$HY) { window._$HY = { events: [], completed: new WeakSet(), r: {} }; } - if (!element.hasAttribute('ssr')) return; const fn = client === 'only' ? render : hydrate; diff --git a/packages/integrations/svelte/client.js b/packages/integrations/svelte/client.js index 3f401b544f94..3e19a426b5e4 100644 --- a/packages/integrations/svelte/client.js +++ b/packages/integrations/svelte/client.js @@ -2,7 +2,6 @@ import SvelteWrapper from './Wrapper.svelte'; export default (target) => { return (component, props, children, { client }) => { - if (!target.hasAttribute('ssr')) return; delete props['class']; try { new SvelteWrapper({ diff --git a/packages/integrations/vue/client.js b/packages/integrations/vue/client.js index 4832a984760b..3685ffc87663 100644 --- a/packages/integrations/vue/client.js +++ b/packages/integrations/vue/client.js @@ -3,7 +3,6 @@ import StaticHtml from './static-html.js'; export default (element) => (Component, props, children, { client }) => { delete props['class']; - if (!element.hasAttribute('ssr')) return; // Expose name on host component for Vue devtools const name = Component.name ? `${Component.name} Host` : undefined; From e833e41cccf43d40bce1af401d20baffd73d0174 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 27 May 2022 15:06:43 -0500 Subject: [PATCH 14/15] Revert "chore: remove `ssr` check in integrations" This reverts commit ba27eaae5514701f4b7bb6259f682fe82821a23d. --- packages/integrations/preact/client.js | 1 + packages/integrations/react/client.js | 1 + packages/integrations/solid/client.js | 1 + packages/integrations/svelte/client.js | 1 + packages/integrations/vue/client.js | 1 + 5 files changed, 5 insertions(+) diff --git a/packages/integrations/preact/client.js b/packages/integrations/preact/client.js index c3c5d1d0d912..6ff40d2ae1c5 100644 --- a/packages/integrations/preact/client.js +++ b/packages/integrations/preact/client.js @@ -2,6 +2,7 @@ import { h, render } from 'preact'; import StaticHtml from './static-html.js'; export default (element) => (Component, props, children) => { + if (!element.hasAttribute('ssr')) return; render( h(Component, props, children != null ? h(StaticHtml, { value: children }) : children), element diff --git a/packages/integrations/react/client.js b/packages/integrations/react/client.js index 493a146ab19e..2828d2cbe3ac 100644 --- a/packages/integrations/react/client.js +++ b/packages/integrations/react/client.js @@ -12,6 +12,7 @@ function isAlreadyHydrated(element) { export default (element) => (Component, props, children, { client }) => { + if (!element.hasAttribute('ssr')) return; const componentEl = createElement( Component, props, diff --git a/packages/integrations/solid/client.js b/packages/integrations/solid/client.js index 7b688d1c48de..005f3c980376 100644 --- a/packages/integrations/solid/client.js +++ b/packages/integrations/solid/client.js @@ -6,6 +6,7 @@ export default (element) => (Component, props, childHTML, { client }) => { if (!window._$HY) { window._$HY = { events: [], completed: new WeakSet(), r: {} }; } + if (!element.hasAttribute('ssr')) return; const fn = client === 'only' ? render : hydrate; diff --git a/packages/integrations/svelte/client.js b/packages/integrations/svelte/client.js index 3e19a426b5e4..3f401b544f94 100644 --- a/packages/integrations/svelte/client.js +++ b/packages/integrations/svelte/client.js @@ -2,6 +2,7 @@ import SvelteWrapper from './Wrapper.svelte'; export default (target) => { return (component, props, children, { client }) => { + if (!target.hasAttribute('ssr')) return; delete props['class']; try { new SvelteWrapper({ diff --git a/packages/integrations/vue/client.js b/packages/integrations/vue/client.js index 3685ffc87663..4832a984760b 100644 --- a/packages/integrations/vue/client.js +++ b/packages/integrations/vue/client.js @@ -3,6 +3,7 @@ import StaticHtml from './static-html.js'; export default (element) => (Component, props, children, { client }) => { delete props['class']; + if (!element.hasAttribute('ssr')) return; // Expose name on host component for Vue devtools const name = Component.name ? `${Component.name} Host` : undefined; From 00ccc4839cc92b786ab252eb9e17dcf057ed6965 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 27 May 2022 15:09:10 -0500 Subject: [PATCH 15/15] chore: add changeset --- .changeset/polite-hounds-lick.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/polite-hounds-lick.md diff --git a/.changeset/polite-hounds-lick.md b/.changeset/polite-hounds-lick.md new file mode 100644 index 000000000000..8b2191648117 --- /dev/null +++ b/.changeset/polite-hounds-lick.md @@ -0,0 +1,9 @@ +--- +'@astrojs/preact': patch +'@astrojs/react': patch +'@astrojs/solid-js': patch +'@astrojs/svelte': patch +'@astrojs/vue': patch +--- + +Update client hydration to check for `ssr` attribute. Requires `astro@^1.0.0-beta.36`.