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:*