Nitro v0.11 shipped with Web Assembly support
Nitro 0.11 can run apps entirely in-browser.
What does this mean?
Ordinarily, your Nitro app runs on a server (or cloud) somewhere, and displays interactive user interfaces in your web browser:
Network
│ Python
│ ┌──────────────────┐
│ │ Nitro │
│ │ ┌──────────────┐ │
│ │ │ │ │
│ │ │ App │ │
│ │ │ │ │
│ │ └─────▲─┬──────┘ │
Browser │ │ │ │ │
┌───────────┐ │ │ │ │ │
│ │ │ │ ┌─────┴─▼──────┐ │
│ │ I/O │ │ │ │ │
│ UI └─────────┼───────────┼─► Flask │ │
│ ◄─────────┼───────────┼─┐ │ │
│ │ │ │ └──────────────┘ │
└───────────┘ │ │ │
│ └──────────────────┘
│
With Nitro 0.11, you can put your Python code (and packages) on a static website somewhere (like Github Pages or S3 static websites), and have it load and execute entirely inside a web browser:
Network
│
Python │
┌──────────────────┐ │
│ Nitro │ │
│ ┌──────────────┐ │ │
│ │ │ │ │
│ │ App │ │ │ Web Server
│ │ │ │ │ ┌──────────────┐
│ └─────▲─┬──────┘ │ │ │ │
Browser │ │ │ ◄─────┼───┤ Static Files │
┌───────────┐ │ │ │ │ │ │ │
│ │ │ ┌─────┴─┴──────┐ │ │ └──────────────┘
│ │ │ │ │ │ │
│ UI └────┼─► Nitride │ │ │
│ ◄────┼─┐ │ │ │
│ │ │ └──────────────┘ │ │
└───────────┘ │ │ │
└──────────────────┘ │
│
How is this possible?
Nitro 0.11 introduces an application runtime called Nitride, built atop Pyodide, a port of CPython to Web Assembly.
Nitride is a tiny (~5KB) layer that makes it possible to spawn a Python process in a Web Worker, which then controls the UI. The UI then continues to operate normally, assuming it’s communicating with a Nitro server over the network. In reality, the Nitro “server” is simply running locally on a separate operating system thread.
What kind of apps is this useful for?
Running apps this way is useful only if:
- You don’t want to host your Python app in the cloud (and deal with everything else related to hosting, including monitoring, uptime, etc.)
- Your app doesn’t need heavy compute, and you’re fine with whatever processing power is available on your user’s computer.
What does it look like in practice?
Broadly, there are two ways to run your app:
- Embed your code directly in HTML using a
<script type="text/python">tag. - (Recommended) Provide a configuration in a
<script type="application/nitro">tag.
Embedding Python code directly in HTML looks like this:
<script type="text/python">
from h2o_nitro import AsyncView as View, box
async def main(view: View):
name = await view(box('What is your name?', value='Boaty McBoatface'))
feel = await view(box(f'How do you feel today, {name}?', value='intrigued'))
await view(f'What a coincidence, {name}, I feel {feel}, too!')
nitro = View(main, title='Hello Nitro!', caption='v1.0')
</script>
Although the above technique works, it’s more convenient to write programs in .py modules and load them dynamically using a YAML configuration that describes your app:
<script type="application/nitro">
language: python
entrypoint: example_hello.py
</script>
You can also load external packages, wheel files, and modules dynamically, like this:
<script type="application/nitro">
language: python
packages:
- numpy
- pandas
- bokeh
bundles:
- h2o-nitro-bokeh
files:
- example_bokeh_util.py
entrypoint: example_bokeh.py
</script>
For more comprehensive documentation, see https://nitro.h2o.ai/reference/wasm/.
Summary
The primary reason I added support for Web Assembly is to make our interactive docs run in-browser, and those docs are a lot more interesting to play with than the static documentation :)
Also, if you’re interested in things like PyScript, Nitro offers a “batteries-included” approach - a huge variety of interactive components that you can snap together quickly and build applications, instead of using Python as a substitute for Javascript and futzing around with the browser DOM. Anything you can do with PyScript, you can do with Nitro, too!
Happy hacking!