cibernox / svelte-intl-precompile

I18n library for Svelte.js that analyzes your keys at build time for max performance and minimal footprint
https://svelte-intl-precompile.com
ISC License
274 stars 13 forks source link

svelte-kit issues with SSR #22

Closed kilianso closed 2 years ago

kilianso commented 2 years ago

Hey there! I running into some issues using Sveltekit and SSR.

SSR issues

  1. Whenever I'm starting the server with npm run dev, an error occurs in the terminal:

    [vite] Error when evaluating SSR module /node_modules/precompile-intl-runtime/dist/modules/stores/formatters.js:
    SyntaxError: Unexpected token '||='

    This even persists if I disable SSR in the svelte.config.js file. If SSR is disabled, the app seems to work even with that error.

  2. Having SSR enabled using addMessages in my __layout.svelte file, devserver and build fails with this error in the terminal:

    __vite_ssr_import_3__.addMessages is not a function
  3. In your docs, you write type="module" but it should be context="module" to load it in the layout file. However, you cannot subscribe to a store value within a module context. So $session fails. You could only use get instead: https://github.com/sveltejs/svelte/issues/4306

  4. The trick with the hook to split the "accept-language" string is not working if you build the app using static-adapter. There should be an additional check like:

    let acceptedLanguage = request.headers["accept-language"] && request.headers["accept-language"].split(',')[0];`
  5. If I import a JSON file like $locales/en.json Vite fails with this:

    09:06:06 [vite] Internal server error: Failed to parse JSON file.

    If I import it as a JS or TS file, it works. Even though it's a JSON file. Is this expected behavior?

environment

MacOS: 10.15.7 Node: 14.18.0 NPM: 6.14.15 svelte-kit: 1.0.0-next.180 vite: 2.6.3

cibernox commented 2 years ago

I’d say I fixed this not long ago. Can you check in your lock file if you have the latest version of precompile-intl-runtime? Also, I believe this works in node 16

kilianso commented 2 years ago

Thanks for that quick response. Can confirm that issues 1 & 2 are gone when using Node 16. Would be cool if you could share your opinion on 3,4 and 5.

cibernox commented 2 years ago

I was AFK all day.

  1. Thanks, I fixed that typo. Regarding the store, I might need more information about what you want to do. I say that you should load the library and set the locale and translations in a context module because it's what runs first, but nothing prevents you from defining a non-module script. Did you check and run the sample app in https://github.com/cibernox/sample-app-svelte-intl-precompile/ ? Maybe you can compare things and check what's different.

  2. I added that check too. I don't use the static adapter myself but it's always good to not assume a header will be present.

  3. You must import it like if it was a javascript file. See https://github.com/cibernox/sample-app-svelte-intl-precompile/blob/main/src/routes/__layout.svelte#L3-L4 . The reason is that the json file with the translations is statically analyzed and transformed with babel into a JS module with inline functions that are fast to invoke. What you import is not the json file but its transformed sibling.

kilianso commented 2 years ago

Hey @cibernox

Ah nice! Thanks for your answers. I see, it totally makes sense to use context="module". Just to clarify what i meant by point 3. At the very bottom of your readme you have this:

<script>
  ...
  import { session } from '$app/stores';
  ...
  init({
    fallbackLocale: 'en',
    initialLocale: getLocaleFromNavigator($session.acceptedLanguage),
  });
</script>

But if you put that code in a context="module" instead of a regular script tag (as suggested) it won't work since you cannot subscribe this way to a store within a module. You need to use get.

<script context="module">
  ...
  import { session } from '$app/stores';
  import { get } from 'svelte/store';
  ...
  init({
    fallbackLocale: 'en',
    initialLocale: getLocaleFromNavigator(get(session.acceptedLanguage)),
  });
</script>
kilianso commented 2 years ago

@cibernox One more update on this.

Putting it into the regular script tag works on the development server. However, if you try to build your app using static-adapter none of the message can be found:

[svelte-i18n] The message "msg.test" was not found in "".

If you load all i18n related stuff in a context=module it works on the development server and also as a static build. But getLocaleFromNavigator does not work when placed in a context="module". It remains null even if you try to pass the session using get (since you cannot use $ in a module):

<script context="module">
  import { addMessages, init, getLocaleFromNavigator} from 'svelte-intl-precompile';
  import { session } from '$app/stores';
  import { get } from 'svelte/store';

  import en from '$locales/en.js';
  addMessages('en', en);

  init({
    fallbackLocale: 'en',
    initialLocale: getLocaleFromNavigator(get(session.acceptedLanguage))
  });
 </script>

The only solution that seems to work is loading the locales in the module but then run the init and all the session-related stuff in the regular script tag. This way, if you do a static build (or probably also with SSR) it will always take the fallback locale at first. Then, it sets the locale on the client side once the session kicks in. Like this:

<script context="module">
    import { init, getLocaleFromNavigator } from 'svelte-intl-precompile';  
    import en from '$locales/en.js';
    addMessages('en', en);
</script>
<script>
    import { addMessages } from 'svelte-intl-precompile';   
    import { session } from '$app/stores';

    init({
        fallbackLocale: 'en',
        initialLocale: getLocaleFromNavigator($session.acceptedLanguage)
    });
</script>

Not sure if this is the recommended way of doing it, but it works. ¯_(ツ)_/¯

cibernox commented 2 years ago

Yep, that looks correct. I tend to put as much as I can in a context="module" but in this situation you don't seem to have access to the session in that module. I'm not exactly sure why, the hooks run before the script, but whatever the reason it's fine to initialize the library in a regular script tag.

I updated the sample app with a code quite similar to yours.