Fix race condition with directive definitions (#4375)

This commit is contained in:
Matthew Phillips 2022-08-22 10:39:16 -04:00 committed by GitHub
parent 439f1d1c0d
commit 5e82f6c245
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 128 additions and 2 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes race condition between directives being defined

View file

@ -0,0 +1,5 @@
import preact from '@astrojs/preact';
export default {
integrations: [preact()]
};

View 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:*"
}
}

View file

@ -0,0 +1,9 @@
---
import One from './One.jsx';
---
<div>
<span>Before one</span>
<One name="One" client:idle />
</div>

View file

@ -0,0 +1,6 @@
---
import Wrapper from './Wrapper.astro';
---
<Wrapper />
<slot />

View file

@ -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>
);
}

View file

@ -0,0 +1,8 @@
---
import One from './One.jsx';
import Deeper from './Deeper.astro';
---
<One name="Two" client:visible />
<Deeper />

View file

@ -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>

View 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');
});
});

View file

@ -10,3 +10,4 @@
setTimeout(cb, 200); setTimeout(cb, 200);
} }
}; };
window.dispatchEvent(new Event('astro:idle'));

View file

@ -4,3 +4,4 @@
await hydrate(); await hydrate();
})(); })();
}; };
window.dispatchEvent(new Event('astro:load'));

View file

@ -16,3 +16,4 @@
} }
} }
}; };
window.dispatchEvent(new Event('astro:media'));

View file

@ -7,3 +7,4 @@
await hydrate(); await hydrate();
})(); })();
}; };
window.dispatchEvent(new Event('astro:only'));

View file

@ -24,3 +24,4 @@
io.observe(child); io.observe(child);
} }
}; };
window.dispatchEvent(new Event('astro:visible'));

View file

@ -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([

View file

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