From 5e82f6c245be332764fcd5a90be491a430655c87 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Mon, 22 Aug 2022 10:39:16 -0400 Subject: [PATCH] Fix race condition with directive definitions (#4375) --- .changeset/smooth-nails-remain.md | 5 +++ .../fixtures/hydration-race/astro.config.mjs | 5 +++ .../e2e/fixtures/hydration-race/package.json | 13 +++++++ .../src/components/Deeper.astro | 9 +++++ .../src/components/Layout.astro | 6 ++++ .../hydration-race/src/components/One.jsx | 8 +++++ .../src/components/Wrapper.astro | 8 +++++ .../hydration-race/src/pages/slot.astro | 15 ++++++++ packages/astro/e2e/hydration-race.test.js | 36 +++++++++++++++++++ packages/astro/src/runtime/client/idle.ts | 1 + packages/astro/src/runtime/client/load.ts | 1 + packages/astro/src/runtime/client/media.ts | 1 + packages/astro/src/runtime/client/only.ts | 1 + packages/astro/src/runtime/client/visible.ts | 1 + .../astro/src/runtime/server/astro-island.ts | 12 +++++-- pnpm-lock.yaml | 8 +++++ 16 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 .changeset/smooth-nails-remain.md create mode 100644 packages/astro/e2e/fixtures/hydration-race/astro.config.mjs create mode 100644 packages/astro/e2e/fixtures/hydration-race/package.json create mode 100644 packages/astro/e2e/fixtures/hydration-race/src/components/Deeper.astro create mode 100644 packages/astro/e2e/fixtures/hydration-race/src/components/Layout.astro create mode 100644 packages/astro/e2e/fixtures/hydration-race/src/components/One.jsx create mode 100644 packages/astro/e2e/fixtures/hydration-race/src/components/Wrapper.astro create mode 100644 packages/astro/e2e/fixtures/hydration-race/src/pages/slot.astro create mode 100644 packages/astro/e2e/hydration-race.test.js diff --git a/.changeset/smooth-nails-remain.md b/.changeset/smooth-nails-remain.md new file mode 100644 index 000000000000..185e12f7eea0 --- /dev/null +++ b/.changeset/smooth-nails-remain.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes race condition between directives being defined diff --git a/packages/astro/e2e/fixtures/hydration-race/astro.config.mjs b/packages/astro/e2e/fixtures/hydration-race/astro.config.mjs new file mode 100644 index 000000000000..c9662ed094ff --- /dev/null +++ b/packages/astro/e2e/fixtures/hydration-race/astro.config.mjs @@ -0,0 +1,5 @@ +import preact from '@astrojs/preact'; + +export default { + integrations: [preact()] +}; diff --git a/packages/astro/e2e/fixtures/hydration-race/package.json b/packages/astro/e2e/fixtures/hydration-race/package.json new file mode 100644 index 000000000000..ee28e25f93f5 --- /dev/null +++ b/packages/astro/e2e/fixtures/hydration-race/package.json @@ -0,0 +1,13 @@ +{ + "name": "@e2e/hydration-race", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "astro dev", + "build": "astro build" + }, + "dependencies": { + "astro": "workspace:*", + "@astrojs/preact": "workspace:*" + } +} diff --git a/packages/astro/e2e/fixtures/hydration-race/src/components/Deeper.astro b/packages/astro/e2e/fixtures/hydration-race/src/components/Deeper.astro new file mode 100644 index 000000000000..76945dff9527 --- /dev/null +++ b/packages/astro/e2e/fixtures/hydration-race/src/components/Deeper.astro @@ -0,0 +1,9 @@ +--- +import One from './One.jsx'; +--- + +
+ Before one + +
+ diff --git a/packages/astro/e2e/fixtures/hydration-race/src/components/Layout.astro b/packages/astro/e2e/fixtures/hydration-race/src/components/Layout.astro new file mode 100644 index 000000000000..8ee14c08f4a8 --- /dev/null +++ b/packages/astro/e2e/fixtures/hydration-race/src/components/Layout.astro @@ -0,0 +1,6 @@ +--- +import Wrapper from './Wrapper.astro'; +--- + + + diff --git a/packages/astro/e2e/fixtures/hydration-race/src/components/One.jsx b/packages/astro/e2e/fixtures/hydration-race/src/components/One.jsx new file mode 100644 index 000000000000..7b76c8f6ccc6 --- /dev/null +++ b/packages/astro/e2e/fixtures/hydration-race/src/components/One.jsx @@ -0,0 +1,8 @@ +import { h } from 'preact'; + +export default function({ name }) { + const inTheClient = import.meta.env.SSR ? '' : ' in the client' + return ( +
Hello {name}{inTheClient}
+ ); +} diff --git a/packages/astro/e2e/fixtures/hydration-race/src/components/Wrapper.astro b/packages/astro/e2e/fixtures/hydration-race/src/components/Wrapper.astro new file mode 100644 index 000000000000..a3de9b8ecc6b --- /dev/null +++ b/packages/astro/e2e/fixtures/hydration-race/src/components/Wrapper.astro @@ -0,0 +1,8 @@ +--- +import One from './One.jsx'; +import Deeper from './Deeper.astro'; +--- + + + + diff --git a/packages/astro/e2e/fixtures/hydration-race/src/pages/slot.astro b/packages/astro/e2e/fixtures/hydration-race/src/pages/slot.astro new file mode 100644 index 000000000000..f509a2bf7b75 --- /dev/null +++ b/packages/astro/e2e/fixtures/hydration-race/src/pages/slot.astro @@ -0,0 +1,15 @@ +--- +import Layout from '../components/Layout.astro'; +import One from '../components/One.jsx'; +--- + + + Testing + + + + + + + + diff --git a/packages/astro/e2e/hydration-race.test.js b/packages/astro/e2e/hydration-race.test.js new file mode 100644 index 000000000000..e516adfc8423 --- /dev/null +++ b/packages/astro/e2e/hydration-race.test.js @@ -0,0 +1,36 @@ +import { expect } from '@playwright/test'; +import { testFactory } from './test-utils.js'; + +const test = testFactory({ + root: './fixtures/hydration-race/', +}); + +let devServer; + +test.beforeEach(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterEach(async () => { + await devServer.stop(); +}); + +test.describe('Hydration race', () => { + test('Islands inside of slots hydrate', async ({ page, astro }) => { + await page.goto('/slot'); + + const html = await page.content(); + + const one = page.locator('#one'); + await expect(one, 'updated text').toHaveText('Hello One in the client'); + + const two = page.locator('#two'); + await expect(two, 'updated text').toHaveText('Hello Two in the client'); + + const three = page.locator('#three'); + await expect(three, 'updated text').toHaveText('Hello Three in the client'); + + const four = page.locator('#four'); + await expect(four, 'updated text').toHaveText('Hello Four in the client'); + }); +}); diff --git a/packages/astro/src/runtime/client/idle.ts b/packages/astro/src/runtime/client/idle.ts index 90090731e09d..4af28bd46180 100644 --- a/packages/astro/src/runtime/client/idle.ts +++ b/packages/astro/src/runtime/client/idle.ts @@ -10,3 +10,4 @@ setTimeout(cb, 200); } }; +window.dispatchEvent(new Event('astro:idle')); diff --git a/packages/astro/src/runtime/client/load.ts b/packages/astro/src/runtime/client/load.ts index 9081799ed622..426c6c68aae9 100644 --- a/packages/astro/src/runtime/client/load.ts +++ b/packages/astro/src/runtime/client/load.ts @@ -4,3 +4,4 @@ await hydrate(); })(); }; +window.dispatchEvent(new Event('astro:load')); diff --git a/packages/astro/src/runtime/client/media.ts b/packages/astro/src/runtime/client/media.ts index 0bf11e448df5..c180d396a1e8 100644 --- a/packages/astro/src/runtime/client/media.ts +++ b/packages/astro/src/runtime/client/media.ts @@ -16,3 +16,4 @@ } } }; +window.dispatchEvent(new Event('astro:media')); diff --git a/packages/astro/src/runtime/client/only.ts b/packages/astro/src/runtime/client/only.ts index 78832e9dcbc8..e8272edbb429 100644 --- a/packages/astro/src/runtime/client/only.ts +++ b/packages/astro/src/runtime/client/only.ts @@ -7,3 +7,4 @@ await hydrate(); })(); }; +window.dispatchEvent(new Event('astro:only')); diff --git a/packages/astro/src/runtime/client/visible.ts b/packages/astro/src/runtime/client/visible.ts index eac73115ff3c..28975040cf0a 100644 --- a/packages/astro/src/runtime/client/visible.ts +++ b/packages/astro/src/runtime/client/visible.ts @@ -24,3 +24,4 @@ io.observe(child); } }; +window.dispatchEvent(new Event('astro:visible')); diff --git a/packages/astro/src/runtime/server/astro-island.ts b/packages/astro/src/runtime/server/astro-island.ts index 96b1b8b6e0b8..07ea982d3dc8 100644 --- a/packages/astro/src/runtime/server/astro-island.ts +++ b/packages/astro/src/runtime/server/astro-island.ts @@ -5,7 +5,7 @@ type directiveAstroKeys = 'load' | 'idle' | 'visible' | 'media' | 'only'; declare const Astro: { - [k in directiveAstroKeys]: ( + [k in directiveAstroKeys]?: ( fn: () => Promise<() => void>, opts: Record, root: HTMLElement @@ -57,8 +57,16 @@ declare const Astro: { async childrenConnectedCallback() { window.addEventListener('astro:hydrate', this.hydrate); await import(this.getAttribute('before-hydration-url')!); + this.start(); + } + start() { const opts = JSON.parse(this.getAttribute('opts')!) as Record; - Astro[this.getAttribute('client') as directiveAstroKeys]( + const directive = this.getAttribute('client') as directiveAstroKeys; + if(Astro[directive] === undefined) { + window.addEventListener(`astro:${directive}`, () => this.start(), { once: true }); + return; + } + Astro[directive]!( async () => { const rendererUrl = this.getAttribute('renderer-url'); const [componentModule, { default: hydrator }] = await Promise.all([ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de355297a331..9a449db84ae1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -660,6 +660,14 @@ importers: react: 18.2.0 react-dom: 18.2.0_react@18.2.0 + packages/astro/e2e/fixtures/hydration-race: + specifiers: + '@astrojs/preact': workspace:* + astro: workspace:* + dependencies: + '@astrojs/preact': link:../../../../integrations/preact + astro: link:../../.. + packages/astro/e2e/fixtures/invalidate-script-deps: specifiers: astro: workspace:*