New library: stlite, a port of Streamlit to Wasm, powered by Pyodide

I understand your point and it is of course a very strong defence in favour of staying with custom components.

On the other hand, however, I can argue that, Isn’t avoiding such a big work of providing seamless interoperability between JS and Python, where you can even run JS methods as Python functions and just have the output without even noticing it’s a different language, a waste of resources? Custom components can of cour still be there and be preferred as the “cross-platform” solution.

Take this code for example: Isn’t it nice? :smiley: Why should we need two (or at least one) files in two/three programming languages, just to know the width of the screen?

P.S. This Shiny issue is also interesting. Apparently, you can switch between webworker and main thread with a URL parameter. Is it difficult for STLite to provide the same config parameter?

Can you create a new GitHub issue for this topic separately and move the discussion there?

1 Like

Hi, I started versioned releases and publishing an NPM package, so now the script can be loaded from the jsDelivr CDN with a version-pinned URL as below.
So I have removed the warning from the README saying stlite might be broken or the API might change without any notice. It never happens now as long as we use the pinned URL.

<!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>
</head>
<body>
  <div id="root"></div>
  <script src="https://cdn.jsdelivr.net/npm/@stlite/mountable@0.1.0/build/stlite.js"></script>
  <!-- 👆👆👆 New URL! 👆👆👆 -->
  <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>

The URL starting with https://whitphx.github.io/stlite/ that has been used in some old examples is now recommended not to use.

Please check out the updated README :smiley:

(BTW, I created a logo ↑ :v:)

1 Like

Multipage apps (MPA) are supported!

The playground app has been updated to be an MPA too.

You can mount multiple files including pages/*.py as below.

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")
);

I am developing the online code editor for stlite.

editor screenshot

I created the dedicated post for it :slight_smile:

1 Like

I created a desktop app toolkit for stlite.
Here is the thread about it :slight_smile:


A sample app repository
Distributable files (the macOS app file is not signed, so the security alert may be shown)

1 Like

This is a great tool! Do you have a list of packages that are compatible with stlite? I’ve tried a few like millify, sklearn and pdfkit but they didn’t work.

Thank you!

In short, please see Packages built in Pyodide — Version 0.21.3


The installable packages are

  1. Pure Python packages (packages that do not include C code).
  2. C extensions compiled for the Pyodide runtime.

The linked page is the list for 2.

There is no complete list for 1.
You can only check the availability of each package by checking if the distributed package file name ends with “py3-none-any.whl”.
For example, semver package is installable because its file name is semver-2.13.0-py2.py3-none-any.whl as found at the PyPI package that ends with “py3-none-any.whl”.

Hello,
thank you for your Work! That is really awsome!

Is it possible to refer to other files or folders?

For example reference the file to start the streamlit app?

Thank you!

For example:

<!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/mountable@0.15.0/build/stlite.css"
    />
  </head>
  <body>
    <div id="root"></div>
    <script src="https://cdn.jsdelivr.net/npm/@stlite/mountable@0.15.0/build/stlite.js"></script>
    <script>
      stlite.mount(
  {
    entrypoint: "streamlit_app.py", // The target file of the `streamlit run` command
    files: {
      "streamlit_app.py": 
      `
#refere to home.py file
import home

#execute main()
home.main()
    `,
    },
  },
  document.getElementById("root")
);
    </script>
  </body>
</html>
1 Like

Just add home.py to the files option in addition to streamlit_app.py like


stlite.mount(
  {
    entrypoint: "streamlit_app.py",
    files: {
"streamlit_app.py": `import home

home.main()
`,
"home.py": `import streamlit as st

def main():
    st.write("Hello")`,

},
  },
  document.getElementById("root")
)

Sample: stlite sharing

The multipage app example can also be a sample with folders: GitHub - whitphx/stlite: Serverless Streamlit

Hi,

Thank you!
I have successfully added a streamlit app to my webpage using stlite according to your example.
But it takes over the entire page. I would prefer to keep my own (for example bootstrap) navbar and add some descriptive text before the app.
Is there an easy way to do this?


<!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/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
  <link rel="stylesheet" href="css/styles.css">
</head>
<body>
    <!-- I would like to add my navbar and other html elements here before the streamlit app  -->
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="collapse navbar-collapse" >
            <div class="navbar-nav">
                <a class="nav-item nav-link active" href="#">Home</a>
                <a class="nav-item nav-link" href="#">Features</a>
            </div>
        </div>
    </nav>
    <div class="container">
        <p>Description</p>
    </div>
    <!-- end of example html elements to add-->

    <div id="root"></div>
    <script src="https://cdn.jsdelivr.net/npm/@stlite/mountable@0.15.0/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>

At this moment, the full-screen behavior is inevitable as it’s an intended design by Streamlit.

One option may be to create the stlite app in another page and embed it to the main page with iframe.
How is it?

Thank you for the reply and suggestion. I will do that :slightly_smiling_face:

Hello,
I confirm that this code is working

import pyodide
raw_data = pd.read_csv(pyodide.open_url(data_url))

Still doesn’t know how to read a non text file like an image or .joblib.
Outstanding work btw!

Thank you.

It seems to be not possible now, sorry.

The download of binary files is not supported.
pyodide.http — Version 0.24.1

One workaround is, if you are using @stlite/mountable like this example on README, to download the file outside stlite and pass it to the files option of stlite.mount() so that the app can access the file on the virtual local file system.

I opened the issue about it: Provide a way to download binary files · Issue #511 · whitphx/stlite · GitHub
So please track it.

Thank you!

1 Like

hi,
is it possible to acces a csv file in a github private repository?
for public repositories, this code actually works

import pyodide
raw_data = pd.read_csv(pyodide.open_url(data_url))

also, i can acces the csv file in a private repo locally using a personal access token, here is the code

import requests 
github_session = requests.Session()
github_session.auth = (username, token)
url = "csv_url"
download = github_session.get(url).content
df = pd.read_csv(io.StringIO(download.decode('utf-8')))

but when trying it with stlite i get a connection error
image

my second question is :
Since the data file and the streamlit page are in the same repo, is it possible to access the files just using a relative path/reference (“data/file.csv”) instead of accesing via web url ?

nb : i am using github entreprise

@s.mounji

@Abdelgha_4
I released v0.29.0, which allows us to use pyodide.http.pyfetch() with top-level awaits as described in the README as GitHub - whitphx/stlite: Serverless Streamlit.

The charts.audio example in the “Component Gallery” sample app actually uses it as below

1 Like

The pyodide.http.pyfetch() solution works perfectly for public urls. Thank you for that!

However for my usecase, I’m hosting the stlite page in a private Github Entreprise repo using Github Pages.
As far as I know, in Github Pages it is possible to use relative links to reference other files, for instance with this structure:

├── some_dir
│   ├── page1.html
│   └── subdir
│       └── page2.html
└── index.html

The following works:

File Link
index.html href=“some_dir/page1.html”
page1.html href=“subdir/page2.html”

I wonder if it is possible to somehow use the same feature to access data stored in the same repo.
That’ll save me from requesting the data from the web url (which is not possible in my case).

@Abdelgha_4 You mean, for example, you want to run code like below from index.html to load ./some_dir/data.txt?

res = await pyfetch("./some_dir/data.txt")
├── some_dir
│   └── data.txt
└── index.html

Looks like it is not possible at least in a straightforward way as pyfetch() doesn’t accept relative paths.
The js module (you can access the JS global scope via js module in Python) and its location attr might be used to get the absolute path from the relative path though (not tested yet).

import js

js.location