fix: keep onCleanup order consistent between dev and prod#2710
fix: keep onCleanup order consistent between dev and prod#2710ATOM00blue wants to merge 2 commits into
Conversation
In dev builds, components are wrapped in an extra owner scope by `devComponent` so devtools can observe component boundaries. This wrapper does not exist in production, which caused `onCleanup` callbacks registered directly in a component body to fire in a different order on disposal than in production. Register such cleanups on the same owner they would be in production by skipping past any dev component wrapper(s). The component owner returned by `getOwner()` is left unchanged so devtools still works. Closes solidjs#1561
🦋 Changeset detectedLatest commit: a4dba97 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Ensures onCleanup callback disposal order remains consistent between dev and production by skipping dev-only component wrapper owners when registering cleanups (fixing Solid issue #1561), and adds regression tests to lock in the expected behavior.
Changes:
- Update
onCleanupto register callbacks on the same owner chain as production by skipping dev component wrapper owner(s). - Add dev-only tests for cleanup ordering, nested components, re-creation behavior, and
getOwnerexpectations.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/solid/test/dev.spec.ts | Adds regression tests that compare dev vs simulated-production cleanup order and related owner behavior. |
| packages/solid/src/reactive/signal.ts | Adjusts onCleanup owner selection in dev to avoid dev wrapper affecting cleanup ordering. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // which means cleanups registered directly in a component body would otherwise | ||
| // be ordered differently on disposal. Skip past any dev component wrapper(s) so | ||
| // the cleanup is registered on the same owner it would be in production. See #1561. | ||
| if (IS_DEV) while (o && (o as DevComponent<any>).component) o = o.owner; |
| // Simulate the production code path (no dev wrapper) by calling the | ||
| // component directly under `untrack`, like `createComponent` does in prod. | ||
| const prodOrder: string[] = []; | ||
| createRoot(dispose => { | ||
| onCleanup(() => prodOrder.push("before")); | ||
| untrack(() => | ||
| (function Child() { | ||
| onCleanup(() => prodOrder.push("child")); | ||
| return null; | ||
| })() | ||
| ); |
Summary
In dev builds, components are wrapped in an extra owner scope by
devComponentso devtools can observe component boundaries. This wrapper does not exist in production, which causedonCleanupcallbacks registered directly in a component body to fire in a different order on disposal than in production.This registers such cleanups on the same owner they would be in production by skipping past any dev component wrapper(s). The owner returned by
getOwner()inside a component is intentionally left unchanged, so devtools continues to see the component owner.Test plan
dev.spec.tsassert devonCleanuporder matches prod for sibling, nested, and re-created component casesCloses #1561