Background
On 11 August 2026 QuantStack announced Numba in the Browser (Anutosh Bhat): a genuine Numba JIT running entirely in the browser. Python functions are compiled through llvmlite to WebAssembly, linked in-process with LLD, and loaded as Emscripten side modules into a JupyterLite kernel — no server anywhere. Numba is now published on emscripten-forge (numba 0.67.0 + 9 emscripten patches; llvmlite 0.49.0 + 4 patches), and the announcement showcases the economics stack built on top of it (EconForge's interpolation.py and Dolo.py, PyMC/PyTensor). QuantEcon.py is the obvious next citizen of that ecosystem, and its authors explicitly invite the wider Numba ecosystem to validate their packages.
Concretely, "browser support" for QuantEcon.py means: works in JupyterLite with the xeus-python kernel against the emscripten-forge + conda-forge channels. This is not the classic Pyodide kernel — Numba remains unavailable there (see numba/numba#3284, pyodide/pyodide-recipes#192).
Where we stand
The starting position is strong. QuantEcon.py is pure Python (noarch on conda-forge, universal wheel), every hard dependency (numba, numpy, scipy, sympy) is already packaged for the browser, and all ~90 jitted callables are nopython-mode with lazy specialisation — exactly the model the WASM engine implements. The library's widespread cache=True discipline becomes an outright asset under JupyterLite's new persistent cross-session compilation cache. The requests, urllib, and sympy imports are already lazy, so import quantecon touches nothing browser-hostile.
The constraints of the in-browser Numba build, read from the nine emscripten-forge patches and the recipe's tests:
- The runtime is single-threaded (OpenMP and TBB disabled at build time).
target='parallel' ufuncs silently fall back to 'cpu' on sys.platform == "emscripten" (patch 0007), but no equivalent fallback exists for @njit(parallel=True) (the ParallelAccelerator pass) — that path is expected to fail.
@njit(cache=True) gains a persistent WASM object cache across browser sessions (patch 0006), but cache=True on @vectorize/@guvectorize is force-disabled on Emscripten — ufuncs recompile every session.
- wasm32 is a 32-bit platform: pointers,
np.intp, and NumPy's default integer are 32 bits; addressable memory is capped at 2–4 GB shared with the whole kernel.
Gaps
| # |
Gap |
Severity |
| 1 |
gini_coefficient uses @njit(parallel=True) + prange; no browser fallback exists, so it is expected to fail at first call |
Blocker (one function) |
| 2 |
_numba_linalg_solve calls LAPACK by raw function address via the private numba.np.linalg._LAPACK API; unverified in WASM; affects game_theory.support_enumeration |
High risk / unverified |
| 3 |
import quantecon eagerly compiles four guvectorize ufuncs plus one eagerly-signed @jit function; ufunc caching is disabled on Emscripten so the cost recurs every session |
UX / performance |
| 4 |
32-bit intp on wasm32: overflow guards trip ~4×10⁹ times earlier, simplex_index can silently wrap, int64 dtype assertions fail |
Correctness at the margins |
Work plan
| Phase |
Issue |
Work item |
| 0 — Prove it |
#928 |
JupyterLite proof-of-concept deployment + browser smoke suite |
| 1 — Code fixes |
#926 |
Gate @njit(parallel=True) in gini_coefficient on Emscripten |
| 1 |
#927 |
Verify _numba_linalg_solve / _LAPACK in the browser; fallback solver if needed |
| 1 |
#929 |
Audit 32-bit intp behaviour: overflow guards, simplex_index wrapping, dtype-agnostic tests |
| 1 |
#930 |
Reduce import-time eager Numba compilation |
| 1 |
#931 |
Document browser limitations; raise helpful platform-specific errors |
| 2 — Distribution |
#932 |
Contribute a quantecon recipe to emscripten-forge |
| 3 — CI |
#933 |
Add a WebAssembly/JupyterLite job to CI |
| 4 — Ecosystem |
#934 |
Demo notebook, lectures without a kernel server, WASM SIMD exploration |
Sequencing: Phase 0 comes first and converts every unknown into a known — #927 is gated on its findings, and #930 is calibrated by its import-time benchmark. #926 is independent and can land immediately. #932 is best contributed after the Phase 1 fixes are in a released version.
What does not need to change
No C extensions, no build step, no subprocess/multiprocessing/threading anywhere in the runtime package, no object-mode @jit, no @jitclass, no generated_jit, no threading-layer APIs. All five runtime dependencies resolve in the browser ecosystem today, and the test tooling (pytest, pandas, coverage) is itself packaged for WASM, so browser CI is a configuration exercise rather than an infrastructure project.
Sources
Background
On 11 August 2026 QuantStack announced Numba in the Browser (Anutosh Bhat): a genuine Numba JIT running entirely in the browser. Python functions are compiled through llvmlite to WebAssembly, linked in-process with LLD, and loaded as Emscripten side modules into a JupyterLite kernel — no server anywhere. Numba is now published on emscripten-forge (numba 0.67.0 + 9 emscripten patches; llvmlite 0.49.0 + 4 patches), and the announcement showcases the economics stack built on top of it (EconForge's interpolation.py and Dolo.py, PyMC/PyTensor). QuantEcon.py is the obvious next citizen of that ecosystem, and its authors explicitly invite the wider Numba ecosystem to validate their packages.
Concretely, "browser support" for QuantEcon.py means: works in JupyterLite with the xeus-python kernel against the emscripten-forge + conda-forge channels. This is not the classic Pyodide kernel — Numba remains unavailable there (see numba/numba#3284, pyodide/pyodide-recipes#192).
Where we stand
The starting position is strong. QuantEcon.py is pure Python (
noarchon conda-forge, universal wheel), every hard dependency (numba, numpy, scipy, sympy) is already packaged for the browser, and all ~90 jitted callables are nopython-mode with lazy specialisation — exactly the model the WASM engine implements. The library's widespreadcache=Truediscipline becomes an outright asset under JupyterLite's new persistent cross-session compilation cache. Therequests,urllib, andsympyimports are already lazy, soimport quantecontouches nothing browser-hostile.The constraints of the in-browser Numba build, read from the nine emscripten-forge patches and the recipe's tests:
target='parallel'ufuncs silently fall back to'cpu'onsys.platform == "emscripten"(patch 0007), but no equivalent fallback exists for@njit(parallel=True)(the ParallelAccelerator pass) — that path is expected to fail.@njit(cache=True)gains a persistent WASM object cache across browser sessions (patch 0006), butcache=Trueon@vectorize/@guvectorizeis force-disabled on Emscripten — ufuncs recompile every session.np.intp, and NumPy's default integer are 32 bits; addressable memory is capped at 2–4 GB shared with the whole kernel.Gaps
gini_coefficientuses@njit(parallel=True)+prange; no browser fallback exists, so it is expected to fail at first call_numba_linalg_solvecalls LAPACK by raw function address via the privatenumba.np.linalg._LAPACKAPI; unverified in WASM; affectsgame_theory.support_enumerationimport quanteconeagerly compiles fourguvectorizeufuncs plus one eagerly-signed@jitfunction; ufunc caching is disabled on Emscripten so the cost recurs every sessionintpon wasm32: overflow guards trip ~4×10⁹ times earlier,simplex_indexcan silently wrap, int64 dtype assertions failWork plan
@njit(parallel=True)ingini_coefficienton Emscripten_numba_linalg_solve/_LAPACKin the browser; fallback solver if neededintpbehaviour: overflow guards,simplex_indexwrapping, dtype-agnostic testsquanteconrecipe to emscripten-forgeSequencing: Phase 0 comes first and converts every unknown into a known — #927 is gated on its findings, and #930 is calibrated by its import-time benchmark. #926 is independent and can land immediately. #932 is best contributed after the Phase 1 fixes are in a released version.
What does not need to change
No C extensions, no build step, no subprocess/multiprocessing/threading anywhere in the runtime package, no object-mode
@jit, no@jitclass, nogenerated_jit, no threading-layer APIs. All five runtime dependencies resolve in the browser ecosystem today, and the test tooling (pytest, pandas, coverage) is itself packaged for WASM, so browser CI is a configuration exercise rather than an infrastructure project.Sources
recipes_emscripten/{numba,llvmlite,scipy,sympy,pytensor-base}/, especially numba patches 0006 (persistent WASM cache) and 0007 (parallel-ufunc fallback)main@ 13b436b (13 Aug 2026); file references are in the sub-issues