Feature/reactive runtime resilient to duplicate module instances#36
Merged
hexplus merged 39 commits intoJun 26, 2026
Merged
Conversation
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.
Description
Makes the reactive runtime resilient to duplicate module instances.
When a bundler loads the reactive-core module more than once on a page — which
routinely happens under dependency pre-bundling (Vite
optimizeDeps/ esbuild,where the same internal chunk is served twice: once with an
?v=<hash>queryand once raw) — reactivity silently died. The two copies formed independent
reactive "worlds": a
signal()write notified one copy's queue while a reactivebinding had tracked itself through the other's, so the binding stopped updating
with no error thrown. Raw ESM usage was never affected.
This PR fixes it with a first-copy-wins, share-the-functions registry: the
first-loaded copy publishes its implementations on a
globalThisregistry(keyed by a versioned
Symbol.for), and every later copy re-exports the firstcopy's functions. Exactly one copy's code runs, so all copies funnel through a
single source of truth — and because the running code is byte-identical to the
single-instance build, there's no hot-path indirection and no perf change.
(An earlier attempt that shared the state regressed binding/effect creation by
~70%; sharing the functions avoids that entirely — confirmed by interleaved
cold + warm benchmarks.)
Also adds a one-time, dev-only warning when a duplicate runtime is detected,
explaining how to de-duplicate (
optimizeDeps.exclude/resolve.dedupe);stripped from production builds.
Changes:
src/reactivity/track-core.ts(new — implementations + state),src/reactivity/track.ts(rewritten — registry shim),src/reactivity/batch.ts(same pattern),
src/core/signals/derived.ts(usesisTrackingSuspended()),new
tests/duplicate-instance.test.ts. Version3.3.0→3.3.1, CHANGELOGentry, and docs-site version badges bumped (en/es/zh).
Related Issue
Closes #
Type of Change
Checklist