.md →
EsmWidget API#
Bases: AnyWidget
Render an inline ES module <https://developer.mozilla.org/docs/Web/JavaScript/Guide/Modules>_ in a notebook.
This is a thin, library-agnostic runner. You hand it a JavaScript ES module
(the code), and it loads that module in the browser and calls its
render function. Because the module is loaded as a real ES module,
top-level import statements work — so you can pull any library straight
from a CDN (motion.dev, Observable Plot, d3, chart.js, three.js, …)::
import { animate } from "https://cdn.jsdelivr.net/npm/motion@12/+esm";
The module must follow the standard anywidget
<https://anywidget.dev/en/afm/>_ contract — export default { render } —
where render receives { model, el }, may append to el, and may
return a cleanup function.
The point of the widget is the data traitlet: a JSON-able value synced
both ways. Update it from Python and the browser sees a change:data
event; set it from JS and Python sees the new value. Crucially, changing
data does not re-run render — your module observes the change and
decides what to do, so animation libraries can tween toward the new state
instead of hard-cutting.
Examples:
Tween on data change with motion.dev:
import marimo as mo
from wigglystuff import EsmWidget
w = EsmWidget(
'''
import { animate } from "https://cdn.jsdelivr.net/npm/motion@12/+esm";
export default {
render({ model, el }) {
const box = document.createElement("div");
box.style.cssText = "width:60px;height:60px;background:#e94560";
el.appendChild(box);
const draw = () => animate(box, { x: model.get("data").x }, { type: "spring" });
draw();
model.on("change:data", draw);
}
}
''',
data={"x": 0},
width=600,
height=200,
)
mo.ui.anywidget(w)
# ...then, from another cell:
w.data = {"x": 240} # the box springs to the new position
Redraw on data change with Observable Plot:
EsmWidget(
'''
import * as Plot from "https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6.17/+esm";
export default {
render({ model, el }) {
const draw = () => el.replaceChildren(
Plot.plot({ marks: [Plot.dot(model.get("data").points, { x: "x", y: "y" })] })
);
draw();
model.on("change:data", draw);
}
}
''',
data={"points": [{"x": 1, "y": 2}, {"x": 2, "y": 1}]},
)
Point at a local file or a URL instead of an inline string:
The module is executed verbatim in the browser, so only pass JavaScript you trust (typically your own).
Create an EsmWidget.
The module source is resolved in Python, so the browser always receives
plain JavaScript. code (or src) may be any of:
- an inline JavaScript ES module string,
- a path to a local
.jsfile, or - an
http(s)://URL.
URLs and file paths are fetched/read here at construction time; the
detection is by value, so EsmWidget("https://.../widget.js") and
EsmWidget("export default { render() {} }") both do the right thing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
Optional[str]
|
Inline ES module JavaScript, a local file path, or an
|
None
|
src
|
Optional[str]
|
An explicit alias for |
None
|
data
|
Any
|
Any JSON-able value, synced two-way with the browser. Defaults
to |
None
|
css
|
str
|
Optional inline CSS injected into the widget root. |
''
|
width
|
int
|
Container width in pixels. |
500
|
height
|
int
|
Container height in pixels. |
300
|
**kwargs
|
Any
|
Forwarded to |
{}
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If neither or both of |
Source code in wigglystuff/esm_widget.py
Synced traitlets#
| Traitlet | Type | Notes |
|---|---|---|
code |
str |
Resolved ES module JavaScript (inline JS, or the contents of a file / URL, fetched in Python). Must export default { render }; loaded in the browser as a real module, so top-level import statements work. |
css |
str |
Optional inline CSS injected into the widget root. |
data |
Any |
Any JSON-able value, synced two-way. Changing it fires change:data in the browser but does not re-run render. |
width |
int |
Container width in pixels. |
height |
int |
Container height in pixels. |
error |
str |
Read-back of the latest JS runtime error, or "". |