Skip to content

Add Pyodide/PyScript support for running NiceGUI in the browser#5776

Draft
evnchn wants to merge 3 commits intozauberzeug:mainfrom
evnchn:pyodide-support-v2
Draft

Add Pyodide/PyScript support for running NiceGUI in the browser#5776
evnchn wants to merge 3 commits intozauberzeug:mainfrom
evnchn:pyodide-support-v2

Conversation

@evnchn
Copy link
Copy Markdown
Collaborator

@evnchn evnchn commented Feb 13, 2026

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:

  • Static hosting (GitHub Pages, S3, CDN) — just HTML + JS + a .whl file
  • Offline apps — works without internet after initial load
  • Demos & playgrounds — shareable single-page NiceGUI examples
  • Edge computing — run NiceGUI where there's no Python backend

Implementation

Architecture: A PyodideRuntime class replaces server-side socket.io with a direct Python↔JS bridge via Pyodide's FFI. The runtime:

  1. PyodideBridge (nicegui/pyodide/bridge.py) — Replaces socketio.AsyncServer. All emit() calls forward to window.niceguiBridge.handleMessage() in JS. Binary data is base64-encoded since there's no WebSocket binary framing.

  2. 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 via background_tasks.create() so timers, await run_javascript(), and background tasks all work without explicit flush calls.

  3. PyodideRuntime (nicegui/pyodide/runtime.py) — Main entry point. Sets up the bridge, outbox, event handlers (events, JS responses, uploads), collects Vue component URLs, and calls window.createNiceGUIApp() to mount the Vue app.

  4. page_pyodide.page (nicegui/page_pyodide.py) — Lightweight page stub that stores configuration without registering FastAPI routes.

  5. JS-side changes (nicegui/static/nicegui.js) — createNiceGUIApp() now accepts components/config JSON and loads Vue component JS before mounting. A niceguiBridge object handles Python↔JS message dispatch.

  6. 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/):

  • Multi-page SPA with sub-pages (home, basics, forms, content, I/O)
  • Covers: data binding, timers, run_javascript, notifications, async tasks, refreshable, form elements, markdown, tables, tabs, dialogs, mermaid diagrams, file upload/download
  • prepare.py — build script that creates a stripped wheel, copies component JS/ESM bundles, fonts, and generates import maps
  • test_pyodide.py — 22 automated Playwright tests covering all demo features

Other changes:

  • upload.js — Pyodide factory reads files client-side and sends base64 data via bridge instead of HTTP POST
  • input.js — Guard against missing element in beforeUnmount (SPA navigation edge case)
  • CSS layering in index.html matches the server-side template (@layer ordering for Quasar, Tailwind, NiceGUI)

Progress

  • I chose a meaningful title that completes the sentence: "If applied, this PR will..."
  • The implementation is complete.
  • If this PR addresses a security issue, it has been coordinated via the security advisory process.
  • Pytests have been added (or are not necessary).
  • Documentation has been added (or is not necessary).

@evnchn
Copy link
Copy Markdown
Collaborator Author

evnchn commented Feb 13, 2026

@falkoschindler I have a contender for top 3 most interesting PR.

@evnchn evnchn added the feature Type/scope: New or intentionally changed behavior label Feb 13, 2026
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>
@evnchn evnchn force-pushed the pyodide-support-v2 branch from a41fe43 to 11aa818 Compare February 13, 2026 17:14
@evnchn
Copy link
Copy Markdown
Collaborator Author

evnchn commented Feb 13, 2026

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.

@falkoschindler falkoschindler added the in progress Status: Someone is working on it label Feb 13, 2026
@falkoschindler
Copy link
Copy Markdown
Contributor

Crazy! And a bit scary. 🤯
I hope we don't loose control over all dimensions we opened. Right now we don't even have tests for ui.run_with or script mode, let alone native mode. Pyodide brings another world to the table. If it works, it's definitely an amazing feature. But we shouldn't risk breaking any core functionality or maintainability.

@evnchn
Copy link
Copy Markdown
Collaborator Author

evnchn commented Feb 14, 2026

I think we should handle this very carefully.

For this new feature, I got tests in tesy_pyodide.py, but its completeness is uncertain.

@nistvan86
Copy link
Copy Markdown

Very interesting. I think this could open up some cool new use cases.
eg. using NiceGUI to create PWA apps which when online can fetch data from servers, but otherwise work self-contained with local data only.
But this optional network connectivity would also support better scalability, as it doesn't require a peristent websocket connection to a real server just for UI event handling.

@evnchn
Copy link
Copy Markdown
Collaborator Author

evnchn commented Mar 13, 2026

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.

@nistvan86
Copy link
Copy Markdown

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?

@evnchn
Copy link
Copy Markdown
Collaborator Author

evnchn commented Mar 13, 2026

@nistvan86 The example currently compiles everything so that the browser only needs the files served by the HTTP server.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Type/scope: New or intentionally changed behavior in progress Status: Someone is working on it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants