Skip to content

Commit

Permalink
feat: support setting timeout for client:idle (#11743)
Browse files Browse the repository at this point in the history
* feat: support setting timeout for `client:idle`

* tst: add client:idle timeout e2e test

* Update .changeset/clever-emus-roll.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* Update .changeset/clever-emus-roll.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* nit: we wait for times, not values!

---------

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
  • Loading branch information
ph1p and sarah11918 committed Aug 28, 2024
1 parent 5af8b4f commit cce0894
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 4 deletions.
11 changes: 11 additions & 0 deletions .changeset/clever-emus-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'astro': minor
---

Adds a new, optional property `timeout` for the `client:idle` directive.

This value allows you to specify a maximum time to wait, in milliseconds, before hydrating a UI framework component, even if the page is not yet done with its initial load. This means you can delay hydration for lower-priority UI elements with more control to ensure your element is interactive within a specified time frame.

```astro
<ShowHideButton client:idle={{timeout: 500}} />
```
33 changes: 33 additions & 0 deletions packages/astro/e2e/client-idle-timeout.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { expect } from '@playwright/test';
import { testFactory, waitForHydrate } from './test-utils.js';

const test = testFactory({ root: './fixtures/client-idle-timeout/' });

let devServer;

test.beforeAll(async ({ astro }) => {
devServer = await astro.startDevServer();
});

test.afterAll(async () => {
await devServer.stop();
});

test.describe('Client idle timeout', () => {
test('React counter', async ({ astro, page }) => {
await page.goto(astro.resolveUrl('/'));

const counter = page.locator('#react-counter');
await expect(counter, 'component is visible').toBeVisible();

const count = counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');

await waitForHydrate(page, counter);

const inc = counter.locator('.increment');
await inc.click();

await expect(count, 'count incremented by 1').toHaveText('1');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import react from '@astrojs/react';
import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({
integrations: [
react(),
],
});
13 changes: 13 additions & 0 deletions packages/astro/e2e/fixtures/client-idle-timeout/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "@e2e/client-idle-timeout",
"version": "0.0.0",
"private": true,
"devDependencies": {
"@astrojs/react": "workspace:*",
"astro": "workspace:*"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React, { useState } from 'react';

export default function Counter({ children, count: initialCount = 0, id }) {
const [count, setCount] = useState(initialCount);
const add = () => setCount((i) => i + 1);
const subtract = () => setCount((i) => i - 1);

return (
<>
<div id={id} className="counter">
<button className="decrement" onClick={subtract}>-</button>
<pre>{count}</pre>
<button className="increment" onClick={add}>+</button>
</div>
<div className="counter-message">{children}</div>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
import Counter from '../components/Counter.jsx';
---

<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
</head>
<body>
<main>
<Counter id="react-counter" client:idle={{timeout: 200}}></Counter>
</main>
</body>
</html>
2 changes: 1 addition & 1 deletion packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export type {

export interface AstroBuiltinProps {
'client:load'?: boolean;
'client:idle'?: boolean;
'client:idle'?: IdleRequestOptions | boolean;
'client:media'?: string;
'client:visible'?: ClientVisibleOptions | boolean;
'client:only'?: boolean | string;
Expand Down
14 changes: 11 additions & 3 deletions packages/astro/src/runtime/client/idle.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import type { ClientDirective } from '../../@types/astro.js';

const idleDirective: ClientDirective = (load) => {
const idleDirective: ClientDirective = (load, options) => {
const cb = async () => {
const hydrate = await load();
await hydrate();
};

const rawOptions =
typeof options.value === 'object' ? (options.value as IdleRequestOptions) : undefined;

const idleOptions: IdleRequestOptions = {
timeout: rawOptions?.timeout,
};

if ('requestIdleCallback' in window) {
(window as any).requestIdleCallback(cb);
(window as any).requestIdleCallback(cb, idleOptions);
} else {
setTimeout(cb, 200);
setTimeout(cb, idleOptions.timeout || 200);
}
};

Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

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

0 comments on commit cce0894

Please sign in to comment.