Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix race condition with directive definitions #4375

Merged
merged 1 commit into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/smooth-nails-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes race condition between directives being defined
5 changes: 5 additions & 0 deletions packages/astro/e2e/fixtures/hydration-race/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import preact from '@astrojs/preact';

export default {
integrations: [preact()]
};
13 changes: 13 additions & 0 deletions packages/astro/e2e/fixtures/hydration-race/package.json
Original file line number Diff line number Diff line change
@@ -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:*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
import One from './One.jsx';
---

<div>
<span>Before one</span>
<One name="One" client:idle />
</div>

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
import Wrapper from './Wrapper.astro';
---

<Wrapper />
<slot />
Original file line number Diff line number Diff line change
@@ -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>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
import One from './One.jsx';
import Deeper from './Deeper.astro';
---


<One name="Two" client:visible />
<Deeper />
15 changes: 15 additions & 0 deletions packages/astro/e2e/fixtures/hydration-race/src/pages/slot.astro
Original file line number Diff line number Diff line change
@@ -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 changes: 36 additions & 0 deletions packages/astro/e2e/hydration-race.test.js
Original file line number Diff line number Diff line change
@@ -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');
});
});
1 change: 1 addition & 0 deletions packages/astro/src/runtime/client/idle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
setTimeout(cb, 200);
}
};
window.dispatchEvent(new Event('astro:idle'));
1 change: 1 addition & 0 deletions packages/astro/src/runtime/client/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
await hydrate();
})();
};
window.dispatchEvent(new Event('astro:load'));
1 change: 1 addition & 0 deletions packages/astro/src/runtime/client/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
}
}
};
window.dispatchEvent(new Event('astro:media'));
1 change: 1 addition & 0 deletions packages/astro/src/runtime/client/only.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
await hydrate();
})();
};
window.dispatchEvent(new Event('astro:only'));
1 change: 1 addition & 0 deletions packages/astro/src/runtime/client/visible.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@
io.observe(child);
}
};
window.dispatchEvent(new Event('astro:visible'));
12 changes: 10 additions & 2 deletions packages/astro/src/runtime/server/astro-island.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>,
root: HTMLElement
Expand Down Expand Up @@ -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<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 });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice and clean 👏

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

()=>this.start() may be simply passed as this.start to make it cleaner

return;
}
Astro[directive]!(
async () => {
const rendererUrl = this.getAttribute('renderer-url');
const [componentModule, { default: hydrator }] = await Promise.all([
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.