Skip to main content

Run NiceGUI entirely in the browser via Pyodide/PyScript — no server — as an external extension to stock NiceGUI (no fork, no core patches).

Project description

nicegui-pyodide

Run stock NiceGUI entirely in the browser via Pyodide / PyScriptno server, no websocket, no backend — as an external extension.

No fork. No patches to NiceGUI's source. This package makes an unmodified pip install nicegui importable and runnable under Pyodide from the outside, by weaving a set of sys.modules shims in before import nicegui.

This is the extension re-implementation of the upstream Pyodide effort (zauberzeug/nicegui#5776), which required editing ~25 core files. Here that lives entirely in a separate package instead.

Why an extension instead of a PR?

The PR approach had to touch NiceGUI core in ~25 places — wrapping every from fastapi import … / from socketio import … in try/except ImportError and adding if IS_PYODIDE: branches. That is invasive and hard to merge.

Pyodide has no server: there is no uvicorn, no socket.io, and NiceGUI's server-only imports (fastapi, starlette, uvicorn, aiofiles, anyio, ifaddr) simply don't exist. So a plain import nicegui explodes before any of your code runs.

This extension solves it from outside core:

  1. A sys.meta_path finder fabricates a permissive stub module for every import under those absent packages — rich enough (subclassable, callable, decorator-friendly, operator-safe) that stock NiceGUI's unguarded usages don't crash: class App(FastAPI), @app.post(url), class APIRouter(fastapi.APIRouter), class CacheControlledStaticFiles(StaticFiles), Jinja2Templates(path). (nicegui_pyodide/_shims.py)
  2. A lazy nicegui.nicegui seed supplies the app singleton without running stock nicegui/nicegui.py — which would build the FastAPI app + socket.io server + HTTP routes. Stock nicegui/__init__.py does from .nicegui import app; the import machinery finds the seed first.
  3. An in-process JS bridge replaces socket.io: Python emit() calls hop straight to JavaScript over the Pyodide FFI. (bridge.py, outbox.py, runtime.py)
  4. A pyodide-patched nicegui.js (shipped as an asset, pinned to a NiceGUI version) that talks to the bridge instead of a websocket. All new paths are gated on window.niceguiBridge, so the file is backward-compatible with server mode.

On a normal (server) Python this package is inertimport nicegui behaves exactly as usual — so shared code can import it unconditionally.

Usage

pip install git+https://github.com/evnchn/nicegui-pyodide  # pulls in a compatible nicegui; PyPI package coming soon
nicegui-pyodide-build ./dist           # assemble a servable demo dir
python -m http.server -d ./dist 8080   # open http://localhost:8080

Edit dist/app.py to change the UI. The app-building pattern:

import nicegui_pyodide                 # MUST come before `import nicegui`
from nicegui import Client, ui
from nicegui_pyodide import page

with Client(page('/')) as client:
    ui.label('Hello from the browser!')
    ui.button('Click me', on_click=lambda: ui.notify('Clicked!'))
# the generated entrypoint mounts `client` via PyodideRuntime

Element support matrix

Most NiceGUI elements are pure client-side Vue and work unchanged. A handful assume a live server (a socket.io connection or a component static route) and therefore need special handling in the browser. Status against the pinned NiceGUI release:

Element Status Notes
labels, buttons, inputs, layout, tables, most Quasar wrappers ✅ works pure client-side; no server assumption
ui.markdown (incl. Mermaid, code highlighting) ✅ works (override) codehilite CSS served from ./dynamic_resources/ instead of a server route
ui.scene / ui.scene_view (3D / WebGL) ✅ works (shim) the init handshake polled window.socket.id; a minimal window.socket stand-in is provided in Pyodide mode. Browser-verified (WebGL canvas renders)
ui.leaflet (map) ✅ works (override) same init shim, plus CSS/JS (leaflet, optional leaflet-draw) resolved from ./esm/nicegui-leaflet/. Browser-verified (map + tiles render)
ui.xterm (terminal) ✅ works (override) xterm.css resolved from ./esm/nicegui-xterm/ instead of a server route. Browser-verified (terminal renders)
ui.echart with a theme URL string ⚠️ partial inline theme objects work; a theme passed as a URL fetch()es a server path — it fails soft (theme just doesn't apply). Use an inline theme object
ui.image / ui.interactive_image / ui.video / ui.audio / ui.link with a root-relative src/href (e.g. /foo.png) ⚠️ partial such paths were app.add_static_files / uploaded-file routes with no backend here. Use absolute URLs, data:/blob: URIs, or bundle the asset. External URLs are unaffected
@ui.page server routes, app.add_static_files, HTTP endpoints, native mode, multiprocessing ❌ unsupported require a real backend; no-op or unavailable in the browser (use the Client(page(...)) pattern shown above)

The overridden/shimmed elements (scene, leaflet, xterm) are the socket.io/handshake and static-route cases that would otherwise fail with an opaque TypeError (window.socket is undefined) or a silent missing-stylesheet; all three are covered by the browser smoke test. Genuinely server-bound features (last row) are documented as unsupported rather than patched.

Version pinning

nicegui_pyodide/static/nicegui.js is a pyodide-patched copy of a specific NiceGUI release's nicegui.js. The nicegui>=3.14,<3.15 pin in pyproject.toml keeps them in lockstep. To support a newer NiceGUI, regenerate the patched JS against that release and widen the pin.

Status / limitations

  • Verified in-browser (Chromium via Playwright): render, button click → handler → DOM update, ui.notify, input round-trip through the bridge, ui.upload round-trip, on_connect handlers, and app.storage.tab.
  • File upload works client-side: files are read in the browser (FileReader → base64) and delivered over the bridge to runtime._handle_upload (no server route).
  • @ui.page server routes are not available (there is no server); use the Client(page(...)) pattern shown above.
  • Anything needing a real backend (HTTP endpoints, app.add_static_files, multiprocessing, native mode) is a no-op or unavailable in the browser.

License & attribution

This project is MIT-licensed (see LICENSE), Copyright (c) 2026 evnchn.

It bundles modified copies of a few files from NiceGUI (MIT, © Zauberzeug GmbH): nicegui_pyodide/static/nicegui.js and the component overrides under nicegui_pyodide/static/component_overrides/ (markdown.js, leaflet/leaflet.js, xterm/xterm.js). See NOTICE for details. Other vendor assets (Quasar, Vue, Tailwind, …) are copied from your own installed NiceGUI at build time and are not redistributed in this repository.

The Pyodide mechanism re-implemented here originated in the author's own upstream PR, zauberzeug/nicegui#5776 (closed).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nicegui_pyodide-0.1.0.tar.gz (41.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nicegui_pyodide-0.1.0-py3-none-any.whl (41.8 kB view details)

Uploaded Python 3

File details

Details for the file nicegui_pyodide-0.1.0.tar.gz.

File metadata

  • Download URL: nicegui_pyodide-0.1.0.tar.gz
  • Upload date:
  • Size: 41.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for nicegui_pyodide-0.1.0.tar.gz
Algorithm Hash digest
SHA256 cb57ba76e8681b48139044a7d2e51f08a694b7efd6c7eee760576ef56bdc9a6c
MD5 879a545c774fcc78e06e2752af29a40f
BLAKE2b-256 63c8a0258f50c14303a3e1bb95fd7ec183f69a9a4e1e66a77b4f9e16d4599978

See more details on using hashes here.

File details

Details for the file nicegui_pyodide-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for nicegui_pyodide-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e1dfa5d8965968753c990f327484a97e96f7a6a6aff49d08897e3c16c2e36b08
MD5 97b16bdedc773ceae190be7027cfd4ff
BLAKE2b-256 4b8ca02bc7897f86a4c75a9e9fd7e9997e60723069ad100acb14a1f7d0ddffda

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page