Svelte/Sapper.js - How to initialize store with lo

2020-05-27 04:15发布

问题:

I come from a React background but I'm switching to Svelte and Sapper for my next application in order to fight the massive bundle size that comes with React these days. However, I'm having trouble initializing Svelte's store with data retreived from localStorage.

As per the Sapper docs (https://sapper.svelte.dev/docs#Getting_started), I created my project by running npx degit "sveltejs/sapper-template#rollup" my-app from the command line. I then installed the dependencies and removed the demo code in the src folder.

I then created two files: src/routes/index.svelte and src/store/index.js.

Code for both:

src/store/index.js

import {writable} from "svelte/store";

export let userLang;

if(typeof window !== "undefined") {
    userLang = writable(localStorage.getItem("lang") || "en");
} else {
    userLang = writable(null);
}

src/routes/index.svelte

<script>
    import {userLang} from "../store";
</script>

<p>Your Preferred Language: {$userLang}</p>

When I run the application and hit the index route, I see this:

Your Preferred Language: null

which then almost immediately updates and changes to

Your Preferred Language: en

when there is no lang item in localStorage, and changes to

Your Preferred Language: fr

after explicitly setting localStorage.setItem("lang", "fr") from the developer console and refreshing.

I know that the store is being initialized on the server first where window is undefined and then is being rehydrated on the client. So this behavior is expected.

So my question is: how can I skip the server initialization entirely? Is it possible to only setup the store on the client (where localStorage is defined) so that the user's chosen language is immediately available?

I can't default to having everything in English or any other language after the user has chosen to change their preferred language. I also can't get the user language from the browser via navigator.language on inital page load either since navigator is undefined on the server as well.

And having a flash of empty text appear before the store rehytdates would screw up the UX for my application, especially when the value of userLang is going to be used all over the place with translations.

So any strategies or hacks for this are definitely appreciated.

**** Deeper Issue ****

I would actually prefer to not have server-side rendering at all for this application, but I do need all the other excellent features that Sapper provides, like routing, prefetching, and static site building.

So I tried running npx sapper export as per the docs to generate a completely static site in an effort to remove the server from the equation, but the exact same issue still occurs, even though there is no server being used at all.

Does anyone have any advice on how to configure Sapper and turn off SSR but keep the other features?

Thank you!

**** Update ****

As per Rich Harris's answer, wrapping the markup with {#if process.browser} does the trick just fine. So I've updated the src/routes/index.sveltefile like so:

<script>
    import {userLang} from "../store";
</script>

{#if process.browser}
    <p>Your Preferred Language: {$userLang}</p>
{/if}

and the userLang variable is immediately set with the value from localStorage or defaults to en as I intended for this simple demo. There is no more flash of null, so it's essentially behaving like it's client-side only at this point.

I will work on fleshing out my project and see if there are any more issues I encounter. Til then, I think this solves my issue.

回答1:

At present, SSR is non-optional. There's an issue open for an SPA mode — https://github.com/sveltejs/sapper/issues/383 — that would behave as you describe, we just need to get round to it.

We also plan to have built-in support for i18n in a future release: https://github.com/sveltejs/sapper/issues/576

In the meantime, you can fake it by wrapping all your markup in {#if process.browser} — anything inside won't be server rendered, but will be present as soon as JavaScript kicks in.