From cce08945340312776a0480fc9ffe43929257639a Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 28 Aug 2024 13:09:19 +0200 Subject: [PATCH] feat: support setting timeout for `client:idle` (#11743) * 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 * Update .changeset/clever-emus-roll.md Co-authored-by: Sarah Rainsberger * nit: we wait for times, not values! --------- Co-authored-by: Sarah Rainsberger --- .changeset/clever-emus-roll.md | 11 +++++++ .../astro/e2e/client-idle-timeout.test.js | 33 +++++++++++++++++++ .../client-idle-timeout/astro.config.mjs | 9 +++++ .../fixtures/client-idle-timeout/package.json | 13 ++++++++ .../src/components/Counter.jsx | 18 ++++++++++ .../client-idle-timeout/src/pages/index.astro | 16 +++++++++ packages/astro/src/@types/astro.ts | 2 +- packages/astro/src/runtime/client/idle.ts | 14 ++++++-- pnpm-lock.yaml | 16 +++++++++ 9 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 .changeset/clever-emus-roll.md create mode 100644 packages/astro/e2e/client-idle-timeout.test.js create mode 100644 packages/astro/e2e/fixtures/client-idle-timeout/astro.config.mjs create mode 100644 packages/astro/e2e/fixtures/client-idle-timeout/package.json create mode 100644 packages/astro/e2e/fixtures/client-idle-timeout/src/components/Counter.jsx create mode 100644 packages/astro/e2e/fixtures/client-idle-timeout/src/pages/index.astro diff --git a/.changeset/clever-emus-roll.md b/.changeset/clever-emus-roll.md new file mode 100644 index 000000000000..5b9b2ee69829 --- /dev/null +++ b/.changeset/clever-emus-roll.md @@ -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 + +``` diff --git a/packages/astro/e2e/client-idle-timeout.test.js b/packages/astro/e2e/client-idle-timeout.test.js new file mode 100644 index 000000000000..034cfc8dcab3 --- /dev/null +++ b/packages/astro/e2e/client-idle-timeout.test.js @@ -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'); + }); +}); diff --git a/packages/astro/e2e/fixtures/client-idle-timeout/astro.config.mjs b/packages/astro/e2e/fixtures/client-idle-timeout/astro.config.mjs new file mode 100644 index 000000000000..02dccb9780b4 --- /dev/null +++ b/packages/astro/e2e/fixtures/client-idle-timeout/astro.config.mjs @@ -0,0 +1,9 @@ +import react from '@astrojs/react'; +import { defineConfig } from 'astro/config'; + +// https://astro.build/config +export default defineConfig({ + integrations: [ + react(), + ], +}); diff --git a/packages/astro/e2e/fixtures/client-idle-timeout/package.json b/packages/astro/e2e/fixtures/client-idle-timeout/package.json new file mode 100644 index 000000000000..af4c416058ad --- /dev/null +++ b/packages/astro/e2e/fixtures/client-idle-timeout/package.json @@ -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" + } +} diff --git a/packages/astro/e2e/fixtures/client-idle-timeout/src/components/Counter.jsx b/packages/astro/e2e/fixtures/client-idle-timeout/src/components/Counter.jsx new file mode 100644 index 000000000000..9d2212b0cae8 --- /dev/null +++ b/packages/astro/e2e/fixtures/client-idle-timeout/src/components/Counter.jsx @@ -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 ( + <> +
+ +
{count}
+ +
+
{children}
+ + ); +} diff --git a/packages/astro/e2e/fixtures/client-idle-timeout/src/pages/index.astro b/packages/astro/e2e/fixtures/client-idle-timeout/src/pages/index.astro new file mode 100644 index 000000000000..0045ca55cbce --- /dev/null +++ b/packages/astro/e2e/fixtures/client-idle-timeout/src/pages/index.astro @@ -0,0 +1,16 @@ +--- +import Counter from '../components/Counter.jsx'; +--- + + + + + + + + +
+ +
+ + diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 5e060ec99034..19bbe480623d 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -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; diff --git a/packages/astro/src/runtime/client/idle.ts b/packages/astro/src/runtime/client/idle.ts index 990d5da6ef10..1d630c4afe39 100644 --- a/packages/astro/src/runtime/client/idle.ts +++ b/packages/astro/src/runtime/client/idle.ts @@ -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); } }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b21136618ff..093d3615e7ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1004,6 +1004,22 @@ importers: specifier: ^3.4.38 version: 3.4.38(typescript@5.5.4) + packages/astro/e2e/fixtures/client-idle-timeout: + dependencies: + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@astrojs/react': + specifier: workspace:* + version: link:../../../../integrations/react + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/e2e/fixtures/client-only: dependencies: preact: