Fix race condition with directive definitions (#4375)
This commit is contained in:
parent
439f1d1c0d
commit
5e82f6c245
16 changed files with 128 additions and 2 deletions
5
.changeset/smooth-nails-remain.md
Normal file
5
.changeset/smooth-nails-remain.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fixes race condition between directives being defined
|
|
@ -0,0 +1,5 @@
|
||||||
|
import preact from '@astrojs/preact';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
integrations: [preact()]
|
||||||
|
};
|
13
packages/astro/e2e/fixtures/hydration-race/package.json
Normal file
13
packages/astro/e2e/fixtures/hydration-race/package.json
Normal file
|
@ -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:*"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
import One from './One.jsx';
|
||||||
|
---
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span>Before one</span>
|
||||||
|
<One name="One" client:idle />
|
||||||
|
</div>
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
import Wrapper from './Wrapper.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Wrapper />
|
||||||
|
<slot />
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { h } from 'preact';
|
||||||
|
|
||||||
|
export default function({ name }) {
|
||||||
|
const inTheClient = import.meta.env.SSR ? '' : ' in the client'
|
||||||
|
return (
|
||||||
|
<div id={name.toLowerCase()}>Hello {name}{inTheClient}</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
import One from './One.jsx';
|
||||||
|
import Deeper from './Deeper.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
<One name="Two" client:visible />
|
||||||
|
<Deeper />
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
import Layout from '../components/Layout.astro';
|
||||||
|
import One from '../components/One.jsx';
|
||||||
|
---
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Testing</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<One name="Four" client:idle />
|
||||||
|
<Layout>
|
||||||
|
<One name="Three" client:visible />
|
||||||
|
</Layout>
|
||||||
|
</body>
|
||||||
|
</html>
|
36
packages/astro/e2e/hydration-race.test.js
Normal file
36
packages/astro/e2e/hydration-race.test.js
Normal file
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
|
@ -10,3 +10,4 @@
|
||||||
setTimeout(cb, 200);
|
setTimeout(cb, 200);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
window.dispatchEvent(new Event('astro:idle'));
|
||||||
|
|
|
@ -4,3 +4,4 @@
|
||||||
await hydrate();
|
await hydrate();
|
||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
|
window.dispatchEvent(new Event('astro:load'));
|
||||||
|
|
|
@ -16,3 +16,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
window.dispatchEvent(new Event('astro:media'));
|
||||||
|
|
|
@ -7,3 +7,4 @@
|
||||||
await hydrate();
|
await hydrate();
|
||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
|
window.dispatchEvent(new Event('astro:only'));
|
||||||
|
|
|
@ -24,3 +24,4 @@
|
||||||
io.observe(child);
|
io.observe(child);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
window.dispatchEvent(new Event('astro:visible'));
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
type directiveAstroKeys = 'load' | 'idle' | 'visible' | 'media' | 'only';
|
type directiveAstroKeys = 'load' | 'idle' | 'visible' | 'media' | 'only';
|
||||||
|
|
||||||
declare const Astro: {
|
declare const Astro: {
|
||||||
[k in directiveAstroKeys]: (
|
[k in directiveAstroKeys]?: (
|
||||||
fn: () => Promise<() => void>,
|
fn: () => Promise<() => void>,
|
||||||
opts: Record<string, any>,
|
opts: Record<string, any>,
|
||||||
root: HTMLElement
|
root: HTMLElement
|
||||||
|
@ -57,8 +57,16 @@ declare const Astro: {
|
||||||
async childrenConnectedCallback() {
|
async childrenConnectedCallback() {
|
||||||
window.addEventListener('astro:hydrate', this.hydrate);
|
window.addEventListener('astro:hydrate', this.hydrate);
|
||||||
await import(this.getAttribute('before-hydration-url')!);
|
await import(this.getAttribute('before-hydration-url')!);
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
start() {
|
||||||
const opts = JSON.parse(this.getAttribute('opts')!) as Record<string, any>;
|
const opts = JSON.parse(this.getAttribute('opts')!) as Record<string, any>;
|
||||||
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 () => {
|
async () => {
|
||||||
const rendererUrl = this.getAttribute('renderer-url');
|
const rendererUrl = this.getAttribute('renderer-url');
|
||||||
const [componentModule, { default: hydrator }] = await Promise.all([
|
const [componentModule, { default: hydrator }] = await Promise.all([
|
||||||
|
|
|
@ -660,6 +660,14 @@ importers:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_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:
|
packages/astro/e2e/fixtures/invalidate-script-deps:
|
||||||
specifiers:
|
specifiers:
|
||||||
astro: workspace:*
|
astro: workspace:*
|
||||||
|
|
Loading…
Reference in a new issue