Skip to content
Feeding a clanker? Grab this page as raw .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:

EsmWidget("widgets/chart.js")
EsmWidget("https://example.com/widget.js")

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 .js file, 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 http(s):// URL. Mutually exclusive with src.

None
src Optional[str]

An explicit alias for code (same accepted forms), handy when the intent is "load from here". Mutually exclusive with code.

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 anywidget.AnyWidget.

{}

Raises:

Type Description
ValueError

If neither or both of code and src are given.

Source code in wigglystuff/esm_widget.py
def __init__(
    self,
    code: Optional[str] = None,
    *,
    src: Optional[str] = None,
    data: Any = None,
    css: str = "",
    width: int = 500,
    height: int = 300,
    **kwargs: Any,
) -> None:
    """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 ``.js`` file, 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.

    Args:
        code: Inline ES module JavaScript, a local file path, or an
            ``http(s)://`` URL. Mutually exclusive with ``src``.
        src: An explicit alias for ``code`` (same accepted forms), handy
            when the intent is "load from here". Mutually exclusive with
            ``code``.
        data: Any JSON-able value, synced two-way with the browser. Defaults
            to ``{}``.
        css: Optional inline CSS injected into the widget root.
        width: Container width in pixels.
        height: Container height in pixels.
        **kwargs: Forwarded to ``anywidget.AnyWidget``.

    Raises:
        ValueError: If neither or both of ``code`` and ``src`` are given.
    """
    if (code is None) == (src is None):
        raise ValueError("Provide exactly one of `code` or `src`.")

    source = code if code is not None else src
    super().__init__(
        code=self._resolve_source(source),
        css=css,
        data={} if data is None else data,
        width=width,
        height=height,
        **kwargs,
    )

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 "".