Fix Preact 11 support - #522
Merged
Merged
Conversation
JoviDeCroock
marked this pull request as ready for review
August 21, 2026 04:41
upupming
approved these changes
Aug 21, 2026
upupming
left a comment
Contributor
There was a problem hiding this comment.
Looks promising, Thanks!
marvinhagemeister
approved these changes
Aug 21, 2026
upupming
added a commit
to lynx-family/lynx-stack
that referenced
this pull request
Aug 21, 2026
Points the @lynx-js/preact-devtools override at the lynx-family/ preact-devtools#20 preview (upstream preactjs/preact-devtools#522 merged: released Preact 11 routes through the shared VNode adapter). Verified on the Android emulator via lynx-devtool CLI: component tree, find, and hooks inspection all work against a Preact 11 bundle.
upupming
added a commit
to lynx-family/preact-devtools
that referenced
this pull request
Aug 21, 2026
* fix: detect context providers without private fields * fix: support current Preact 11 internals * fix: isolate Preact 11 portal bindings * ci: forward retries to Playwright * fix: exclude experimental Preact 11 renderer * test: cover Preact 11 Suspense Co-authored-by: Jovi De Croock <decroockjovi@gmail.com>
upupming
added a commit
to lynx-family/preact-devtools
that referenced
this pull request
Aug 27, 2026
Brings released-Preact-11 support: the v11 line routes through the shared VNode adapter and the obsolete experimental adapter is gone. The fork's ReactLynx id-mapping wiring moves onto bindings.getInstance, the browser e2e suites keep their fork skips, and peerDependencies stays off (the fork consumes internal-preact). Replaces the squash merge that landed the same tree without recording upstream as a parent, which left the fork permanently 1 commit behind.
upupming
added a commit
to lynx-family/lynx-stack
that referenced
this pull request
Sep 1, 2026
Migrates ReactLynx to Preact 11, consuming the published fork build: lynx-family/internal-preact#22. Ships as a **minor** bump of `@lynx-js/react` with one breaking change: `useEffect` cleanups of unmounted components move from synchronous unmount to the after-paint flush, matching React. Page destroy still drains them synchronously, so cleanups that release native resources keep running before the runtime goes away. ## What changed **Dependency** (`pnpm-workspace.yaml` catalog): `preact` resolves to `@lynx-js/internal-preact@11.0.0-rc.1-20260827072551-a63bfa4` (upstream 11.0.0-rc.1 plus all Lynx fork patches). `@lynx-js/preact-devtools` moves to `5.0.1-20260827072047-5f77e12`, which carries upstream preactjs/preact-devtools#522 (released-v11 adapter) alongside the fork's Lynx wiring. No overrides and no pkg.pr.new URLs remain, so the single-instance guard is back to its original form. **Runtime adaptation** (`@lynx-js/react`): - `Component._dirty`/`_force` booleans became the `_bits` bitfield (`__g`): `render-constants.ts` now exposes `BITS`/`COMPONENT_DIRTY`/`COMPONENT_FORCE`; renderToOpcodes, forceRootRender and the profiling hooks use bit ops. - Hand-rolled jsx-runtime vnodes drop the removed `__d` slot. - `SnapshotInstance`/`BackgroundSnapshotInstance`/`BackgroundElementTemplateInstance` implement `remove()` (Preact 11's `removeNode` calls `node.remove()`). - New `withSyncEffectFlush()`: Preact 11 defers passive-effect cleanups of unmounted components to the after-paint flush (React-aligned, preactjs/preact#4299). Background destroy is the one path with no later turn to run them in — `callDestroyLifetimeFun` returns and native tears the runtime down — so both destroy paths wrap their `render(null)` with a synchronous scheduler, restoring v10's behavior. Ordinary unmounts keep the deferred semantics. - v11 type adjustments: `useRef` requires an initial value; compat `Children.map` takes a context argument. **Behavior changes surfaced by tests** (all judged equivalent-or-better): - Context consumers no longer double-render on provider updates (preactjs/preact#4724): fewer `rLynxChange` flushes; patches merge into the first flush. - List reorders use v11's LIS diff — equivalent final state via a different minimal move set. - None outstanding for `<page />`: its attribute reset now runs as a layout effect (landed separately in #3622), so it stays inside the unmount patch as on v10. ## Test status (local) - root `vitest`: 247 files, 2006 passed, 0 failed (includes react/runtime 816, element-template 692, testing-library, transform, signals, webpack, rspeedy projects) - `rstest`: 202 files, 1847 passed, 0 failed - `pnpm -r run test:type`: green - `turbo api-extractor -- --local`: no API drift - internal-preact fork suite (rc.1): 1152 passed / 186 skipped, lint + test:ts green ## Known open items - `useLayoutEffect` remains aliased to `useEffect` in ReactLynx, so user effect cleanups are deferred on ordinary unmounts. Runtime code that needs a cleanup inside the unmount commit imports `useLayoutEffect` from `preact/hooks` directly (see `<page />`). Destroy is synchronous for everyone. - Web-platform e2e suites left to CI. - CodSpeed reports a real regression on the `*-destroyBackground` benchmarks (-5% to -17%, confirmed in Simulation mode, so not timing noise). This is the cost of correctness: destroy now actually runs every unmount cleanup. Before this PR's fix, Preact 11 deferred them to a flush that never happens during destroy, so those benchmarks were measuring a destroy that skipped the work entirely. Against the `main` (Preact 10) baseline — which also runs cleanups synchronously — the remaining gap is Preact 11's own unmount bookkeeping (the `errorParent` walk in `options.unmount`, plus the queue round-trip through `flushAfterPaintEffects`). Batching the flush to once per destroy instead of once per unmounted component would recover part of it, at the cost of no longer matching v10's ordering (cleanups would run after the tree is detached rather than during the unmount walk). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Upgraded the rendering engine to Preact 11. - Improved list reordering performance and consistency. - Added reliable node removal for background and snapshot rendering. - **Bug Fixes** - Prevented duplicate context-consumer renders during provider updates. - Improved effect cleanup timing during unmount and page destruction. - Ensured incomplete renders reset correctly. - Updated developer tooling and compatibility across supported integrations. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Internalnode modelWhy
The existing Preact 11 adapter targets the 2022 experimental reconciler. It expects separate internal nodes, node-type flags and
_internallifecycle hooks. Current Preact 11 uses the same VNode shape and options hooks as Preact 10, so those assumptions cause most nodes to be classified as text and prevent profiling and owner tracking from working.Sharing the working VNode adapter is both smaller and closer to Preact's current implementation than refreshing the obsolete adapter field by field.
Before this change, the Preact 11 E2E run on this branch reported 88 failures, 14 passes and 7 skips. After the change, both supported majors pass the complete suite, including restored Signals coverage and a new core portal regression test.
Validation
npm run format:checknpm run lintnpm run type-checknpm run test— 164 passed, 1 skippednpm run test:e2e:10— 109 passed, 3 skippednpm run test:e2e:11— 109 passed, 3 skipped