sapper
sapper copied to clipboard
Consider making preload a derived store
This was proposed by @adamb in #internals and discussed with @pngwn and @lukeed. I'm adding it here so that we don't lose track of it as an idea to consider when thinking through the Sapper API
Discussion here: https://discord.com/channels/457912077277855764/653341885674553345/736269925475155969
Idea would be to do something along the lines of:
const fetchInputs = derived([page, session],
([$page, $session]) => ({
productId: page.params.productId,
userId: $session.userId,
})
)
export const preload = derived(fetchInputs,
async (apiArgs) => {
const r = await fetch("/api", {
body: JSON.stringify(apiArgs)
})
return await r.json()
}
)
in this example, preload is of type Readable<Promise<Record<string, any>>>
https://github.com/sveltejs/sapper/issues/917 seems to suggest there's some issue with updating stores during SSR
Security-wise, a global store may not contain personal data during SSR (or it might leak into other users' requests). Also, $page and $session are only available during component initialization while preload currently runs before that.
Let's assume preload is moved to the component level so it can have access to page&session. When the component initializes, would it still be possible to redirect?
Just thinking out loud for a moment 😉
Would this change make it so that we can actually set stuff in the @sapper/app session store while in preload? The fact that the session parameter passed in the preload is not reactive literally forces me to now migrate my entire Sapper application to Nuxt :/...
I like the idea of making preload an async generator, i.e
async function* preload(effects) {
if (cache) {
yield cache;
}
while(true){
yield effects.fetch('/blabla');
yiels effects.sleep(1000);
}
}
result of this preload can be store as page input prop i.e
<script>
export let data: Readable<Type>;
</script>
<h1>{$data}</h1>
This would allow to use strategies like cache then fetch, socket subcriptions etc. On a server we can just use 1st yield value.
let { value } = await generator.next();
if (value instanceof Effect) {
let result = await value.runEffect();
}
On a client in case of hydration we can just skip 1st yield (having that data already exists)
Something like above already can be implemented on current sapper, having that we can return stores on preload. It needs some additional code as stores are not serializable, btw works ok.
<script lang="ts" context="module">
import { readable } from 'svelte/store';
import type { Readable } from 'svelte/store';
import type { Preload } from '@sapper/common';
// What I like
async function* preloadG() {
while (true) {
yield new Date();
await new Promise(r => setTimeout(r, 1000));
}
}
// Code to support above
let store_: null | Readable<Date> = null;
export const preload: Preload<{
data: Readable<Date>;
}> = async function (this) {
const data = new Date();
// Todo add listings list cache based on query and slug
if (typeof window === 'undefined') {
return { data: readable(data, () => () => {}) };
}
if (store_ == null) {
let brk = false;
const startLoop = async (set: (v: Date) => void) => {
for await (let dt of preloadG()) {
if (brk) break;
console.log('ss');
set(dt);
}
};
store_ = readable(data, set => {
brk = false;
startLoop(set);
return () => {
brk = true;
};
});
}
return { data: store_ };
};
</script>
<script lang="ts">
export let data: Readable<Date>;
</script>
<h1>B: {$data}</h1>
PS: making prefetch as store would make above much easier