How to make offline data storage work in Sapper.js?

Viewed 162

What I want to achieve

I want to build a web app with Sapper.js that works offline but also has server side rendering functionality. For this, I need two data storage implementations: One that works in the browser and one that works on the server.

How I imagined this would work

I thought in client.js and server.js, I maybe could've imported the corresponding implementation of the store and then pass it to my page components as a prop, similar to this:

// server.js
// [some more imports here]
import DataStore from "./my/backend/datastore"

polka()
    .use(
        sapper.middleware({
            pageProps: {DataStore}
        })
    )
    .listen(PORT, err => {/* other stuff */});
// client.js
import * as sapper from '@sapper/app';
import DataStore from "./my/frontend/datastore"

sapper.start({
    target: document.querySelector('#sapper'),
    pageProps: {DataStore}
})
<!-- page.svelte -->
<script>
    export let DataStore;
    let item;

    if (!item) {
        DataStore.getItem().then(i => item = i);
    }
</script>

<p>{item}</p>

However, there doesn't seem to be a way to pass props from the two entry points into the page.
The answer to question #3 at https://stackoverflow.com/a/49085459/9236140 would've kinda allowed for the same thing, although with global variables instead of props. However, it doesn't seem to work anymore, since I haven't managed to access window and global from within my components.

How I (kinda) got it to work

// routes/server/route/exposing/get_item.js
import DataStore from "./my/backend/datastore"

export async function get(req, res, next) {
    res.end(await DataStore.getItem());
}
<!-- page.svelte -->
<script context="module">
    export async function preload(page, session) {
        const response = await this.fetch(`/server/route/exposing/get_item`);
        return {
            item: await response.text()
        }
    }
</script>

<script>
    import {onMount} from 'svelte';

    export let item;
    
    let DataStore;

    onMount(async () => {
        // Dynamically import DataStore so I can use it later, e.g. in on click handlers.
        const module = await import("./my/frontend/datastore");
        DataStore = module.default;
    }
</script>

<p>{item}</p>

Why that solution isn't satisfying

  1. It's quite a bit of boilerplate, which should be avoidable, given that Svelte is a compiler.
  2. The preload function in page.svelte gets called every time the page is navigated to.
    But on the frontend, I do not want this to happen, since I want to check for the data in the local offline store first.

My questions

  1. Is there a way to make this work that avoids either or both of these problems?
    (Maybe someone involved with the development can answer the other two:)
  2. What's the reasoning behind not letting props be passed into pages from the two entry point files?
  3. Will this be different in the upcoming Sapper replacement recently announced by Rich Harris?

Thank you very much for reading. All pointers appreciated. :)

// edit:

After fiddling around a little more, I found a way to differentiate between server and client in the preload function and prevent the AJAX call when navigating on the frontend at least, but it feels quite hacky and adds more boilerplate:

// server.js
// [imports here]

polka()
    .use(
        sapper.middleware({
            session: (req, res) => ({environment: "server"})
        })
    )
    .listen(PORT, err => {/* other stuff */});
<!-- page.svelte -->
<script context="module">
    export async function preload(page, session) {
        if (session.environment === "server") {
            session.environment = "client"

            const response = await this.fetch(`/server/route/exposing/get_item`);
            return {
                item: await response.text()
            }
        } else {
            const module = await import("./my/frontend/datastore");
            return {
                item: await module.default.getItem()
            };
        }
    }
</script>
0 Answers
Related