如何在Svelte/Sapper中加载外部JS库?

19

我一直在尝试将 Ace 编辑器 (https://ace.c9.io/) 加载到我的 Sapper 应用程序中。当我将其加载到 Sapper 路由的脚本标签中时,我成功加载了它,但是当我尝试在另一个由路由渲染的 Svelte 组件中执行相同操作时,出现以下错误:

未定义 ace

这是我目前拥有的代码,在 Sapper 路由中的情况下可以正常工作:

<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>
5个回答

30

我在开始尝试 Svelte2 时,曾经草率地编写了一个组件,用于加载外部的遗留 JS 库,现在我已将其重构为 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>

这并没有经过彻底的测试,对于一次性使用可能不需要单独的组件;但基本上这种方法解决了 Rich Harris 提到的时序问题。如果有可用的话,现在 import 显然是更好的选择。


1
这看起来是一个很好的方法,但它有一个问题:如果库被缓存或加载非常快,你可能会遇到竞争条件,即浏览器在Svelte的onMount运行之前就已经触发了SCRIPT的加载。在这种情况下,你将永远不会得到加载回调。 - hallvors
5
我用这个变种解决了这个问题 - 你向LibLoader组件传递一个额外的参数,即一个对象的名称,该对象将在外部脚本准备就绪时添加到窗口中。(作为字符串传递名称,libraryDetectionObject="fabulousLibraryAPI" https://gist.github.com/hallvors/5340f94cb2ad3ab83b9bd0246a4f5b5e) - hallvors

17
在Svelte中使用外部库的方法是通过import进行。我不知道如何轻易地使用Ace实现这一点 - 代码编辑器往往相当复杂,它们有自己的模块系统来加载语言和主题等 - 但理论上看起来应该像这样
<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>

如果导入失败,您总是可以以老式的方式添加<script src="...">标签到您的主要template.html中,并继续将ace作为全局使用。Svelte组件内的<script src="...">标签将会异步加载——换句话说,通常情况下您的组件代码将在外部脚本加载之前运行。


具体而言,特别是ace editor(至少是brace版本),需要一个dispatch('init', editorInstance)才能正常工作。其中editorInstance = ace.edit(editorElement),而dispatch = [svelte.]createEventDispatcher()。此外,请参见svelte-ace-editor - milahu

7
// 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));
    });
}

然后在 Svelte 组件上

// 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>

4

我需要添加Keycloak Javascript adapter。这里的旧答案 (https://dev59.com/olIH5IYBdhLWcg3wb97W#61979865) 对我无效,所以我简单地:

  1. <head>中的template.html中首先包含Keycloak脚本,如:<script src="http://localhost:8080/auth/js/keycloak.js"></script>
  2. 然后在我的login.svelte路由内使用了onMount

它在组件首次呈现到DOM之后运行

正如预期的那样工作。


1
{@html '<script src="/js/pages/projects.js" />'}

可以在SvelteKit中使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接