ES6 imports inside cells
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?
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.
I think I have a good solution! We can make ES6 imports work using a code transformation, detecting import ... from ... and then either:
- Placing it outside of the async wrapper function that we put around user code, since we now execute user code in a real
<script> - Rewriting to
await import(with theMapsystem 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?
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)
Oooh awesome! I can make a notebook that you can fix?
Fixing the underlying microtask problem instead of writing our own transpiler is a 👌👌👌 idea
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.