Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration Stories #116

Open
EisenbergEffect opened this issue Apr 1, 2024 · 8 comments
Open

Integration Stories #116

EisenbergEffect opened this issue Apr 1, 2024 · 8 comments

Comments

@EisenbergEffect
Copy link
Collaborator

EisenbergEffect commented Apr 1, 2024

A list of library/framework integration experiences to help validate the proposal.

@EisenbergEffect
Copy link
Collaborator Author

Lit was able to integrate the proposal with very little effort and code.

Tweet here:

https://twitter.com/justinfagnani/status/1774880922560815222

Demo here:

https://lit.dev/playground/#gist=a1b12ce26246d3562dda13718b59926c

@EisenbergEffect EisenbergEffect pinned this issue Apr 1, 2024
@justinfagnani
Copy link
Collaborator

So I did hit a problem with trying to port the watch() directive from our @lit-labs/preact-signals package.

The watch() directive takes a signal, binds it to a specific DOM location and synchronously updates the DOM when the signal changes. The watch() directive also reads the signal outside of tracking (using Preact's signal.peek() and signal.subscribe()) in order to not trigger an outer computed signal that's watching the entire render method of a component. This let's watch() be used for targeted DOM updates while a computation wrapping the render can batch and react to any signal access, but not re-render a signal that's only passed to watch().

See: https://github.com/lit/lit/blob/23121c203965d4c74f78bebfc1d6bcc79e5b6738/packages/labs/preact-signals/src/lib/watch.ts#L23

This was my attempt at a port:

  override render(signal: Signal.State<any> | Signal.Computed<any>) {
    if (signal !== this.__signal) {
      this.__dispose?.();
      this.__signal = signal;

      // Whether the Watcher callback is called because of this render
      // pass, or because of a separate signal update.
      let updateFromLit = true;
      const watcher = new Signal.subtle.Watcher(() => {
        if (updateFromLit === false) {
          this.setValue(Signal.subtle.untrack(() => signal.get()));
        }
      });
      watcher.watch(signal);
      this.__dispose = () => watcher.unwatch(signal);
      updateFromLit = false;
    }
    
    // We use untrack() so that the signal access is not tracked by the watcher
    // created by SignalWatcher. This means that an can use both SignalWatcher
    // and watch() and a signal update won't trigger a full element update if
    // it's only passed to watch() and not otherwise accessed by the element.
    return Signal.subtle.untrack(() => signal.get());
  }

@littledan
Copy link
Member

littledan commented Apr 6, 2024

High level experience report from @yyx990803 : https://twitter.com/youyuxi/status/1776560691572535341

I was involved quite early in the proposal’s discussions. Overall I think it’s nice to have a built-in primitive so that frameworks can ship less code and have better performance and potential interop (think cross-framework VueUse). We are currently prototyping to make sure Vue reactivity can be re-implemented on top of it.

@dakom
Copy link

dakom commented Apr 15, 2024

I made a little drum machine demo here: https://github.com/dakom/drum-machine-js-signals

(hat tip @EisenbergEffect who pointed out that this is a good place to share it)

@dakom
Copy link

dakom commented Apr 18, 2024

Another demo here: https://github.com/dakom/local-chat-js-signals

This one tests out the idea of doing efficient non-lossy list updates, by having 4 different "chat" windows with edit, delete, etc.

The "chat" is just local, nothing is sent out over the internet

Similar to the drum machine above - no framework, just vanilla

@muhammad-salem
Copy link

Integration with @ibyar/aurora

example: https://github.com/muhammad-salem/aurora/blob/feature/signal-proposal/example/src/signals/proposal.ts

import { Component } from '@ibyar/aurora';
import { Signal } from 'signal-polyfill';


@Component({
	selector: 'signal-proposal',
	template: `<div>
		<button class="btn btn-link" (click)="resetCounter()">Reset</button>
		<p>{{counter?.get()}}</p>
		<button class="btn btn-link" (click)="addCounter(+ 1)">+</button>
		<button class="btn btn-link" (click)="addCounter(- 1)">-</button>
		<input type="number" [value]="+counter?.get() ?? 100" (input)="counter?.set(+$event.target.value)"/>
	</div>`
})
export class SignalProposal {
	counter?: Signal.State<number> | null = new Signal.State(100);

	resetCounter() {
		this.counter = new Signal.State(100);
	}
	addCounter(num: number) {
		this.counter?.set(this.counter?.get() + num);
	}
}

requird to init scope: https://github.com/muhammad-salem/aurora/blob/feature/signal-proposal/src/core/signals/proposal-signals.ts

Integration with view
https://github.com/muhammad-salem/aurora/blob/feature/signal-proposal/src/core/view/base-view.ts#L72

@mary-ext
Copy link

mary-ext commented May 13, 2024

Here's my attempt at making Solid.js' signals on top of the spec (just the signals and effects, no suspense and transitions)

https://gist.github.com/mary-ext/2bfb49da129f40d0ba92a1a85abd9e26

I've only tested running it standalone at the moment, but it'll probably work just as well once I've gotten dom-exressions to work with it (the actual DOM manipulation "runtime" that Solid.js uses)

I've some gripes with making batched synchronous reactions happen with the watchers, right now they're preventing me from just having one watcher for all effects.

  1. It's not telling me who specifically got marked dirty at the time of callback
  2. I'd have to unmark the watcher every time the notify callback is fired

But other than that, everything else has been great. I can somewhat understand why synchronous reactions aren't allowed especially when it's being done naively, but I'm also somewhat unsure of it at the moment.

@yinxulai
Copy link

I have developed a tsx application framework called airx that fully embraces Signal. It is designed for learning purposes. Here is a code snippet:

const count = new Signal.State(0);

function Counter() {
  const handleClick = () => {
    count.set(count.get() + 1);
  };

  return () => (
    <button onClick={handleClick}>
      count is {count.get()}
    </button>
  );
}

Quick demo: https://codesandbox.io/p/devbox/github/airxjs/vite-template/tree/signal/?file=%2Fsrc%2FApp.tsx%3A7%2C1

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

No branches or pull requests

7 participants