concept-collection / jupyterlite-numbl-kernel
Go to fileHistoryFork
Jeremy Magland add numbl link 2c57b11 14 Commits
.githubAdd cooperative cell interrupt (Stop button)
demoadd numbl link
jupyterlite_numbl_kerneljupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLite
srcSupport input() (stdin)
stylejupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLite
.gitignoreCommit yarn.lock now that numbl 0.4.14 is on npm
.prettierignoreAdd cooperative cell interrupt (Stop button)
.yarnrc.ymljupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLite
install.jsonjupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLite
LICENSEjupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLite
package.jsonSupport input() (stdin)
pyproject.tomljupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLite
README.mdSimplify and tidy README; de-emphasize mip
tsconfig.jsonjupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLite
yarn.lockSupport input() (stdin)

jupyterlite-numbl-kernel#

Run numbl — numerical computing with MATLAB syntax — in JupyterLite notebooks, entirely in the browser. No server, no kernel process, nothing for the reader to install.

numbl is an open-source numerical-computing engine, written in TypeScript, that uses MATLAB syntax, so .m code runs unchanged. This kernel runs a numbl session in a Web Worker on the page: variables persist across cells, console output streams into the running cell, plots render as figures (including interactive 3-D), and .m files next to the notebook are part of the workspace (named functions, called from cells). Everything runs client-side.

Demo: https://concept-collection.github.io/jupyterlite-numbl-kernel/ (deployed from this repo via GitHub Pages)

Why#

A numbl notebook can be a static web page: hostable on GitHub Pages, shareable as a link, and runnable by anyone with a browser. Because numbl uses MATLAB syntax and needs no MATLAB/Octave install (or any server), the same .m code that would otherwise sit behind a licensed product just runs in the tab.

How it works#

Three small pieces, all in src/:

  • Kernel (kernel.ts) implements JupyterLite's BaseKernel. Before each cell runs, .m files in the notebook's directory are synced into the numbl session; the cell then runs against the session's persistent workspace (a Web Worker that numbl manages). Console output streams back as stream messages, and plots are published as display_data with the mime type application/vnd.numbl.figure+json.
  • Figure renderer (mime.tsx) is a mime renderer for that type: it replays the plot instructions and mounts numbl's React FigureView. Outputs are plain JSON, so saved notebooks re-render wherever the extension is installed.
  • Registration (index.ts) registers the kernelspec.

Build a site with it#

pip install jupyterlite-core jupyterlite-numbl-kernel
jupyter lite build --contents my-notebooks --output-dir dist
# dist/ is a static site; serve it anywhere

.m files next to a notebook are synced into the session recursively, so MATLAB's folder-based +namespace/, @class/, and private/ layouts work as expected.

The demo/ directory is the source of the demo site above — a numbered walkthrough of the language and the MATLAB object model (classes, namespaces, packages). .github/workflows/deploy.yml builds and deploys it to GitHub Pages.

Fresh content on redeploy (demo choice)#

JupyterLite caches notebooks in the browser (IndexedDB) and can keep serving stale copies from its service worker even after a redeploy. Because this is a demo, demo/jupyter-lite.json uses in-memory storage and disables the service worker, so every reload re-seeds the latest deployed notebooks. The trade-off is that a visitor's edits last only for the session. For a real deployment where users keep their work, drop those settings and use JupyterLite's default persistent storage.

Cross-origin isolation (for interrupt and input)#

The Stop button and input() both rely on a SharedArrayBuffer, which browsers expose only on a cross-origin-isolated page (COOP/COEP headers). GitHub Pages can't set those headers, so the demo ships a small service worker, demo/coi-serviceworker.js (based on coi-serviceworker), that synthesizes them client-side; demo/inject-coi.mjs wires it into every generated page during the build. Without isolation, interrupt and input() degrade gracefully (see Limitations).

Limitations (proof of concept)#

  • Interrupt is cooperative: the Stop button aborts the running cell at the next loop iteration or function call, reports a KeyboardInterrupt, and leaves earlier variables intact. It needs cross-origin isolation (above); on a non-isolated page it's a no-op, and a tight loop with no calls (e.g. while true; x = x + 1; end) has no checkpoint to abort at — both cases need a kernel restart.
  • input() prompts in the notebook and blocks until you answer. It also needs cross-origin isolation; without it, input() raises rather than prompting.
  • Figures are per-cell (like inline matplotlib): hold on does not span cells.
  • Named functions go in .m files, not cells (a numbl REPL limitation); anonymous functions work in cells. See demo/content/fib.m.
  • .m sync is one-way: deleting a file leaves its function defined until the kernel restarts, and files written by cell code don't appear back in the file browser.
  • numbl is not MATLAB — it covers a large, tested subset of the language and toolboxes. See numbl for scope.

Development#

Requires Python ≥ 3.9, Node ≥ 20, and numbl >= 0.4.16.

python -m venv .venv && source .venv/bin/activate
pip install "jupyterlab~=4.6.0" "jupyterlite-core==0.8.1"

jlpm install
jlpm build            # tsc + labextension (dev)
pip install -e .      # editable install, registers the labextension

# Build and serve the demo site locally
pip install -r demo/requirements.txt
jupyter lite build --lite-dir demo --contents content --output-dir demo/_output
node demo/inject-coi.mjs demo/_output   # cross-origin isolation, for interrupt
python -m http.server -d demo/_output 8000   # then open http://localhost:8000

jlpm watch rebuilds on change during development.

License#

Apache-2.0. Built on numbl and JupyterLite; scaffolded from the jupyterlite/echo-kernel template.