[2.0 rc.14 / next] Feature request: public onHydrationEnd, so E2E tests can tell when hydration has finished
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 62/100
- Tipo di issue
- Funzionalità
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- javascript, playwright, typescript
Direzione di ricerca
Start with the onHydrationEnd exports and server stub in packages/solid/src/index.ts and packages/solid/src/server/index.ts, then read packages/solid/test/internal-surface.spec.ts and published-declarations.spec.ts. Check how the declarations currently exclude this helper and update the public API coverage and documentation described in the issue. Done means the callback is documented as a public export, including when it fires and its single-root limitation, and the declaration checks reflect that.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Our Playwright tests need a documented end-of-hydration signal before they check state the client writes: history state after a navigation, attributes on <html>, no CSP violations once the client code has run. A few tests do the reverse: they hold the client script request and assert that the page has not hydrated yet. Solid 2 has this signal in onHydrationEnd, but it is @internal, so what a test can read from outside the bundle today is a side effect with no contract.
The changelog entry that took onHydrationEnd out of the public API (CHANGELOG.md#L2034) says snapshots make hydration timing transparent. That holds for code running inside the app. A page.evaluate call runs outside it and only sees the DOM and globals, so snapshots don't help there.
Today
// Playwright, against 2.0.0-rc.14
const hydrated = () =>
page.evaluate(() => (globalThis as any)._$HY?.events === null);
// script request held: the page must not be hydrated
expect(await hydrated()).toBe(false);
// script released
await expect.poll(hydrated).toBe(true);
await expect(page.locator("html")).toHaveAttribute("lang", "pt-BR");
We use this check in six E2E test files. It reads internals that each mean "done" at a different moment:
drainHydrationCallbacksruns theonHydrationEndcallbacks synchronously and sets_$HY.done = trueonly in a latersetTimeout(hydration.ts#L387-L401)._$HY.eventsgoes tonullin the microtask thatrunHydrationEvents()queues, only ifsharedConfig.doneis already true when it runs and every queued event could be replayed (client.ts#L2636-L2672). That is the only place it gets nulled.runHydrationEvents()is emitted after a hydratable template root that has a delegated handler or a spread (babel-plugin element.ts#L337-L344, compiler events.rs#L21-L28), plusdynamic().
So on a page where nothing in the hydrated markup has a delegated handler, a spread or a dynamic() element, the check never turns true. A minimal SSR page shows it on rc.14 in headless Chromium: with <main><p>static {n()}</p></main> as the whole app, _$HY.done is true and _$HY.events is still [] five seconds after load. Swap the paragraph for a <button onClick={...}> and _$HY.events goes to null.
_$HY.done would be a better proxy, but server-components-principles.md lists it among the things that dissolve (L656), so we'd rather not move to it. sharedConfig.onHydrationEnd is reachable through solid-js/internal, but that entry says it is not public API and may change or vanish in any release (internal.ts#L1-L4), and the sharedConfig docs call it cross-package wiring (hydration.ts#L187-L198). #3718 moved Solid Router and TanStack off that object for the same reason, and a test hook in an app looks like the same case to us.
Proposal
Drop @internal from onHydrationEnd, maybe as @experimental like setLinkClaim in #3921. #3718 made isHydrating public again after the changelog entry quoted above had removed it. Ruling 3 already names onHydrationEnd and isHydrationInProgress() as the single meaning of done (frames-rulings.md#L127-L135), so exposing it keeps a single channel.
On next it is already a runtime export of solid-js on both entries (index.ts#L138-L155, and a stub on the server at server/index.ts#L203-L206), so the bare export adds no runtime bytes. It sits in an @internal block with other helpers, so a PR would move it out of that block, drop the tag at the declaration and on the server stub, and update internal-surface.spec.ts (which lists onHydrationEnd among the seams that must stay out of the declarations, #L60-L76) and published-declarations.spec.ts, plus a changeset and a line in the docs.
The app would set its own marker and the tests wait for it:
import { onHydrationEnd } from "solid-js";
import { hydrate } from "@solidjs/web";
hydrate(() => <App />, document);
// register after hydrate(), see below
onHydrationEnd(() => {
document.documentElement.dataset.hydrated = "";
});
await expect(page.locator("html[data-hydrated]")).toBeAttached();
Before the entry runs the attribute isn't there, which covers the "client hasn't run yet" assertions too. Registered before hydration starts, the callback is queued for the next microtask and doesn't wait for hydration work started afterward (hydration.ts#L379-L381). In the same repro, with a streamed <Loading> boundary, a callback registered before hydrate() runs before the boundary content arrives, and one registered after hydrate() waits for it. A public version should document this.
The marker would mean the hydration phase ended. Whether it succeeded is a separate question: when a document root's module preload fails, hydrate() sets hydrating to false and keeps the server markup (client.ts#L2455-L2478), and that drains the callbacks. In the same repro, pointing _$HY.r._assets at a missing chunk sets the marker and _$HY.done, and a click on the button does nothing. Our tests check errors and the expected behavior anyway, but the docs would need to say this, or a later version could report how hydration ended, at some cost in bytes. Either would work for us.
We saw that isHydrationInProgress is kept off the exports so app code isn't invited to branch on hydration state (hydration.ts#L327-L333). We'd only use the callback to set a test marker, and our components would never read hydration state. If you'd rather keep it away from app code, a documented marker meant for test tooling would work for us as well. What we need is a documented way to observe this phase without reading _$HY.events.
Our pages have a single hydration root. With several roots GH5 still applies: done fires before a root deferred behind a module preload has claimed anything (preload-hold.spec.tsx#L89, a test.fails). Until that's fixed, the docs shouldn't call this page-wide completion.
Checked against 2.0.0-rc.14 from npm and next at 84dada7. Related: #3718, #3813, #3921.
- Lingua principale
- TypeScript
- Stelle
- 36.1k
- Fork
- 1.1k
- Merge medio
- 10h 42m
- PR unite (30g)
- 361
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di solidjs/solid
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 20/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 15/100
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 4/5 3-5 giorni Idoneità per principianti 15/100
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 4/5 3-5 giorni Idoneità per principianti 25/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 8/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di solidjs/solid
Issue simili
-
ai-driven-qa bug claude Minor
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
linagora/twake-contacts-frontend#241 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
I maintainer di solito rispondono entro 1 giorno
-
ui(ListToolbar): search takes the full row on a phone instead of sharing it with the result countAperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 64/100
I maintainer di solito rispondono entro 1 giorno
-
priority:high
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
ahmedkhaleel2004/gitdiagram#228 ·