A port of Streamlit to WebAssembly, powered by Pyodide.
Streamlit is a Python web app framework for the fast development of data apps. This project is to make it run completely on web browsers.
Visit stlite sharing.
See @stlite/desktop
.
You can use stlite on your web page loading the script and CSS files via <script>
and <link>
tags as below.
Here is a sample HTML file.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<title>stlite app</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@stlite/[email protected]/build/stlite.css"
/>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.jsdelivr.net/npm/@stlite/[email protected]/build/stlite.js"></script>
<script>
stlite.mount(
`
import streamlit as st
name = st.text_input('Your name')
st.write("Hello,", name or "world")
`,
document.getElementById("root")
);
</script>
</body>
</html>
In this sample,
- stlite library is imported with the first script tag, then the global
stlite
object becomes available. stlite.mount()
mounts the Streamlit app on the<div id="root" />
element as specified via the second argument. The app script is passed via the first argument.
If more controls are needed such as installing dependencies or mounting multiple files, use the following API instead.
stlite.mount(
{
requirements: ["matplotlib"], // Packages to install
entrypoint: "streamlit_app.py", // The target file of the `streamlit run` command
files: {
"streamlit_app.py": `
import streamlit as st
import matplotlib.pyplot as plt
import numpy as np
size = st.slider("Sample size", 100, 1000)
arr = np.random.normal(1, 1, size=size)
fig, ax = plt.subplots()
ax.hist(arr, bins=20)
st.pyplot(fig)
`,
},
},
document.getElementById("root")
);
In the example above, the stlite script is loaded via the <script>
tag with the versioned URL.
You can use another version by changing the version number in the URL.
The following URLs are also available, while our recommendation is to use the versioned one as above because the API may change without backward compatibility in future releases.
<script src="https://cdn.jsdelivr.net/npm/@stlite/mountable/build/stlite.js"></script>
You can use the latest version of the published stlite package with this URL.
<script src="https://whitphx.github.io/stlite/lib/mountable/stlite.js"></script>
This URL points to the head of the main branch which is usually ahead of the released packages. However, we strongly recommend NOT to use this URL because this might be broken and there is no guarantee that this resource will be kept available in the future.
You can pass the multiple files to the files
option as below to construct the multipage app structure, the entry point file and pages/*.py
files.
Read the Streamlit official document about the multipage apps.
stlite.mount(
{
entrypoint: "👋_Hello.py",
files: {
"👋_Hello.py": `
import streamlit as st
st.set_page_config(page_title="Hello")
st.title("Main page")
`,
"pages/1_⭐️_Page1.py": `
import streamlit as st
st.set_page_config(page_title="Page1")
st.title("Page 1")
`,
"pages/2_🎈_Page2.py": `
import streamlit as st
st.set_page_config(page_title="Page2")
st.title("Page 2")
`,
},
},
document.getElementById("root")
);
As stlite runs on the web browser environment (Pyodide runtime), there are things not working well. The known issues follow.
st.spinner()
does not work with blocking methods likepyodide.http.open_url()
because stlite runs on a single-threaded environment, sost.spinner()
can't execute its code to start showing the spinner during the blocking method occupies the only event loop.time.sleep()
is no-op. Useasyncio.sleep()
instead. This is a restriction from Pyodide runtime. See pyodide/pyodide#2354. The following section about top-level await may also help to know how to use async functions on stlite.st.experimental_data_editor
does not work as it relies on PyArrow, but it doesn't work on Pyodide. Track this issue on whitphx#509.- For URL access,
urllib
orrequests
don't work on Pyodide/stlite, so we have to use alternative methods provided by Pyodide, such aspyodide.http.pyfetch()
orpyodide.http.open_url()
. See https://pyodide.org/en/stable/usage/faq.html#how-can-i-load-external-files-in-pyodide for the details. Forpyodide.http.pyfetch()
, see also the following section about top-level await. - The C extension packages that are not built for Pyodide cannot be installed. See https://pyodide.org/en/stable/usage/faq.html#micropip-can-t-find-a-pure-python-wheel for the details.
Other problems are tracked at GitHub Issues: https://github.com/whitphx/stlite/issues If you find a new problem, please report it.
TL;DR: use top-level await instead of asyncio.run()
on stlite.
Unlike the original Streamlit, stlite supports top-level await due to the differences in their execution models. Streamlit runs in a standard Python environment, allowing the use of asyncio.run()
when an async function needs to be executed within a script. In contrast, stlite runs in a web browser, operating in an environment where the only event loop is always in a running state. This makes it impossible to use asyncio.run()
within a script, necessitating the support for top-level await.
Top-level await can be useful in various situations.
One of the most common use cases is asyncio.sleep()
. As mentioned in the previous section, time.sleep()
is no-op on stlite because its blocking nature is not compatible with the single-threaded event loop in the web browser environment. Instead, asyncio.sleep()
, which is non-blocking, can be used to pause the execution of a script for a specified amount of time.
You can use top-level await either for asyncio.sleep()
directly or for an async function that contains asyncio.sleep()
like the following:
import asyncio
import streamlit as st
st.write("Hello, world!")
await asyncio.sleep(3)
st.write("Goodbye, world!")
import asyncio
import streamlit as st
async def main():
st.write("Hello, world!")
await asyncio.sleep(3)
st.write("Goodbye, world!")
await main()
Another common use case is accessing external resources. In the Pyodide environment, widely-used URL access methods in Python, like requests
, are not available. However, Pyodide provides pyodide.http.pyfetch()
as an alternative for accessing external resources. Since this method is async, top-level await becomes handy for utilizing pyodide.http.pyfetch()
.
Here's a sample code snippet demonstrating the usage of top-level await with pyodide.http.pyfetch()
:
import pyodide.http
url = "your_url_here"
response = await pyodide.http.pyfetch(url)
data_in_bytes = await response.bytes()
- 📖 Streamlit meets WebAssembly - stlite, by whitphx: A blog post covering from some technical surveys to the usages of the online editor stlite sharing, self-hosting apps, and the desktop app bundler.
- 📺 "Serverless Streamlit OpenCV Python Web App Tutorial", by 1littlecoder, YouTube: A quick tutorial to develop an OpenCV image processing app with stlite that runs completely on browsers.
- 📖 "New library: stlite, a port of Streamlit to Wasm, powered by Pyodide", Streamlit Community: The stlite thread at the Streamlit online forum.
- 📺 "Stlite - Streamlit without Server - powered by Pyodide (WebAssembly)", by 1littlecoder, YouTube: A quick tutorial with local app development and GitHub Pages deployment.
- 📺 "How to Convert a Streamlit App to an .EXE Executable", by Fanilo Andrianasolo, YouTube: A tutorial to convert a Streamlit app to an executable file with stlite and a demo to run it on an offline machine.
- 📖 "Is This the Easiest Way to Build Your Streamlit App?", by Shantala Mukherjee
- 📖 "The Best Python Desktop App Framework?", by Caleb Robey at Depot Analytics
Image processing with OpenCV works on the client-side.
- Repository📌: https://github.com/whitphx/stlite-image-processing-app
- Online demo🎈: https://whitphx.github.io/stlite-image-processing-app/
See the tutorial video
Serverless Streamlit OpenCV Python Web App Tutorial, crafted by 1littlecoder.
They are sponsoring me on GitHub Sponsors!
They are sponsoring me on GitHub Sponsors!