Add Pyodide/PyScript support for running NiceGUI in the browser#5776
Add Pyodide/PyScript support for running NiceGUI in the browser#5776evnchn wants to merge 3 commits intozauberzeug:mainfrom
Conversation
|
@falkoschindler I have a contender for top 3 most interesting PR. |
Enable NiceGUI apps to run fully client-side using Pyodide (Python compiled to WebAssembly). No server is required — the app loads as a static page. Core changes: - Add nicegui/pyodide/ package: PyodideBridge (replaces socket.io), PyodideOutbox (microtask-based auto-flush), PyodideRuntime (mount/render) - Add nicegui/pyodide_compat.py and nicegui/page_pyodide.py for Pyodide environment detection and page configuration - Guard server-only imports (FastAPI, Starlette, uvicorn, aiofiles) behind IS_PYODIDE checks across ~20 core modules - Extend nicegui.js with Pyodide bridge mode: handleMessage dispatcher, createNiceGUIApp() global, event/JS-response routing via window.niceguiBridge - Add factory-based file upload in upload.js for client-side file reading (FileReader + base64) instead of HTTP POST - Add Pyodide-aware CSS loading in markdown.js for codehilite resources Example & tooling: - examples/pyodide/: full SPA demo with sub-page routing, data binding, timers, forms, markdown, mermaid diagrams, file upload/download, dark mode - prepare.py: copies static assets, components, ESM bundles; generates import maps; builds stripped wheel (19MB → 330KB) - test_pyodide.py: comprehensive Playwright test suite (22 checks, 0 errors) - test_pyodide_import.py: fast offline import chain validation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
a41fe43 to
11aa818
Compare
|
I think the discussion with @rodja boils down to: HMW incorporate client-server communication, such that client responsible for interactivity, while backend responsible only for compute. If we pull it off, it'd be an amazing feat. |
|
Crazy! And a bit scary. 🤯 |
|
I think we should handle this very carefully. For this new feature, I got tests in |
|
Very interesting. I think this could open up some cool new use cases. |
|
Exactly. I also think this could make running NiceGUI on embedded device possible. An ESP32, or something along those lines. Nevertheless, the undertaking is huge, and we best move carefully. @nistvan86 if you want to start using it, feel free to install NiceGUI to this fork. You are more than welcome to submit changes and I will pull them in manually. |
|
I'm not familiar with how Pyodide works. I see that there are some micropip installs happening in the code . Is it possible to precompile the full application so it can be hosted on a network where no internet connection is available? |
|
@nistvan86 The example currently compiles everything so that the browser only needs the files served by the HTTP server. |
Motivation
NiceGUI currently requires a Python server (FastAPI/Starlette + socket.io) to serve the UI. This PR adds an alternative Pyodide/PyScript runtime that runs NiceGUI entirely in the browser via WebAssembly — no server needed. This enables:
.whlfileImplementation
Architecture: A
PyodideRuntimeclass replaces server-side socket.io with a direct Python↔JS bridge via Pyodide's FFI. The runtime:PyodideBridge(nicegui/pyodide/bridge.py) — Replacessocketio.AsyncServer. Allemit()calls forward towindow.niceguiBridge.handleMessage()in JS. Binary data is base64-encoded since there's no WebSocket binary framing.PyodideOutbox(nicegui/pyodide/outbox_pyodide.py) — Replaces the server outbox's background flush loop with a microtask-based auto-flush. Whenever updates are enqueued, a one-shot flush is scheduled viabackground_tasks.create()so timers,await run_javascript(), and background tasks all work without explicit flush calls.PyodideRuntime(nicegui/pyodide/runtime.py) — Main entry point. Sets up the bridge, outbox, event handlers (events, JS responses, uploads), collects Vue component URLs, and callswindow.createNiceGUIApp()to mount the Vue app.page_pyodide.page(nicegui/page_pyodide.py) — Lightweight page stub that stores configuration without registering FastAPI routes.JS-side changes (
nicegui/static/nicegui.js) —createNiceGUIApp()now accepts components/config JSON and loads Vue component JS before mounting. AniceguiBridgeobject handles Python↔JS message dispatch.Conditional imports — Modules that import FastAPI, uvicorn, socket.io, etc. are guarded with
pyodide_compat.is_pyodide()checks to avoid import errors in the browser.Demo app (
examples/pyodide/):run_javascript, notifications, async tasks, refreshable, form elements, markdown, tables, tabs, dialogs, mermaid diagrams, file upload/downloadprepare.py— build script that creates a stripped wheel, copies component JS/ESM bundles, fonts, and generates import mapstest_pyodide.py— 22 automated Playwright tests covering all demo featuresOther changes:
upload.js— Pyodide factory reads files client-side and sends base64 data via bridge instead of HTTP POSTinput.js— Guard against missing element inbeforeUnmount(SPA navigation edge case)index.htmlmatches the server-side template (@layerordering for Quasar, Tailwind, NiceGUI)Progress