Browse Source

chore(reactivity): sync system.ts to latest alien-signals (#14813)

* chore(reactivity): port system.ts up to alien-signals v3.2.0

Tracks the upstream v3.0.0 → v3.2.0 changes in
https://github.com/stackblitz/alien-signals/blob/v3.2.0/src/system.ts

- `propagate` accepts a new `innerWrite` parameter. When true and the
  subscriber wasn't already pending/dirty/recursed, the subscriber is
  marked `Recursed` in addition to `Pending`. The parameter defaults to
  `false`, preserving current behavior at all existing call sites
  (ref.ts and dep.ts).
- `checkDirty` no longer special-cases single-subscriber dependencies in
  its stack handling — it always pushes/pops the stack. The return
  value is gated on `sub.flags` so a fully-cleared subscriber doesn't
  report dirty.
- The `subs` reference in the dirty/update branch is read before the
  `update()` call (mechanical reorder upstream, no behavior change).

* chore(reactivity): wire runDepth so propagate(innerWrite) is reachable

The previous commit added the `innerWrite` parameter to `propagate` but
defaulted every caller to `false`, leaving the new code path dead.

Mirror the upstream wiring:

- `system.ts` exports `runDepth` plus `incRunDepth`/`decRunDepth` helpers
- `ReactiveEffect.run()` and `ComputedRefImpl.update()` bracket their
  user-fn invocations with `incRunDepth()` / `decRunDepth()`
- `ref.ts` (set value + triggerRef) and `dep.ts` (collection trigger)
  pass `!!runDepth` to `propagate`

So a write performed while an effect/computed is mid-run now correctly
marks the affected subscribers as `Recursed | Pending` instead of just
`Pending`.

* chore(reactivity): read runDepth directly inside propagate

The previous commit added `innerWrite` as an explicit parameter to
`propagate` (mirroring upstream's API). But upstream needs that because
its `runDepth` lives in `index.ts` while `propagate` lives in
`system.ts` — they're in different modules.

Vue's port colocates both in `system.ts`, so `propagate` can just read
the module-local `runDepth` directly. Drop the parameter and the
`!!runDepth` pass-through at every call site. `runDepth` itself becomes
module-private; callers only see `incRunDepth` / `decRunDepth`.

* chore(reactivity): sync to alien-signals d4d04497

Upstream's only post-v3.2.0 change to `src/system.ts`-adjacent code is
[d4d04497] "remove unnecessary runDepth in computed evaluation". It
drops the `runDepth` bump from the computed evaluation path on the
grounds that a computed getter should be pure — writes inside it are
an anti-pattern and don't need the recursion-marking behavior.

Mirror this: drop `incRunDepth` / `decRunDepth` from
`ComputedRefImpl.update`. The counter still wraps effect runs in
`effect.ts`, which is where the `innerWrite` recursion handling matters
in practice. Also bump the header SHA so future syncs have an exact
reference point.

[d4d04497]: https://github.com/stackblitz/alien-signals/commit/d4d04497e9cf28de9558cad5fc1fcd835049d459

* chore(reactivity): point system.ts header at the source commit

`d4d04497` is alien-signals' latest commit but it doesn't touch
`src/system.ts`. Anchor the porting reference on `7e53655` — the most
recent commit that actually changed the upstream file — so the link
keeps useful "diff since this point" semantics.

* chore(reactivity): make port header link to upstream diff

Replace the static blob link with a `compare` URL anchored at the
currently-ported commit. The link now resolves to "everything in
alien-signals' main that we haven't ported yet" and self-updates as
upstream advances, so the next sync just clicks through.
Johnson Chu 1 month ago
parent
commit
f512da51e4
2 changed files with 27 additions and 16 deletions
  1. 4 0
      packages/reactivity/src/effect.ts
  2. 23 16
      packages/reactivity/src/system.ts

+ 4 - 0
packages/reactivity/src/effect.ts

@@ -8,7 +8,9 @@ import {
   type ReactiveNode,
   type ReactiveNode,
   activeSub,
   activeSub,
   checkDirty,
   checkDirty,
+  decRunDepth,
   endTracking,
   endTracking,
+  incRunDepth,
   link,
   link,
   setActiveSub,
   setActiveSub,
   startTracking,
   startTracking,
@@ -117,9 +119,11 @@ export class ReactiveEffect<T = any>
     }
     }
     cleanup(this)
     cleanup(this)
     const prevSub = startTracking(this)
     const prevSub = startTracking(this)
+    incRunDepth()
     try {
     try {
       return this.fn()
       return this.fn()
     } finally {
     } finally {
+      decRunDepth()
       endTracking(this, prevSub)
       endTracking(this, prevSub)
       const flags = this.flags
       const flags = this.flags
       if (
       if (

+ 23 - 16
packages/reactivity/src/system.ts

@@ -1,4 +1,5 @@
-// Ported from https://github.com/stackblitz/alien-signals/blob/v3.0.0/src/system.ts
+// Ported from alien-signals. Diff against upstream main:
+// https://github.com/stackblitz/alien-signals/compare/7e53655f40c3dd298168c278b3bf248a72f742d9...main
 import type { ComputedRefImpl as Computed } from './computed.js'
 import type { ComputedRefImpl as Computed } from './computed.js'
 import type { ReactiveEffect as Effect } from './effect.js'
 import type { ReactiveEffect as Effect } from './effect.js'
 import type { EffectScope } from './effectScope.js'
 import type { EffectScope } from './effectScope.js'
@@ -42,6 +43,16 @@ const notifyBuffer: (Effect | undefined)[] = []
 export let batchDepth = 0
 export let batchDepth = 0
 export let activeSub: ReactiveNode | undefined = undefined
 export let activeSub: ReactiveNode | undefined = undefined
 
 
+let runDepth = 0
+
+export function incRunDepth(): void {
+  ++runDepth
+}
+
+export function decRunDepth(): void {
+  --runDepth
+}
+
 let globalVersion = 0
 let globalVersion = 0
 let notifyIndex = 0
 let notifyIndex = 0
 let notifyBufferLength = 0
 let notifyBufferLength = 0
@@ -167,6 +178,9 @@ export function propagate(link: Link): void {
         )
         )
       ) {
       ) {
         sub.flags = flags | ReactiveFlags.Pending
         sub.flags = flags | ReactiveFlags.Pending
+        if (runDepth) {
+          sub.flags |= ReactiveFlags.Recursed
+        }
       } else if (
       } else if (
         !(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))
         !(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))
       ) {
       ) {
@@ -274,8 +288,8 @@ export function checkDirty(link: Link, sub: ReactiveNode): boolean {
       (depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) ===
       (depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) ===
       (ReactiveFlags.Mutable | ReactiveFlags.Dirty)
       (ReactiveFlags.Mutable | ReactiveFlags.Dirty)
     ) {
     ) {
+      const subs = dep.subs!
       if ((dep as Computed).update()) {
       if ((dep as Computed).update()) {
-        const subs = dep.subs!
         if (subs.nextSub !== undefined) {
         if (subs.nextSub !== undefined) {
           shallowPropagate(subs)
           shallowPropagate(subs)
         }
         }
@@ -285,9 +299,7 @@ export function checkDirty(link: Link, sub: ReactiveNode): boolean {
       (depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Pending)) ===
       (depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Pending)) ===
       (ReactiveFlags.Mutable | ReactiveFlags.Pending)
       (ReactiveFlags.Mutable | ReactiveFlags.Pending)
     ) {
     ) {
-      if (link.nextSub !== undefined || link.prevSub !== undefined) {
-        stack = { value: link, prev: stack }
-      }
+      stack = { value: link, prev: stack }
       link = dep.deps!
       link = dep.deps!
       sub = dep
       sub = dep
       ++checkDepth
       ++checkDepth
@@ -301,18 +313,13 @@ export function checkDirty(link: Link, sub: ReactiveNode): boolean {
 
 
     while (checkDepth) {
     while (checkDepth) {
       --checkDepth
       --checkDepth
-      const firstSub = sub.subs!
-      const hasMultipleSubs = firstSub.nextSub !== undefined
-      if (hasMultipleSubs) {
-        link = stack!.value
-        stack = stack!.prev
-      } else {
-        link = firstSub
-      }
+      link = stack!.value
+      stack = stack!.prev
       if (dirty) {
       if (dirty) {
+        const subs = sub.subs!
         if ((sub as Computed).update()) {
         if ((sub as Computed).update()) {
-          if (hasMultipleSubs) {
-            shallowPropagate(firstSub)
+          if (subs.nextSub !== undefined) {
+            shallowPropagate(subs)
           }
           }
           sub = link.sub
           sub = link.sub
           continue
           continue
@@ -328,7 +335,7 @@ export function checkDirty(link: Link, sub: ReactiveNode): boolean {
       dirty = false
       dirty = false
     }
     }
 
 
-    return dirty
+    return dirty && !!sub.flags
   } while (true)
   } while (true)
 }
 }