1# jupyterlite-numbl-kernel
3A MATLAB-syntax kernel for [JupyterLite](https://jupyterlite.readthedocs.io/) —
4notebooks that run **entirely in the browser**, with no server, no kernel
5process, and nothing for the reader to install.
7The language engine is [numbl](https://github.com/flatironinstitute/numbl), an
8open-source MATLAB-syntax implementation in TypeScript. Each kernel runs a
9numbl session in a Web Worker in the page: variables persist across cells,
10console output streams into the running cell, MATLAB plotting commands render
11as figures in cell outputs (including interactive 3-D), the `mip` package
12manager can install MATLAB-syntax packages from GitHub, and `.m` files next
13to the notebook are part of the workspace (named functions, called from
14cells) — all client-side.
16**Demo site:**
17<https://concept-collection.github.io/jupyterlite-numbl-kernel/> (deployed
18from this repo via GitHub Pages — see `.github/workflows/deploy.yml`)
20## Why
22Existing MATLAB/Octave Jupyter kernels require the real product installed
23behind a server. This kernel is a proof of concept that a MATLAB-syntax
24notebook can be a static web page: hostable on GitHub Pages, shareable as a
25link, and executable by anyone with a browser.
27## How it works
29Three small pieces, all in this repo:
31- **Kernel** (`src/kernel.ts`) — implements JupyterLite's `BaseKernel` from
32 `@jupyterlite/services`. Before each `execute_request`, `.m` files in the
33 notebook's directory (read via the JupyterLite contents manager) are
34 synced into the numbl session; the cell source then runs against the
35 session's persistent workspace (`createNumblSession` /
36 `session.execute` from `numbl/browser`, a Web Worker that numbl manages).
37 Output streams back as `stream` messages; the run's plot instructions are
38 published as `display_data` with the mime type
39 `application/vnd.numbl.figure+json`.
40- **Figure renderer** (`src/mime.tsx`) — a JupyterLab mime renderer for that
41 mime type: it replays the instructions through numbl's figures reducer and
42 mounts numbl's React `FigureView` (from `numbl/graphics`). Outputs are
43 plain JSON, so saved notebooks re-render wherever the extension is
44 installed.
45- **Kernel registration** (`src/index.ts`) — registers the kernelspec with
46 JupyterLite's `IKernelSpecs`.
48## Build a site with it
50```bash
51pip install jupyterlite-core jupyterlite-numbl-kernel
52jupyter lite build --contents my-notebooks --output-dir dist
53# dist/ is a static site — serve it anywhere
54```
56The `demo/` directory in this repo contains the demo site sources
57(notebooks + requirements); `.github/workflows/deploy.yml` builds and
58deploys it to GitHub Pages.
60### Always-fresh content (demo choice)
62By default JupyterLite copies notebooks into the browser's IndexedDB on
63first visit, and that local copy then wins over the deployed files **even
64after a redeploy** — so returning visitors keep seeing stale content. Since
65this is a demo, `demo/jupyter-lite.json` opts into JupyterLite's in-memory
66storage so every page reload re-seeds the latest deployed notebooks:
68```json
69{
70 "jupyter-config-data": {
71 "enableMemoryStorage": true,
72 "contentsStorageDrivers": ["memoryStorageDriver"],
73 "settingsStorageDrivers": ["memoryStorageDriver"],
74 "workspacesStorageDrivers": ["memoryStorageDriver"]
75 }
76}
77```
79The trade-off is that a visitor's edits live only for the session and are
80discarded on reload. For a real deployment where users should keep their
81work, omit these keys (the default persistent storage) and bump
82`contentsStorageName` when you want to force-refresh shipped content.
83numbl's own package cache (installed via `mip`) lives in a separate
84IndexedDB store and is unaffected, so `mip`-installed packages still
85persist across reloads.
87## Limitations (proof of concept)
89- **No interrupt**: a runaway cell can only be stopped by restarting the
90 kernel (restart works and gives a fresh workspace). Cooperative
91 cancellation exists in numbl but needs `SharedArrayBuffer`, i.e.
92 cross-origin isolation headers, which plain GitHub Pages doesn't set.
93- **No `input()`** (stdin), for the same reason.
94- **Figures are per-cell** (like inline matplotlib): each cell renders the
95 figures its own commands produce; `hold on` does not span cells.
96- **Named function definitions are not supported inside cells** (a numbl
97 REPL limitation) — anonymous functions work; named functions belong in
98 `.m` files next to the notebook (see `demo/content/statsutils.m`), which
99 this kernel syncs into the session automatically.
100- The `.m`-file sync is **one-way**: deleting a `.m` file from the file
101 browser leaves its function defined until the kernel restarts, and files
102 written by cell code (e.g. via `fopen`) don't appear back in the file
103 browser.
104- **uihtml** components render display-only; the MATLAB↔HTML event bridge
105 is not wired into outputs yet.
106- numbl itself is not MATLAB: it covers a large, tested subset of the
107 language and toolbox surface. See the
108 [numbl repo](https://github.com/flatironinstitute/numbl) for scope.
110## Development
112Requires Python ≥ 3.9 and NodeJS ≥ 20, and `numbl >= 0.4.14` on npm (the
113first release with the incremental `session.execute` browser API). To
114develop against an unreleased numbl checkout, run `npm pack` there and
115point the `numbl` dependency at the tarball.
117```bash
118python -m venv .venv && source .venv/bin/activate
119pip install "jupyterlab~=4.6.0" "jupyterlite-core==0.8.1"
121jlpm install
122jlpm build # tsc + labextension (dev)
123pip install -e . # editable install, registers the labextension
125# Build and serve the demo site locally
126pip install -r demo/requirements.txt
127jupyter lite build --lite-dir demo --contents content --output-dir demo/_output
128python -m http.server -d demo/_output 8000
129```
131`jlpm watch` rebuilds on change during development.
133## License
135Apache-2.0. Built on [numbl](https://github.com/flatironinstitute/numbl) and
136the [JupyterLite](https://github.com/jupyterlite/jupyterlite) kernel API;
137scaffolding follows the
138[jupyterlite/echo-kernel](https://github.com/jupyterlite/echo-kernel)
139template.