Pluto.jl icon indicating copy to clipboard operation
Pluto.jl copied to clipboard

ES6 imports inside cells

Open fonsp opened this issue 4 years ago • 6 comments

Normally, you can import libraries inside JS using the import syntax:

import confetti from 'https://cdn.skypack.dev/canvas-confetti'
import { html, render, useEffect } from "https://cdn.jsdelivr.net/npm/[email protected]/preact/standalone.mjs"

In Pluto, this is currently not supported (because code is not executed at toplevel), and you need to use a different syntax as workaround:

html"""
<script type=module>

const { default: confetti } = await import("https://cdn.skypack.dev/canvas-confetti@1")
const { html, render, useEffect } = await import( "https://cdn.jsdelivr.net/npm/[email protected]/preact/standalone.mjs")

</script>
"""

Can we add support for the toplevel syntax? Should we do some regex tricks to rewrite user code?

fonsp avatar Mar 11 '21 13:03 fonsp

A second reason is because

const { default: confetti } = await import("https://cdn.skypack.dev/canvas-confetti@1")

schedules a microtask (or whatevs), even if the lib is already loaded, whereas the original does not.

In Pluto, this can cause a single-frame flash where the DOM is rendered without the output from a script, causing scroll shifts and all that.

In one case (a big matrix of Scrubbables), this was causing a real problem, and I had to fix it like so: https://github.com/JuliaPluto/PlutoUI.jl/commit/7180494e58b7b6f872322a4843ce56ecfd5954c1

In this notebook, I made this technique "official" by storing past imports in a Map.

fonsp avatar Feb 15 '22 17:02 fonsp

I think I have a good solution! We can make ES6 imports work using a code transformation, detecting import ... from ... and then either:

  1. Placing it outside of the async wrapper function that we put around user code, since we now execute user code in a real <script>
  2. Rewriting to await import( with the Map system from my previous comment.

Now... rewriting code is hard and we are going to make mistakes. So I have an idea!

  • We only rewrite imports at the top of the code, only if we are really sure about it.
  • This will mean that we will miss hard-to-detect imports. So we leave those, and run the code. If that throws the error SyntaxError: Cannot use import statement outside a module, then we print a second message to the console: "Oops! Pluto was not able to blabla, try putting it at the top of the code."

@dralletje WDYT?

fonsp avatar Feb 15 '22 17:02 fonsp

The microtask thing can be solved way easier, should to that anyway for any microtask stuff...

All I know is that as soon as we are transpiling javascript code, we'll become one of these things always lagging a couple feature behind, not being compatible with this and that unless we actively keep maintaining it with every new thing that comes out...

I first wanna apply the microtask fix and see if that makes it worthwhile, would love to keep type=module clean (you I don't like that we "support" it at all :P)

dralletje avatar Feb 17 '22 14:02 dralletje

Oooh awesome! I can make a notebook that you can fix?

fonsp avatar Feb 17 '22 15:02 fonsp

Fixing the underlying microtask problem instead of writing our own transpiler is a 👌👌👌 idea

fonsp avatar Feb 17 '22 15:02 fonsp

We can support this by just inserting a new script element in DOM, like jupyter: https://github.com/jupyterlab/jupyterlab/blob/fcceef7d6e2c8139c3e95b0a4794428cf485ffdb/packages/rendermime/src/renderers.ts#L1149-L1176

But we still want to define our globals like currentScript, and reassign this. And support return?

We could use acorn to detect all top-level imports (which must come before other code) and turn original code:

import confetti from "https://esm.sh/confetti"

confetti()

into

// Found imports:
import confetti from "https://esm.sh/confetti"

window.___secret_result = await async ((...inserted_globals_as_args_here) => {

	// Original code after imports
	confetti()

})(...window.__get_secret_globals)

It would be really cool if <script type="module"> was just a superset of what it does outside of Pluto.

fonsp avatar Aug 14 '25 20:08 fonsp