41c072cjupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLiteJeremy Magland 1# jupyterlite-numbl-kernel
80cd178Update numbl links and tidy copyJeremy Magland 3Run [**numbl**](https://numbl.org), numerical computing with **MATLAB
4syntax**, in [JupyterLite](https://jupyterlite.readthedocs.io/) notebooks,
5**entirely in the browser**, with no server, no kernel process, and nothing
6for the reader to install.
f44f6a3Rebrand numbl-first: this runs numbl (which uses MATLAB syntax)Jeremy Magland 8numbl is an open-source numerical-computing engine, written in TypeScript,
80cd178Update numbl links and tidy copyJeremy Magland 9that uses MATLAB syntax, so `.m` code runs unchanged. This kernel runs a
41c072cjupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLiteJeremy Magland 10numbl session in a Web Worker in the page: variables persist across cells,
f44f6a3Rebrand numbl-first: this runs numbl (which uses MATLAB syntax)Jeremy Magland 11console output streams into the running cell, plots render as figures in cell
12outputs (including interactive 3-D), the `mip` package manager can install
13numbl packages from GitHub, and `.m` files next to the notebook are part of
80cd178Update numbl links and tidy copyJeremy Magland 14the workspace (named functions, called from cells). Everything runs
15client-side.
17**Demo site:**
18<https://concept-collection.github.io/jupyterlite-numbl-kernel/> (deployed
80cd178Update numbl links and tidy copyJeremy Magland 19from this repo via GitHub Pages; see `.github/workflows/deploy.yml`)
21## Why
f44f6a3Rebrand numbl-first: this runs numbl (which uses MATLAB syntax)Jeremy Magland 23This kernel is a proof of concept that a numbl notebook can be a static web
24page: hostable on GitHub Pages, shareable as a link, and executable by anyone
25with a browser. Because numbl uses MATLAB syntax and needs no MATLAB/Octave
26install (or any server process), the same `.m` code that would otherwise
27require a licensed product behind a server just runs in the tab.
29## How it works
31Three small pieces, all in this repo:
80cd178Update numbl links and tidy copyJeremy Magland 33- **Kernel** (`src/kernel.ts`): implements JupyterLite's `BaseKernel` from
6b31a9aSync .m workspace files from the notebook directory into the sessionJeremy Magland 34 `@jupyterlite/services`. Before each `execute_request`, `.m` files in the
35 notebook's directory (read via the JupyterLite contents manager) are
36 synced into the numbl session; the cell source then runs against the
37 session's persistent workspace (`createNumblSession` /
38 `session.execute` from `numbl/browser`, a Web Worker that numbl manages).
39 Output streams back as `stream` messages; the run's plot instructions are
40 published as `display_data` with the mime type
41 `application/vnd.numbl.figure+json`.
80cd178Update numbl links and tidy copyJeremy Magland 42- **Figure renderer** (`src/mime.tsx`): a JupyterLab mime renderer for that
41c072cjupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLiteJeremy Magland 43 mime type: it replays the instructions through numbl's figures reducer and
44 mounts numbl's React `FigureView` (from `numbl/graphics`). Outputs are
45 plain JSON, so saved notebooks re-render wherever the extension is
46 installed.
80cd178Update numbl links and tidy copyJeremy Magland 47- **Kernel registration** (`src/index.ts`): registers the kernelspec with
41c072cjupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLiteJeremy Magland 48 JupyterLite's `IKernelSpecs`.
50## Build a site with it
52```bash
53pip install jupyterlite-core jupyterlite-numbl-kernel
54jupyter lite build --contents my-notebooks --output-dir dist
58The `demo/` directory in this repo contains the demo site sources
59(notebooks + requirements); `.github/workflows/deploy.yml` builds and
9ff4e5bRestructure demo into a full guided tour with OOP and namespacesJeremy Magland 60deploys it to GitHub Pages. `demo/content/` is a numbered walkthrough: an
61intro (with plotting), a systematic language tour (data types, matrices,
62control flow, linear algebra, data structures, numerical methods,
63plotting), the advanced MATLAB object model (classes and OOP, namespaces
64and packages), and finally installing packages with `mip`. The class and
65package `.m` files live next to the notebooks (e.g. `Vec2.m`, `+geom/`,
66`@Poly/`) and are synced into the session recursively.
8befa62Demo: use in-memory JupyterLite storage so reloads show fresh contentJeremy Magland 68### Always-fresh content (demo choice)
0fd7818Demo: disable the JupyterLite service worker so redeploys show on reloadJeremy Magland 70JupyterLite caches content in two layers that both defeat redeploys:
721. It copies notebooks into the browser's IndexedDB on first visit, and that
73 local copy then wins over the deployed files **even after a redeploy**.
742. Its **service worker** is an offline caching proxy for the app itself and
75 for content files, so it can keep serving the old app and old notebooks
76 after a redeploy until it happens to update.
78Since this is a demo, `demo/jupyter-lite.json` neutralizes both: it uses
79JupyterLite's in-memory storage (so every reload re-seeds the latest
80deployed notebooks) and disables the service-worker plugin (which the numbl
80cd178Update numbl links and tidy copyJeremy Magland 81kernel doesn't need, since it reads content on the main thread, not via the
0fd7818Demo: disable the JupyterLite service worker so redeploys show on reloadJeremy Magland 82service worker's kernel drive):
84```json
85{
86 "jupyter-config-data": {
87 "enableMemoryStorage": true,
88 "contentsStorageDrivers": ["memoryStorageDriver"],
89 "settingsStorageDrivers": ["memoryStorageDriver"],
0fd7818Demo: disable the JupyterLite service worker so redeploys show on reloadJeremy Magland 90 "workspacesStorageDrivers": ["memoryStorageDriver"],
91 "disabledExtensions": [
92 "@jupyterlite/application-extension:service-worker-manager"
93 ]
95}
96```
98The trade-off is that a visitor's edits live only for the session and are
0fd7818Demo: disable the JupyterLite service worker so redeploys show on reloadJeremy Magland 99discarded on reload. A visitor who loaded the site **before** the service
100worker was disabled still has it registered and must clear browser data
101(or `Help > Clear Browser Data`) once to get past it. For a real deployment where users should keep their
8befa62Demo: use in-memory JupyterLite storage so reloads show fresh contentJeremy Magland 102work, omit these keys (the default persistent storage) and bump
103`contentsStorageName` when you want to force-refresh shipped content.
104numbl's own package cache (installed via `mip`) lives in a separate
105IndexedDB store and is unaffected, so `mip`-installed packages still
106persist across reloads.
cf2d698Add cooperative cell interrupt (Stop button)Jeremy Magland 108### Cross-origin isolation (for interrupt)
110Cell interrupt needs a `SharedArrayBuffer`, which browsers only expose on a
111**cross-origin-isolated** page (served with `Cross-Origin-Opener-Policy:
112same-origin` and `Cross-Origin-Embedder-Policy: credentialless`). GitHub Pages
113serves static files and can't set those headers, so the demo synthesizes them
114client-side with a small service worker, `demo/coi-serviceworker.js` (based on
115[coi-serviceworker](https://github.com/niccokunzmann/coi-serviceworker)). It
116only rewrites **same-origin** responses, so numbl's cross-origin `mip` download
117from its GitHub release still works.
119`demo/inject-coi.mjs` runs after `jupyter lite build`: it copies the worker to
120the site root and adds a `<script>` registering it to every generated page's
121`<head>` (the deploy workflow does this automatically). The worker adds no
122caching — it only injects headers — so it doesn't undermine the always-fresh
123content choice above. Service workers require a secure context, so view the
124site over `https://` or `http://localhost` / `http://127.0.0.1`; an `http://`
125LAN IP or an embedded/preview browser has no service worker, and interrupt
126degrades to a no-op there.
41c072cjupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLiteJeremy Magland 128## Limitations (proof of concept)
cf2d698Add cooperative cell interrupt (Stop button)Jeremy Magland 130- **Interrupt** works cooperatively: the Stop button aborts the running cell
131 at the next loop iteration or function/builtin call, reports it as a
132 `KeyboardInterrupt`, and leaves the workspace intact (variables from before
133 the cell survive). It relies on a `SharedArrayBuffer` cancel flag that numbl
134 polls during execution, so the page must be **cross-origin isolated**
135 (`COOP`/`COEP`). Plain GitHub Pages can't set those headers, so the demo
136 ships a `coi-serviceworker` that synthesizes them (see [Cross-origin
137 isolation](#cross-origin-isolation-for-interrupt)). Two caveats: on a
138 deployment that is **not** cross-origin isolated the interrupt silently
139 falls back to a no-op (a runaway cell can then only be stopped by
140 restarting the kernel), and a **tight loop with no function or builtin
141 calls** (e.g. `while true; x = x + 1; end`) is JIT-compiled straight
142 through with no cancellation checkpoint, so it too needs a restart.
143- **No `input()`** (stdin): numbl's browser session has no stdin channel in
144 its worker protocol yet, so `input()` is unsupported. (This is now a
145 missing feature, not a headers limitation — the demo is cross-origin
146 isolated for interrupt, so the `SharedArrayBuffer` such a channel would
147 need is available.)
41c072cjupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLiteJeremy Magland 148- **Figures are per-cell** (like inline matplotlib): each cell renders the
149 figures its own commands produce; `hold on` does not span cells.
150- **Named function definitions are not supported inside cells** (a numbl
80cd178Update numbl links and tidy copyJeremy Magland 151 REPL limitation): anonymous functions work, and named functions belong in
6b31a9aSync .m workspace files from the notebook directory into the sessionJeremy Magland 152 `.m` files next to the notebook (see `demo/content/statsutils.m`), which
153 this kernel syncs into the session automatically.
154- The `.m`-file sync is **one-way**: deleting a `.m` file from the file
155 browser leaves its function defined until the kernel restarts, and files
156 written by cell code (e.g. via `fopen`) don't appear back in the file
157 browser.
41c072cjupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLiteJeremy Magland 158- **uihtml** components render display-only; the MATLAB↔HTML event bridge
159 is not wired into outputs yet.
160- numbl itself is not MATLAB: it covers a large, tested subset of the
80cd178Update numbl links and tidy copyJeremy Magland 161 language and toolbox surface. See [numbl](https://numbl.org) for scope.
163## Development
cf2d698Add cooperative cell interrupt (Stop button)Jeremy Magland 165Requires Python ≥ 3.9 and NodeJS ≥ 20, and `numbl >= 0.4.15` on npm — the
166first release with the browser cancellation API (`session.interrupt()` /
167`canInterrupt`) that cell interrupt uses. (numbl `0.4.14` added the
168incremental `session.execute` browser API this kernel is built on.) To
169develop against an unreleased numbl checkout, run `npm pack` there and point
170the `numbl` dependency at the tarball.
172```bash
173python -m venv .venv && source .venv/bin/activate
174pip install "jupyterlab~=4.6.0" "jupyterlite-core==0.8.1"
176jlpm install
177jlpm build # tsc + labextension (dev)
178pip install -e . # editable install, registers the labextension
180# Build and serve the demo site locally
181pip install -r demo/requirements.txt
182jupyter lite build --lite-dir demo --contents content --output-dir demo/_output
cf2d698Add cooperative cell interrupt (Stop button)Jeremy Magland 183node demo/inject-coi.mjs demo/_output # cross-origin isolation, for interrupt
41c072cjupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLiteJeremy Magland 184python -m http.server -d demo/_output 8000
cf2d698Add cooperative cell interrupt (Stop button)Jeremy Magland 185# then open http://localhost:8000 — a secure context, required for the
186# service worker (interrupt is a no-op without it)
189`jlpm watch` rebuilds on change during development.
191## License
80cd178Update numbl links and tidy copyJeremy Magland 193Apache-2.0. Built on [numbl](https://numbl.org) and
41c072cjupyterlite-numbl-kernel: MATLAB-syntax kernel for JupyterLiteJeremy Magland 194the [JupyterLite](https://github.com/jupyterlite/jupyterlite) kernel API;
195scaffolding follows the
196[jupyterlite/echo-kernel](https://github.com/jupyterlite/echo-kernel)
197template.