31

I'm trying in the <script> to manually document.createElement and then to appendChild an audio every time an eventListener is called to replace it. Everything works fine in the browser, apart a really quick error when the page loads but this lasts less then 100ms. There is an error in the Terminal as well

ReferenceError: document is not defined
    at Object (webpack:///./src/components/Record/Component.svelte?:26:17)

Seems that the above is called when document is not ready yet but afterwards it is fine, how to fix it? Or what is the preferred way to destroy and recreate components in Svelte world (Sapper)?

Paolo
  • 20,112
  • 21
  • 72
  • 113
Mac_W
  • 2,927
  • 6
  • 17
  • 30

4 Answers4

57

document is not defined on the server, so you need to guard against that in your component so that particular piece of code is only run in the browser.

You can use the onMount function which is only run in the browser when the component has been rendered.

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

  onMount(() => {
    document.createElement(...);
    
    // ...
  });
</script>
Paolo
  • 20,112
  • 21
  • 72
  • 113
Tholle
  • 108,070
  • 19
  • 198
  • 189
  • 12
    Need to switch mindset to remember that initially this is all n the server. – Mac_W May 17 '19 at 20:29
  • 3
    if you get this error when using onDestroy, try `onMount(() => { return () => { // on destroy code }})` –  Oct 30 '22 at 04:26
23

Sapper works well with most third-party libraries you are likely to come across. However, sometimes, a third-party library comes bundled in a way which allows it to work with multiple different module loaders. Sometimes, this code creates a dependency on window, such as checking for the existence of window.global might do.

Since there is no window in a server-side environment like Sapper's, the action of simply importing such a module can cause the import to fail, and terminate the Sapper's server with an error such as:

ReferenceError: window is not defined

The way to get around this is to use a dynamic import for your component, from within the onMount function (which is only called on the client), so that your import code is never called on the server.

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

    let MyComponent;

    onMount(async () => {
        const module = await import('my-non-ssr-component');
        MyComponent = module.default;
    });
</script>

<svelte:component this={MyComponent} foo="bar"/>
Rutuja
  • 331
  • 2
  • 3
2

Thank you to Rutuja's answer here, I was also able to get my application working. Adding to Rutuja's answer, if anyone runs into this issue while using Sapper + Svelte + another package (in my case theme.js), you may also need to import multiple components. This is described in Mozilla's documentation here for import.

When importing a default export with dynamic imports, it works a bit differently. You need to destructure and rename the "default" key from the returned object.

(async () => {
  if (somethingIsTrue) {
    const { default: myDefault, foo, bar } = await import('/modules/my-module.js');
  }
})();

For my own project (using theme.js), I had to use the following logic (based on the svelte example for theme.js located here):

const themeState = {
    active: undefined,
    selected: undefined,
    themes: [],
}

onMount(async () => {
    const { default: Themer, auto, system } = await import('themer.js')

    themeState = {
        active: undefined,
        selected: light,
        themes: [light, dark, auto, system],
    }

    const themer = new Themer({
        debug: true,
        onUpdate: (theme) => (themeState.active = theme),
        themes: { light, dark, auto, system },
    })

    function noThemeSupport({ theme }) {
        return theme === 'system' && !themer.themeSupportCheck()
    }

    function setTheme(theme) {
        themeState.selected = theme
        themer.set(theme)
    }

    themer.set(themeState.selected)
})
ctwheels
  • 21,901
  • 9
  • 42
  • 77
0

According to latest style of svelte X d3.js version 4

You could do


    let mounted = false;
    onMount(() => {
        mounted = true;
    });
    
    // add grid
    $: if(mounted) d3.selectAll('g.yAxis g.tick')
        .append("line")
        .attr("class", "gridline")
        .attr("x1", 0)
        .attr("y1", 0)
        .attr("x2", width_workable)
        .attr("y2", 0)
        .attr("stroke", "#9ca5aecf") // line color
        .attr("stroke-dasharray","4"); // make it dashed;;

another way is


    $: d3.select(elem).selectAll('g.tick')
        .append("line")
        .attr("class", "gridline")
        .attr("x1", 0)
        .attr("y1", 0)
        .attr("x2", width_workable)
        .attr("y2", 0)
        .attr("stroke", "#9ca5aecf") // line color
        .attr("stroke-dasharray","4"); // make it dashed;;

if you don't want to manage mounted state

CircleOnCircles
  • 3,646
  • 1
  • 25
  • 30