woutdp / live_svelte

Svelte inside Phoenix LiveView with seamless end-to-end reactivity
https://hexdocs.pm/live_svelte
MIT License
1.01k stars 38 forks source link

Singletons #71

Closed dev-guy closed 5 months ago

dev-guy commented 10 months ago

To avoid wasting memory on features that might not be used, some Svelte frameworks, such as Svelte Skeleton v2, use singleton stores that are intended to be created via top-level SvelteKit files such as +layout.svelte.

<script>
import { initializeStores } from '@skeletonlabs/skeleton';
initializeStores();
</script>

initializeToastStore() does this:

return setContext(TOAST_STORE_KEY, toastStore);

In other words, the Toast store is a singleton that is accessed via a top-level Svelte context that is available to every Svelte component in the application.

A LiveSvelte application needs to initialize the Skeleton toast store somehow. The top-level objects of a Phoenix application are generally app.js and app.html.heex. Of course, using app.js doesn't work because Svelte throws an error when getContext/setContext/hasContext are called outside of component initialization. Apparently, only a .svelte file can initiate a workflow that calls *Context() since contexts are associated with Svelte components.

Heex templates can include Svelte components via LiveSvelte. Can app.html.heex do it? To try, I created a Svelte component that initializes the Toast store. I added the following to the top of app.html.heex: <.svelte name='Toast'>.

Toast.svelte:

<script>
import { initializeStores, Toast } from '@skeletonlabs/skeleton';
initializeStores();
</script>
<Toast />

My application starts fine -- in fact, it works with Svelte Skeleton 1.x so long as you remove the bit about initializeStores. However, this doesn't work with Skeleton 2.x which now uses Svelte contexts. Svelte components must share a common ancestor in order to access the same context instance. Since the above component doesn't have any children, other Svelte components in my application can't access the context singleton created by it. Therefore, they complain that initializeStores has not been called.

Top-level contexts could be established via a parent LiveSvelte component with slots and/or subcomponents. However, such an approach is at odds with creating a Phoenix application that consists of dead views, live views, and LiveSvelte components.

Working with frameworks that require SvelteKit (because of top-level Svelte contexts) would be a huge win because the most interesting parts of the Svelte ecosystem are headed that way.

woutdp commented 10 months ago

If it's just setting the context object it's possible probably. The context right here, https://github.com/woutdp/live_svelte/blob/master/assets/js/live_svelte/hooks.js#L106-L110

Currently there's no way to do it but we can expose this, just like how it's being done with props.

dev-guy commented 10 months ago

At the moment, this is a minor efficiency and start-up speed issue. It could become more of a problem if my application needed a global store for application state which is how a lot of SvelteKit applications behave. This type of application can not be built with Phoenix alone, so IMO this would make LiveSvelte even more compelling for those of us who are working on offline-first apps, don't want to go whole-hog on SPA, and understand the value of Phoenix including OTP and pubsub.

woutdp commented 10 months ago

Ok, leaving this issue in the backlog for now. But feel free to make a PR that integrates this functionality.

woutdp commented 5 months ago

Closing for now as there's not progress on this