How do I load an external JS library in Svelte/Sapper?
Asked Answered
M

5

20

I have been trying to load the ace editor (https://ace.c9.io/) into my Sapper application. I had success loading it in the script tag when I loaded it in a Sapper route, but when I am trying to do the same in a Svelte component which is again rendered by a route I get the error:

ace is not defined

This is the code I have, which is working fine if it is a Sapper route:

<div id="editor"> def main():
    return sum(range(1,100))
</div>

    <script src="https://pagecdn.io/lib/ace/1.4.6/ace.js" type="text/javascript" charset="utf-8"></script>

    <script>
          var editor = ace.edit("editor");
          editor.setTheme("ace/theme/monokai");
          editor.session.setMode("ace/mode/python");
          editor.resize()
    </script>
Mammilla answered 7/1, 2020 at 14:2 Comment(0)
W
30

I hacked together a component to load external legacy JS libraries when I first started playing with Svelte2 and just refactored it to Svelte 3.

// LibLoader.svelte

<svelte:head>
  <script bind:this={script} src={url} />
</svelte:head>

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

  const dispatch = createEventDispatcher();
  export let url;
  let script;

  onMount(async () => {
    script.addEventListener('load', () => {
      dispatch('loaded');
    })

    script.addEventListener('error', (event) => {
      console.error("something went wrong", event);
      dispatch('error');
    });
  });
</script>
// MyComponent.svelte

<LibLoader url="myExternalLib.js"
on:loaded="{onLoaded}" />


<script>
  import LibLoader from './LibLoader.svelte';


  function onLoaded() {
    myExternalLib.doStuff();
  }
</script>

This is not thoroughly tested and for a one-off probably doesn't warrant a separate component; but basically the approach gets round the timing issue Rich Harris mentions. These days import is obviously the better option if it is available.

Wuhan answered 23/5, 2020 at 23:30 Comment(2)
This looks like an excellent approach, but it has one problematic shortcoming: if the library is cached or loads very fast, you may end up with race conditions where the browser fired load on the SCRIPT before Svelte's onMount runs. In that case you'll never get the loaded callback.Bastion
I worked around that with this variant - you pass one more parameter to the LibLoader component, namely the name of an object that will have been added to window the the external script is ready. (Pass in the name as a string, libraryDetectionObject="fabulousLibraryAPI" gist.github.com/hallvors/5340f94cb2ad3ab83b9bd0246a4f5b5eBastion
C
18

The way to use an external library in Svelte is to import it. I don't know how easy it is to do that with Ace — code editors tend to be somewhat complex, with their own module systems for loading languages and themes etc — but in theory it would look something like this:

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

  let div;
  let editor;

  onMount(() => {
    // we need to use onMount because the div hasn't
    // been created by the time the init code runs
    editor = ace.edit(div);
    editor.setTheme("ace/theme/monokai");
    editor.session.setMode("ace/mode/python");
    editor.resize();

    return () => {
      // any cleanup code goes here
    };
  });
</script>

<div bind:this={div}> def main():
    return sum(range(1,100))
</div>

If importing fails, you can always do it the old-fashioned way, adding the <script src="..."> tag to your main template.html, and continuing to use ace as a global. <script src="..."> tags inside Svelte components will load asynchronously — in other words, your component's code will generally run before the external script has loaded.

Crone answered 7/1, 2020 at 16:15 Comment(1)
specifically ace editor (at least the brace version) needs a dispatch('init', editorInstance) to work. editorInstance = ace.edit(editorElement) and dispatch = [svelte.]createEventDispatcher(). also see svelte-ace-editorSylvanite
M
9
// document.js

export function loadScript(src) {
    return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = src;

        document.body.appendChild(script);

        script.addEventListener('load', () => resolve(script));
        script.addEventListener('error', () => reject(script));
    });
}

And then on the svelte component

// index.svelte
<script>
    import { onMount } from 'svelte';
    import { loadScript } from './document.js';

    onMount(async () => {
      await loadScript('your-external-script.js');
      console.log('script loaded successfully!');
    };
</script>
Mars answered 23/7, 2022 at 19:36 Comment(0)
G
4

I needed to add the Keycloak Javascript adapter. The older answer here (https://mcmap.net/q/610573/-how-do-i-load-an-external-js-library-in-svelte-sapper) did not work for me so I simply

  1. included the Keycloak script first in the template.html inside <head> like <script src="http://localhost:8080/auth/js/keycloak.js"></script>
  2. and then inside my login.svelte route used the onMount

which runs after the component is first rendered to the DOM

Works exactly as expected.

Goblet answered 12/10, 2020 at 19:0 Comment(0)
M
1
{@html '<script src="/js/pages/projects.js" />'}

Can be use for SvelteKit as well.

Mccraw answered 5/1, 2023 at 8:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.