Skip to content

Build the proposal API on top of alien-signals#44

Open
johnsoncodehk wants to merge 54 commits into
proposal-signals:mainfrom
johnsoncodehk:alien-signals
Open

Build the proposal API on top of alien-signals#44
johnsoncodehk wants to merge 54 commits into
proposal-signals:mainfrom
johnsoncodehk:alien-signals

Conversation

@johnsoncodehk

@johnsoncodehk johnsoncodehk commented Jan 8, 2025

Copy link
Copy Markdown

We are re-constructing surface APIs based on alien-signals to obtain performance improvements, now faster than most frameworks.

We intentionally relies on the alien-signals package rather than duplicating code in order to easily discover code specific to the signal proposal.

Regarding the differences in test results.

  • Prohibited contexts - allows writes during computed: The alien-signals algorithm is able to handle computed side effects, so the expected results in the test are now modified to the correct values.

  • type checks - checks types in methods: I'm not sure what I should do to make the current implementation pass these tests, but since this PR is for research purposes only, I don't think this test is worth solving, so I just skipped it. (resolved)

Chart:

image

@trueadm

trueadm commented Jan 8, 2025

Copy link
Copy Markdown

@NullVoxPopuli Did you add prohibited-contexts.test? Does this test need to be updated or is there a genuine issue here do you think?

@NullVoxPopuli

Copy link
Copy Markdown
Contributor

I did not add it -- it was implemented with the original spec -- but I did pull it out of a massive test file, as we need to be describing why tests exist, and what they're testing, and why that behavior is important.

It is def a behavior question about whether or not folks want to allow synchronous mutation while reading another value.

Personally, I don't think this is a good idea, as it prior-reads of the mutated state are now out of date, and if the consumer is entangled with the mutated state, that usually leads to infinite looping when a renderer is involved

@johnsoncodehk

Copy link
Copy Markdown
Author

We have already solved the problem of infinite loops at the algorithmic level. If synchronous mutations in computed is not handled, it can not pass the Vue core test suite.

@NullVoxPopuli

Copy link
Copy Markdown
Contributor

Sounds fine since it's solved.
Not sure if proposal text will need to update

@hybrist

hybrist commented Jan 9, 2025

Copy link
Copy Markdown

Can you link to the source of the benchmark? I'd be curious to see what the test setup was like. Was it based on https://github.com/transitive-bullshit/js-reactivity-benchmark?

johnsoncodehk added a commit to johnsoncodehk/js-reactivity-benchmark that referenced this pull request Jan 12, 2025
@johnsoncodehk

johnsoncodehk commented Jan 12, 2025

Copy link
Copy Markdown
Author

@jkrems yes, I just add a test for this PR to a new branch.

https://github.com/johnsoncodehk/js-reactivity-benchmark/tree/alien-polyfill

@hybrist

hybrist commented Jan 13, 2025

Copy link
Copy Markdown

@johnsoncodehk I was asking because that benchmark has some known measuring artifacts for the signal polyfill specifically (see https://x.com/synalx/status/1868235387812053167). So it might be less predictive for this particular PR. That doesn't mean that this PR isn't a performance improvement. But it might just require additional validation before it's clear how it compares.

@johnsoncodehk
johnsoncodehk marked this pull request as ready for review August 12, 2025 11:21
@johnsoncodehk

Copy link
Copy Markdown
Author

I think the PR is complete!

The implementation has been simplified a lot while maintaining as much consistency as possible with the original behavior. To achieve this, surface APIs have added checks for isState, isComputed, and isWatcher, which will affect performance. The initial performance comparison may no longer be applicable, but the improvements should still be significant.

@johnsoncodehk johnsoncodehk changed the title Building a proposal API on top of alien-signals Building the proposal API on top of alien-signals Aug 12, 2025
@johnsoncodehk johnsoncodehk changed the title Building the proposal API on top of alien-signals Build the proposal API on top of alien-signals Aug 12, 2025
@pzuraq

pzuraq commented Nov 11, 2025

Copy link
Copy Markdown

Does this PR support the connected/disconnected callbacks in the spec? Or in other words, is it possible to support something like the behavior of Signalium Relays with alien-signals/extensions built on top of it?

@NullVoxPopuli

Copy link
Copy Markdown
Contributor

I think my biggest concern with this pr is the external dep.

It should be internalized so that experimentation is easier (nothing against alien signals ofc, just don't want to push breaking changes on y'all)

@johnsoncodehk

Copy link
Copy Markdown
Author

@NullVoxPopuli Sorry for the delay, it's no external dep now. 🙌

@darcyparker

Copy link
Copy Markdown

This PR/experiment to build on top of alien-signals is very interesting and it was exciting to see the PR declared complete in Aug 2025... But it seems to have stalled and there are CI errors. Are there any updates?

darcyparker added a commit to darcyparker/signal-polyfill that referenced this pull request Jul 1, 2026
Reframe the cooling tests to DOCUMENT the asynchronous behavior rather than
assert it satisfies the proposal: a push engine cannot detach synchronously
without breaking memoization, so GC-ability here is async (edge gone after a
microtask), weaker than the poll polyfill's synchronous no-edge. Whether that is
acceptable for the polyfill is left as a question for the maintainers, citing
alxhub's design-goal comment on proposal-signals#44.

Add tests/benchmarks/cooling.mjs isolating cooling's cost vs the no-cooling base:
watched propagation is unchanged (off the hot path), standalone churn is faster
(bounded working set), and the post-churn write-cost cliff is eliminated.
@darcyparker

Copy link
Copy Markdown

@alxhub raised the GC question for standalone/unwatched computeds (comment) — the proposal tracks a dep→subscriber edge only for watched signals, so unwatched computeds must be GC-able.

I took a run at restoring this in the polyfill layer, since the core keeps it out by design (stackblitz/alien-signals#79) and the cooldown was moved "outside the system" (d681ca2 / comment) but isn't in the current head: johnsoncodehk#1.

It's a "nursery + microtask" cooling adapted from d681ca2: a standalone-read computed is parked and its dep edges detach on the next microtask, so it (and any unwatched sub-chain) becomes collectable. It's off the hot propagate path — watched propagation is unchanged, standalone churn is actually faster, and the post-churn write-cost cliff disappears (there's a small benchmark, tests/benchmarks/cooling.mjs, in the PR). Conformance + cooling tests are green.

The crux: this restoration is asynchronous. A push core can't detach synchronously without breaking memoization (a same-tick re-read recomputes — pruning.test.ts), so the edge exists synchronously and is gone after a microtask — weaker than the poll polyfill's structural "no edge ever." So alien-based #44 can conform to everything except the synchronous form of the guarantee, unless async GC-ability is accepted.

Worth noting this is a polyfill-on-push limitation, not a proposal-semantics one: a native implementation could keep push speed and synchronous structural GC via cheap VM-level weak references / GC integration (JS WeakRef.deref is too costly on the hot path — a naive weak-edge version regresses propagation ~2.3–3×). So async GC would be a polyfill-era tradeoff a native implementation resolves.

Question for the signal-polyfill maintainers: is async GC-ability acceptable for the polyfill, or is the synchronous/structural property required? If the latter, alien-based #44 can't provide it (only poll can); if the former, johnsoncodehk#1 closes the gap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.