Bundling
anywidget does not require you to bundle or transform your JavaScript source code. However, the use of local dependencies or non-standard syntax (e.g., TypeScript, React, Vue, Solid, Svelte) necessitates the use of a bundler to merge together files into a single optimized ESM file.
Keeping it Simple
For those new to JavaScript, you may be inclined to explore various front-end tools. Here are key considerations for widget context:
-
Deviation from Browser Standards: anywidget requires web-standard ECMAScript modules, ensuring the code you write is directly interpretable by web browsers. Introducing frameworks or tools like TypeScript can lead to misconceptions about what is (and what is not) the front end, especially when first learning.
-
Additional Tooling: Such technologies require your front-end code to be transformed to ESM for the browser to understand. Without them, anywidget projects can be developed purely with Python. Incorporating them turns your project into a Python-JavaScript hybrid, necessitating tools like Node.js and bundlers.
Our recommendation? Keep it simple:
- Framework: You likely donβt need one. If you do, think about crafting a standalone JS library first, outside of the Python widget. Unsure? Start without a framework.
- Dependencies: Import from CDNs like esm.sh or jsDelivr first and get something working. Make sure to use versioned URLs for stability.
- Type-Safety: While TypeScript is powerful, it requires browser
transformation. Use TypeScript within
JSDoc comments
for type-checking benefits in
.js
files, eliminating the need for a build step.
Project Templates
Bootstrap a new widget repository that is ready to publish to PyPI:
npm create anywidget@latest # or pnpm, yarn
Every template is a hatchling
-based Python project and uses the
hatchling-jupyter-builder
plugin to bundle the widget front-end code during a
PEP 517 build. We prefer
esbuild for bundling in most of the templates,
unless Bun is used to create the project,
bun create anywidget@latest
which will prefer Bunβs built-in bundler.
Bundler Guides
We recommend using one of the above project templates if your widget needs a bundler. However, the follow sections provide a general guide of configuring these bundlers.
esbuild
esbuild is very fast JavaScript bundler written in
Golang. It can transform TypeScript, JSX, and CSS files and includes zero
JavaScript dependencies. Binaries can be
installed without npm
,
making it a great fit for anywidget projects.
Project Setup
The following project structure contains a python package (hello_widget
) with
separate JS/CSS source code under src/
:
hello_widget/
βββ pyproject.toml
βββ hello_widget/
β βββ __init__.py
βββ src/
βββ index.js
βββ styles.css
Build
We can bundle these assets into hello_widget/static
with esbuild
:
esbuild --bundle --format=esm --outdir=hello_widget/static src/index.js
#
# hello_widget/static/index.js 150b
# hello_widget/static/index.css 81b
#
# β‘ Done in 2ms
Make sure the final bundled assets are loaded by the Python module:
# hello_widget/__init__.py
import pathlib
import anywidget
import traitlets
# bundler yields hello_widget/static/{index.js,styles.css}
bundler_output_dir = pathlib.Path(__file__).parent / "static"
class HelloWidget(anywidget.AnyWidget):
_esm = bundler_output_dir / "index.js"
_css = bundler_output_dir / "styles.css"
name = traitlets.Unicode().tag(sync=True)
Development
The esbuild
CLI also includes a βwatchβ mode, which tells esbuild to listen
for changes on the file system and automatically rebuild whenever a file changes
that could invalidate the build. anywidgetβs
native HMR will watch
for changes to the re-bundled outputs from esbuild
, swapping in the new bundle
in the front end.
esbuild --bundle --format=esm --outdir=hello_widget/static src/index.js --watch
Vite
Our Vite plugin offers a more fully featured development experience compared to anywidgetβs builtin HMR, but at the cost of added project complexity and tooling. Vite is a good choice if you want to use a front-end framework like Svelte or Vue or need more fine grain control over your bundling.
Project Setup
From the root of the project structure above.
npm install -D vite
Create a vite.config.js
file with the following configuration:
// vite.config.js
import { defineConfig } from "vite";
export default defineConfig({
build: {
outDir: "hello_widget/static",
lib: {
entry: ["src/index.js"],
formats: ["es"],
},
},
});
Your project structure should now look like:
hello_widget/
βββ pyproject.toml
βββ hello_widget/
β βββ __init__.py
βββ node_modules/
βββ package-lock.json
βββ package.json
βββ src/
β βββ index.js
β βββ styles.css
βββ vite.config.js
Build
We can now bundle the assets into hello_widget/static
with Vite, just like
esbuild. Again, make sure the final bundled assets are loaded by the Python
module.
npx vite build
# vite v4.0.4 building for production...
# β 2 modules transformed.
# hello_widget/static/style.css 0.05 kB β gzip: 0.06 kB
# hello_widget/static/index.mjs 0.13 kB β gzip: 0.14 kB
Development
The Vite plugin for anywidget extends its dev server with precise HMR
support for Jupyter Widgets. To get started with HMR for your widget, install
the anywidget
Plugin and add the following to your vite.config.js
:
npm install -D @anywidget/vite
// vite.config.js
import { defineConfig } from "vite";
+ import anywidget from "@anywidget/vite";
export default defineConfig({
build: {
outDir: "hello_widget/static",
lib: {
entry: ["src/index.js"],
formats: ["es"],
},
},
+ plugins: [anywidget()],
});
Start the development server from the root of your project:
npx vite
#
# VITE v4.0.4 ready in 321 ms
#
# β Local: http://localhost:5173/
# β Network: use --host to expose
# β press h to show help
#
Finally, link your Python widget to the dev server during development.
# hello_widget/__init__.py
import pathlib
import anywidget
import traitlets
_DEV = True # switch to False for production
if _DEV:
# from `npx vite`
ESM = "http://localhost:5173/src/index.js?anywidget"
CSS = ""
else:
# from `npx vite build`
bundled_assets_dir = pathlib.Path(__file__).parent / "static"
ESM = (bundled_assets_dir / "index.mjs").read_text()
CSS = (bundled_assets_dir / "styles.css").read_text()
class HelloWidget(anywidget.AnyWidget):
_esm = ESM
_css = CSS
name = traitlets.Unicode().tag(sync=True)
Any changes to src/*
will now be updated immediately in active output cells
with this widget. Happy coding!