sapper icon indicating copy to clipboard operation
sapper copied to clipboard

Consider making preload a derived store

Open benmccann opened this issue 5 years ago • 4 comments

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>>>

benmccann avatar Jul 29 '20 21:07 benmccann

https://github.com/sveltejs/sapper/issues/917 seems to suggest there's some issue with updating stores during SSR

benmccann avatar Aug 01 '20 22:08 benmccann

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 😉

thgh avatar Aug 02 '20 13:08 thgh

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 :/...

UltraCakeBakery avatar Sep 03 '20 19:09 UltraCakeBakery

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

istarkov avatar Feb 09 '21 09:02 istarkov