Importing external js works only in a callback function

Js is not my strongest skill and I can’t find this in the kino examples.
Every time I wan’t to include js it only works for me inside a callback function that I pass to the promise. With one included file it’s fine but with multiple it becomes tricky.
Importing on top of the file does not work with every kind of js file like in the mermaid example.
If I try to import the url that is provided in the importJS in the example below it throws an error in the js.
I tried the same with pev2 package and I have similar problem there.

defmodule VlBlockly do
  use Kino.JS

  def new(toolbox) do
    Kino.JS.new(__MODULE__, toolbox)
  end

  asset "main.js" do
    """
    const blockyRoot = '<div id="blocklyDiv" style="height: 480px; width: 600px;"></div>'
    export function init(ctx, toolbox) {
        ctx.importJS("https://unpkg.com/blockly/blockly.min.js").then(() => {
            load(ctx, toolbox)
        })
    }
    const load = (ctx, toolbox) => {
        ctx.root.innerHTML = blockyRoot;
        const workspace = Blockly.inject('blocklyDiv', {toolbox: JSON.parse(toolbox)});
    }
    """
  end
end
toolbox =
  %{
    contents: [
      %{kind: "block", type: "text"},
      %{kind: "block", type: "text_print"}
    ],
    kind: "flyoutToolbox"
  }
  |> Jason.encode!()

VlBlockly.new(toolbox)
1 Like

@dkuku so the confusion is around importing multiple dependencies with importJS? You could import the second file inside the callback, but to avoid nesting you can do async/await. Like this:

defmodule VlBlockly do
  use Kino.JS

  def new(toolbox) do
    Kino.JS.new(__MODULE__, toolbox)
  end

  asset "main.js" do
    """
    export async function init(ctx, toolbox) {
      await ctx.importJS("https://unpkg.com/blockly/blockly.min.js");
      // await ctx.importJS(...);
      
      ctx.root.innerHTML = `
        <div id="blocklyDiv" style="height: 480px; width: 600px;"></div>
      `;
      
      const workspace = Blockly.inject('blocklyDiv', { toolbox });
    }
    """
  end
end

toolbox = %{
  contents: [
    %{kind: "block", type: "text"},
    %{kind: "block", type: "text_print"}
  ],
  kind: "flyoutToolbox"
}

VlBlockly.new(toolbox)

If you run into any errors, please include those : )

Sidenote, you don’t need to use Jason, the payload is automatically serialized and deserialized!

Importing on top of the file does not work with every kind of js file like in the mermaid example

Yeah, it depends on how the package is distributed. Top-level import works fine for ES modules, but some packages expect to be loaded via <script> tag, which is exactly what importJS does.

In more complex cases you can also consider using a JS bundler as in a regular JS project.

1 Like

Thanks for the hints :+1:

@jonatanklosko
I wonder if having a html asset would not make things easier here? Then the scripts can be embedded directly in the html. rit would be easier to mirror the examples from the web. Building the initial root layout could be also simplified.

@dkuku there is Kino.HTML for some range of use cases, but most of the time we use the JS init function as an entry point, because it receives data from the Elixir side.

This thread was very useful.
I was also able to use p5.js (a JavaScript library for creative coding) inside Livebook. In really don’t know if I can do much more than that.