zodern / melte

Svelte compiler for Meteor with built in tracker integration and HMR
MIT License
32 stars 14 forks source link

Svelte 5 #31

Open jamauro opened 6 months ago

jamauro commented 6 months ago

Thanks for making this package!

Any preliminary thoughts on support for Svelte 5? My guess is that it won't release until early next year but the fine grained reactivity by default with proxies looks pretty good: https://github.com/sveltejs/svelte/pull/9739

I'm not entirely sure how that plays with Meteor's reactive cursors though. Was wondering if you've already given this some thought.

jamauro commented 3 months ago

@zodern I think something like this might work. What do you think?

// tracker.svelte.js inside zodern:melte
import { Tracker } from 'meteor/tracker';
import { onDestroy } from 'svelte';

export const track = (reactive) => { // reactive is a reactive data source, e.g. a cursor or a value like Meteor.user()
  const isCursor = reactive instanceof Meteor.Collection.Cursor;
  let data = isCursor ? $state([]) : $state(undefined);
  let handle;

  if (isCursor) { // allows for fine-grained updates
    handle = reactive.observe({
      added: (document) => data.push(document),
      changed: (newDocument, oldDocument) => data[data.findIndex((item) => item._id === oldDocument._id)] = newDocument,
      removed: (oldDocument) => data.splice(data.findIndex((item) => item._id === oldDocument._id), 1)
    });
  } else {
    handle = Tracker.autorun(() => {
      data = reactive
    })
  }

  onDestroy(() => handle.stop());

  return data;
};

export const subscribe = (subscriptionName, ...args) => {
  let sub = $state({ ready: false })

  const handle = Meteor.subscribe(subscriptionName, …args, onReady(() => {
    sub.ready = true
  });

  onDestroy(() => handle.stop());

  return sub;
};
// store.js
import { track } from 'meteor/zodern:melte'

export const currentUser = track(Meteor.user())
// App.svelte
 <script>
  import { track, subscribe } from 'meteor/zodern:melte'

  const sub = subscribe('todos');
  const todos = track(Todos.find());

  async function createTodo(event) {
    event.preventDefault();
    // call your Meteor method
  }
</script>

{#if !sub.ready}
  <p>loading…</p>
{/if}

{#if currentUser}
  <form onsubmit={createTodo}>
    <input type=‘text’ placeholder=‘Type to add new todos’ />
  </form>
{/if}

{#each todos as todo (todo._id)}
  <p>{todo.text}</p>
{/each}

I haven’t tried it yet so there might be issues with the above. When I attempted previously, I wasn’t able to get Svelte and Meteor 3 to play nicely together.

Maybe there’s an even better way to do it but this feels pretty nice given the direction Svelte 5 is taking.

zodern commented 3 months ago

We'll want a solution that allows the reactive code to re-run, for example if the Todos query needs to change depending on another variable.

I was considering adding a $tracker method, and having the compiler rewrite it into svelte 5 code like we currently do for $m: statements. One challenge is svelte 5 has 3 different api's for reactive code that run code at different times ($derived, $effect, $effect.pre), and I currently am not sure which ones we need a tracker equivalent for.

jamauro commented 3 months ago

We’ll want a solution that allows the reactive code to re-run

Ah yes, good point. Will do some more thinking here.

I was considering adding a $tracker method

Nice! That could be really nice if it was treated like the other runes in that it wouldn’t need to be imported.

One challenge is svelte 5 has 3 different api's for reactive code that run code at different times ($derived, $effect, $effect.pre), and I currently am not sure which ones we need a tracker equivalent for.

Yeah my understanding is that $: got split into $derived and $effect. $effect.pre is akin to beforeUpdate.