{"id":13469152,"url":"https://github.com/whitphx/stlite","last_synced_at":"2026-04-22T06:07:53.441Z","repository":{"id":37984294,"uuid":"492128644","full_name":"whitphx/stlite","owner":"whitphx","description":"In-browser Streamlit 🎈🚀","archived":false,"fork":false,"pushed_at":"2025-04-25T09:39:59.000Z","size":1179195,"stargazers_count":1380,"open_issues_count":80,"forks_count":74,"subscribers_count":13,"default_branch":"main","last_synced_at":"2025-04-25T10:43:41.605Z","etag":null,"topics":["hacktoberfest","in-browser","pyodide","python","serverless","streamlit","wasm","web-assembly","webassembly"],"latest_commit_sha":null,"homepage":"https://edit.share.stlite.net","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/whitphx.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["whitphx"],"patreon":"whitphx","open_collective":null,"ko_fi":"whitphx","tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":["https://www.buymeacoffee.com/whitphx"]}},"created_at":"2022-05-14T06:10:07.000Z","updated_at":"2025-04-25T03:37:24.000Z","dependencies_parsed_at":"2023-10-11T07:01:26.214Z","dependency_job_id":"099db6dd-3079-467a-b314-fc044661278c","html_url":"https://github.com/whitphx/stlite","commit_stats":{"total_commits":1173,"total_committers":9,"mean_commits":"130.33333333333334","dds":0.1159420289855072,"last_synced_commit":"54524e7a92cac2c722f2bc16c4a1634cab1882e4"},"previous_names":[],"tags_count":213,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whitphx%2Fstlite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whitphx%2Fstlite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whitphx%2Fstlite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/whitphx%2Fstlite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/whitphx","download_url":"https://codeload.github.com/whitphx/stlite/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254049574,"owners_count":22006089,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest","in-browser","pyodide","python","serverless","streamlit","wasm","web-assembly","webassembly"],"created_at":"2024-07-31T15:01:27.974Z","updated_at":"2026-04-02T10:41:58.309Z","avatar_url":"https://github.com/whitphx.png","language":"TypeScript","readme":"# Stlite: In-browser Streamlit\n\n**Serverless [Streamlit](https://streamlit.io/) Running Entirely in Your Browser**\n\n[![Test and Build](https://github.com/whitphx/stlite/actions/workflows/test-build.yml/badge.svg)](https://github.com/whitphx/stlite/actions/workflows/test-build.yml)\n[![Postbuild](https://github.com/whitphx/stlite/actions/workflows/postbuild.yml/badge.svg)](https://github.com/whitphx/stlite/actions/workflows/postbuild.yml)\nThis project is tested with BrowserStack.\n\n[![npm (@stlite/browser)](https://img.shields.io/npm/v/@stlite/browser?label=%40stlite%2Fbrowser)](https://www.npmjs.com/package/@stlite/browser)\n[![npm (@stlite/desktop)](https://img.shields.io/npm/v/@stlite/desktop?label=%40stlite%2Fdesktop)](https://www.npmjs.com/package/@stlite/desktop)\n[![npm (@stlite/react)](https://img.shields.io/npm/v/@stlite/react?label=%40stlite%2Freact)](https://www.npmjs.com/package/@stlite/react)\n\n\u003ca href=\"https://flatt.tech/oss/gmo/trampoline\" target=\"_blank\"\u003e\u003cimg src=\"https://flatt.tech/assets/images/badges/gmo-oss.svg\" height=\"20px\"/\u003e\u003c/a\u003e\n\n\u003cimg src=\"./docs/src/assets/logo-themed.svg\" \u003e\n\nStreamlit is a Python web app framework for the fast development of data apps. This project is to make it run completely on web browsers with the power of [Pyodide](https://pyodide.org/), WebAssembly (Wasm)-ported Python.\n\n## Try it out online (_Stlite Sharing_)\n\nVisit [Stlite Sharing](https://edit.share.stlite.net/).\n\n[\u003cimg src=\"https://edit.share.stlite.net/ogp.png\" height=\"180\" \u003e](https://edit.share.stlite.net/)\n\n## Create a desktop app (`@stlite/desktop`)\n\nSee [`@stlite/desktop`](./packages/desktop/README.md).\n\n## Integrate _Stlite_ into your React app (`@stlite/react`)\n\nSee [`@stlite/react`](./packages/react/README.md).\n\n## Use _Stlite_ on your web page (`@stlite/browser`)\n\n\u003e [!NOTE]\n\u003e Since 0.76.0, `@stlite/mountable` is renamed to `@stlite/browser`, and the API is changed. See the [Migration guide](./CHANGELOG.md#how-to-migrate-from-stlitemountable-to-stlitebrowser) for the details.\n\nYou can use _Stlite_ on your web page loading the script and CSS files via `\u003cscript\u003e` and `\u003clink\u003e` tags as below.\nHere is a sample HTML file.\n\n```html\n\u003c!doctype html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"UTF-8\" /\u003e\n    \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" /\u003e\n    \u003cmeta\n      name=\"viewport\"\n      content=\"width=device-width, initial-scale=1, shrink-to-fit=no\"\n    /\u003e\n    \u003ctitle\u003eStlite App\u003c/title\u003e\n    \u003clink\n      rel=\"stylesheet\"\n      href=\"https://cdn.jsdelivr.net/npm/@stlite/browser@0.85.1/build/stlite.css\"\n    /\u003e\n    \u003cscript\n      type=\"module\"\n      src=\"https://cdn.jsdelivr.net/npm/@stlite/browser@0.85.1/build/stlite.js\"\n    \u003e\u003c/script\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cstreamlit-app\u003e\n      import streamlit as st\n\n      name = st.text_input('Your name')\n      st.write(\"Hello,\", name or \"world\")\n    \u003c/streamlit-app\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nIn this sample,\n\n- _Stlite_ is set up by loading the JavaScript and CSS files via `\u003cscript\u003e` and `\u003clink\u003e` tags.\n- The _Stlite_ runtime recognizes the `\u003cstreamlit-app\u003e` tag and launches the Streamlit app defined in it.\n\n---\n\n_Stlite_ also provides the \"more raw\" API with which you call the `mount()` JavaScript function explicitly to mount a Streamlit app on a specific element in the DOM.\n\n```html\n\u003c!doctype html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"UTF-8\" /\u003e\n    \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" /\u003e\n    \u003cmeta\n      name=\"viewport\"\n      content=\"width=device-width, initial-scale=1, shrink-to-fit=no\"\n    /\u003e\n    \u003ctitle\u003eStlite App\u003c/title\u003e\n    \u003clink\n      rel=\"stylesheet\"\n      href=\"https://cdn.jsdelivr.net/npm/@stlite/browser@0.83.0/build/stlite.css\"\n    /\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cdiv id=\"root\"\u003e\u003c/div\u003e\n    \u003cscript type=\"module\"\u003e\n      import { mount } from \"https://cdn.jsdelivr.net/npm/@stlite/browser@0.83.0/build/stlite.js\";\n      mount(\n        `\nimport streamlit as st\n\nname = st.text_input('Your name')\nst.write(\"Hello,\", name or \"world\")\n`,\n        document.getElementById(\"root\"),\n      );\n    \u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nIn this sample,\n\n- _Stlite_'s `mount()` function is imported within the `\u003cscript\u003e` tag. You also need to load the CSS file via `\u003clink\u003e` tag as well.\n- `mount()` mounts the Streamlit app on the `\u003cdiv id=\"root\" /\u003e` element as specified via the second argument. The app script is passed via the first argument.\n\n\u003e [!NOTE]\n\u003e If you use backticks `` ` `` inside your Streamlit code (e.g. writing markdown with code blocks), they may conflict with JavaScript string literal's backtick like ``st.mount(` ... `)``. To avoid it, you can escape them with with a preceding backslash `\\`.\n\u003e This issue doesn't occur when you use the `\u003cstreamlit-app\u003e` tag.\n\u003e\n\u003e ```js\n\u003e mount(\n\u003e   `\n\u003e import streamlit as st\n\u003e\n\u003e st.markdown(\"This is an inline code format: \\`code\\`\")\n\u003e `,\n\u003e   document.getElementById(\"root\"),\n\u003e );\n\u003e ```\n\nHint: Technically, the `\u003cstreamlit-app\u003e` tag API is a wrapper around the `mount()` function.\n\n### More controls\n\nIf you need to do more such as\n\n- mounting multiple files\n- installing dependencies\n- setting the Streamlit config via the `config.toml` file\n\nyou can use the `\u003cstreamlit-app\u003e` tag with the `\u003capp-file\u003e`, and `\u003capp-requirements\u003e` tags as below.\n\n```html\n\u003cstreamlit-app\u003e\n  \u003capp-file name=\"streamlit_app.py\" entrypoint\u003e\n    import streamlit as st\n    import matplotlib.pyplot as plt\n    import numpy as np\n\n    size = st.slider(\"Sample size\", 100, 1000)\n    arr = np.random.normal(1, 1, size=size)\n    fig, ax = plt.subplots()\n    ax.hist(arr, bins=20)\n    st.pyplot(fig)\n  \u003c/app-file\u003e\n  \u003capp-file name=\".streamlit/config.toml\"\u003e\n    [client]\n    toolbarMode = \"viewer\"\n  \u003c/app-file\u003e\n  \u003capp-requirements\u003e\n    matplotlib\n  \u003c/app-requirements\u003e\n\u003c/streamlit-app\u003e\n```\n\n---\n\nIf you want to use the `mount()` function instead, it would look like this:\n\n```js\nmount(\n  {\n    requirements: [\"matplotlib\"], // Packages to install\n    entrypoint: \"streamlit_app.py\", // The target file of the `streamlit run` command\n    files: {\n      \"streamlit_app.py\": `\nimport streamlit as st\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nsize = st.slider(\"Sample size\", 100, 1000)\n\narr = np.random.normal(1, 1, size=size)\nfig, ax = plt.subplots()\nax.hist(arr, bins=20)\n\nst.pyplot(fig)\n`,\n    },\n    streamlitConfig: {\n      // Streamlit configuration\n      \"client.toolbarMode\": \"viewer\",\n    },\n  },\n  document.getElementById(\"root\"),\n);\n```\n\n### Various ways to load files (`files` option)\n\nYou can pass an object to the `files` option to mount files onto the file system, whose keys are file paths, and you can specify the values in various ways as below. See also the [File system](#file-system) section for more details.\n\n#### Passing string or binary data\n\nYou can pass the file content as a string or binary data.\n\nThis is what we did in the example above.\n\n```js\nmount(\n  {\n    files: {\n      \"path/to/text_file.txt\": \"file content\",\n      \"path/to/binary_file.bin\": new Uint8Array([0x00, 0x01, 0x02, 0x03]),\n    },\n    // ... other options ...\n  },\n  document.getElementById(\"root\"),\n);\n```\n\n#### Passing an object with a URL\n\nYou can use this way to load a file from a URL and mount it to the specified path on the virtual file system.\n\nEither an absolute or relative URL is accepted. Consider as the same as the `url` option of the [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) function.\n\n```js\nmount(\n  {\n    files: {\n      \"path/to/file\": {\n        url: \"https://example.com/path/to/file\",\n      },\n      \"path/to/file2\": {\n        url: \"./path/to/file\",\n      },\n    },\n    // ... other options ...\n  },\n  document.getElementById(\"root\"),\n);\n```\n\n#### Passing an object with options (advanced)\n\n_Stlite_ runs on [Pyodide](https://pyodide.org/), and [it has a file system provided by Emscripten](https://pyodide.org/en/stable/usage/file-system.html).\nThe files specified via the `files` option are mounted on the file system, and [Emscripten's `FS.writeFile()` function](https://emscripten.org/docs/api_reference/Filesystem-API.html#FS.writeFile) is used internally for it.\nYou can specify the options (`opts`) for the `FS.writeFile(path, data, opts)` function as below.\n\n```js\nmount(\n  {\n    files: {\n      \"path/to/text_file.txt\": {\n        data: \"file content\",\n        opts: {\n          encoding: \"utf8\",\n        },\n      },\n      \"path/to/file\": {\n        url: \"https://example.com/path/to/file\",\n        opts: {\n          encoding: \"utf8\",\n        },\n      },\n    },\n    // ... other options ...\n  },\n  document.getElementById(\"root\"),\n);\n```\n\n### Loading archive files (`archives` option)\n\nYou can load archive files such as zip files, unpack them, and mount the unpacked files to the file system by using the `archives` option.\n\nThe `url` field of each item accepts either an absolute or relative URL. Consider as the same as the `url` option of the [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) function.\n\nThe downloaded archive file is unpacked by the [`pyodide.unpackArchive(buffer, format, options)`](https://pyodide.org/en/stable/usage/api/js-api.html#pyodide.unpackArchive) function. You have to pass the rest of the arguments of the function, `format` and `options` as below.\n\n```js\nmount(\n  {\n    archives: [\n      {\n        url: \"./foo.zip\",\n        // buffer: new Uint8Array([...archive file binary...]), // You can also pass the binary data directly\n        format: \"zip\",\n        options: {},\n      },\n    ],\n    // ... other options ...\n  },\n  document.getElementById(\"root\"),\n);\n```\n\n### Multipage apps\n\nYou 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.\n\nRead [the Streamlit official document](https://docs.streamlit.io/library/get-started/multipage-apps) about the multipage apps.\n\n```js\nmount(\n  {\n    entrypoint: \"👋_Hello.py\",\n    files: {\n      \"👋_Hello.py\": `\nimport streamlit as st\n\nst.set_page_config(page_title=\"Hello\")\nst.title(\"Main page\")\n`,\n      \"pages/1_⭐️_Page1.py\": `\nimport streamlit as st\n\nst.set_page_config(page_title=\"Page1\")\nst.title(\"Page 1\")\n`,\n      \"pages/2_🎈_Page2.py\": `\nimport streamlit as st\n\nst.set_page_config(page_title=\"Page2\")\nst.title(\"Page 2\")\n`,\n    },\n  },\n  document.getElementById(\"root\"),\n);\n```\n\n### Customizing the Streamlit configuration (`streamlitConfig` option)\n\nYou can pass the Streamlit configuration options to the `streamlitConfig` field as key-value pairs as below. Unlike the original Streamlit configuration, the options are passed as a flat object with the keys separated by dots.\n\n```js\nmount(\n  {\n    streamlitConfig: {\n      \"theme.base\": \"dark\",\n      \"theme.primaryColor\": \"#00b4d8\",\n      \"theme.backgroundColor\": \"#03045e\",\n      \"theme.secondaryBackgroundColor\": \"#0077b6\",\n      \"theme.textColor\": \"#caf0f8\",\n      \"client.toolbarMode\": \"viewer\",\n      \"client.showErrorDetails\": false,\n    },\n    // ... other options ...\n  },\n  document.getElementById(\"root\"),\n);\n```\n\n### Install packages with options\n\nSpecifying the `installs` option on `mount()` or calling `controller.install()` allows you to install packages with specific options that are passed to [`micropip.install`](https://micropip.pyodide.org/en/v0.7.1/project/api.html#micropip.install) internally.\n\n```js\nconst controller = mount({\n  // ... other options ...\n  installs: [\n    {\n      requirements,\n      options: {\n        keep_going,\n        deps,\n        credentials,\n        pre,\n        index_urls,\n        constraints,\n        reinstall,\n        verbose,\n      },\n    },\n  ],\n});\n\ncontroller.install(requirements, {\n  keep_going,\n  deps,\n  credentials,\n  pre,\n  index_urls,\n  constraints,\n  reinstall,\n  verbose,\n});\n```\n\n### Different Stlite versions\n\nIn the example above, the _Stlite_ script is loaded from the versioned URL.\n\nThe 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.\n\n#### The latest release\n\n```\nhttps://cdn.jsdelivr.net/npm/@stlite/browser/build/stlite.js\n```\n\nYou can use the latest version of the published _Stlite_ package with this URL.\n\n#### The head of the main branch\n\n```\nhttps://stlite-browser-preview.pages.dev/stlite.js\n```\n\nThis URL points to the head of the main branch which is usually ahead of the released packages.\nHowever, we strongly recommend NOT to use this URL for production because this might be broken and there is no guarantee that this resource will be kept available in the future.\n\n\u003e [!WARNING]\n\u003e The URL below for this purpose is not supported anymore.\n\u003e https://whitphx.github.io/stlite/lib/browser/stlite.js\n\n### Different Pyodide distributions (`pyodideUrl` option)\n\n_Stlite_ uses [Pyodide](https://pyodide.org/) and loads it from the [CDN](https://pyodide.org/en/stable/usage/downloading-and-deploying.html#cdn) by default. You can use your own Pyodide distribution by passing the URL to the `pyodideUrl` option as below. This would be helpful for example when your organization has a restrictive policy for CDN access.\n\n```js\nmount(\n  {\n    pyodideUrl: \"https://\u003cyour-pyodide-distribution-url\u003e/pyodide.js\",\n    // ... other options ...\n  },\n  document.getElementById(\"root\"),\n);\n```\n\nPyodide provides two distribution types, full and core, and you should serve the full distribution in this case.\n_Stlite_ loads some packages from the Pyodide distribution such as `micropip` and they are not included in the core distribution.\nEven with the full distribution whose size is quite large (+200MB), only the necessary packages are loaded on demand, so the actual size of loaded resources is smaller and you don't have to choose the core distribution worrying about the size. Ref: [#1007](https://github.com/whitphx/stlite/issues/1007#issuecomment-2214434028).\n\n## File system\n\n_Stlite_ executes your Python code on [Pyodide](https://pyodide.org/) in the browser, and Pyodide has its own Linux-like file system isolated from the host OS (see [Pyodide's](https://pyodide.org/en/stable/usage/file-system.html) or [Emscripten's](https://emscripten.org/docs/api_reference/Filesystem-API.html) documents about the FS for details).\nThe source code and data files are mounted on the file system through the `files` and `archives` options as described above, and the Python code can access them. So, for example, what `open(\"path/to/file\")` reads or writes is the file on the file system virtually existing in the browser, not a file on the host OS.\n\nThe default file system ([`MEMFS`](https://emscripten.org/docs/api_reference/Filesystem-API.html#memfs)) is ephemeral, so the files saved in the directories are lost when the page is reloaded.\nThe root `/` and some directories including home are mounted as `MEMFS`, the ephemeral file system, by default.\n\n### File persistence with IndexedDB backend\n\nTo persist the files across the app restarts, you can use the IndexedDB-based file system ([`IDBFS`](https://emscripten.org/docs/api_reference/Filesystem-API.html#filesystem-api-idbfs)). The files saved in the directories mounted with `IDBFS` are stored in the browser's IndexedDB, so they are persistent across the app restarts.\n\nIn the case of `@stlite/browser`, you can mount the IndexedDB-based file system, `IDBFS` to the specified directories in the virtual file system, by passing the `idbfsMountpoints` option as below.\nThe mounted file system is persistent across the page reloads and the browser sessions.\n\n```js\nmount(\n  {\n    idbfsMountpoints: [\"/mnt\"], // Mount the IndexedDB-based file system to the /mnt directory.\n    entrypoint: \"streamlit_app.py\",\n    files: {\n      \"streamlit_app.py\": `\nimport datetime\nimport streamlit as st\n\nwith open(\"/mnt/data.txt\", \"a\") as f:\n    f.write(f\"{datetime.datetime.now()}\\\\n\")\n\nwith open(\"/mnt/data.txt\", \"r\") as f:\n    st.code(f.read())\n`,\n    },\n    // ... other options ...\n  },\n  document.getElementById(\"root\"),\n);\n```\n\n## HTTP requests\n\nTo make HTTP requests, these libraries work on _Stlite_.\n\n- `requests` (only [these classes and methods](https://github.com/koenvo/pyodide-http?tab=readme-ov-file#supported-packages))\n- `urllib` (only [these classes and methods](https://github.com/koenvo/pyodide-http?tab=readme-ov-file#supported-packages))\n- `urllib3` ([since 2.2.0](\u003c(https://urllib3.readthedocs.io/en/stable/reference/contrib/emscripten.html)\u003e))\n- [`pyodide.http.pyfetch()`](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.pyfetch) and [`pyodide.http.open_url()`](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.open_url)\n  - See also the following section about top-level await to know how to use the async method `pyodide.http.pyfetch()`.\n\n_Stlite_ automatically enables [`koenvo/pyodide-http`](https://github.com/koenvo/pyodide-http)'s patches to make `requests` and `urllib` work,\nwhile the networking libraries do not work in general on the Pyodide runtime (Python in browser) as written in [this doc](https://pyodide.org/en/stable/project/roadmap.html#http-client-limit) and Pyodide provides its standard alternative methods to make HTTP requests, `pyodide.http.pyfetch()` and `pyodide.http.open_url()`.\n\nAlso, `urllib3` supports Pyodide since 2.2.0 as [this document](https://urllib3.readthedocs.io/en/stable/reference/contrib/emscripten.html) says.\n\n## SharedWorker mode\n\nNormally, each app created by `stlite.mount()` or `\u003cstreamlit-app\u003e` runs in its own [worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API).\nIt brings good isolation but also can lead to huge memory consumption when there are many Stlite apps running at the same time.\n\nIn such a case, running the apps in a single worker can help mitigate the resource consumption, and _Stlite_ provides [**SharedWorker**](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker) mode to do it.\n\nTo enable SharedWorker mode, set `shared-worker` attribute on the `\u003cstreamlit-app\u003e` tag\n\n```html\n\u003cstreamlit-app shared-worker\u003e\n  ...\n\u003c/streamlit-app\u003e\n```\n\nor set the `sharedWorker` option to `true` on `stlite.mount`\n\n```js\nstlite.mount(\n  {\n    sharedWorker: true,\n    // ... other options ...\n  },\n  document.getElementById(\"root\")\n);\n```\n\nThe Python environment and file system are not isolated in the SharedWorker.\nA different home directory is set for each app (`/home/pyodide/\u003cid\u003e`) but the files are still accessible from all the apps running in the worker.\nAlso modifications on the Python environment (e.g. installed packages or monkey-patching modules) are shared across all apps.\n\n[SharedWorker doesn't work in some browsers](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker#browser_compatibility) such as Chrome Android, and in such a case, _Stlite_ falls back to using the normal separate workers for each app.\n\n## Limitations\n\nAs _Stlite_ runs on the web browser environment ([Pyodide](https://pyodide.org/) runtime), there are things not working well. The known issues follow.\n\n- `st.spinner()` does not work with blocking methods like `pyodide.http.open_url()` because _Stlite_ runs on a single-threaded environment, so `st.spinner()` can't execute its code to start showing the spinner during the blocking method occupies the only event loop.\n  - If you want to show a spinner with a blocking method, add a 0.1-second sleep before the blocking method call, although this will definitely add an empty 0.1-second wait to the execution.\n    ```python\n    with st.spinner(\"Running a blocking method...\"):\n        await asyncio.sleep(0.1)  # Add this line to wait for the spinner to start showing\n        some_blocking_method()\n    ```\n- `st.bokeh_chart()` does not work since Pyodide uses Bokeh version 3.x while Streamlit only supports 2.x. The 3.x support for Streamlit is tracked here: https://github.com/streamlit/streamlit/issues/5858\n- `time.sleep()` is no-op. Use `asyncio.sleep()` instead. This is a restriction from Pyodide runtime. See https://github.com/pyodide/pyodide/issues/2354. The following section about top-level await may also help to know how to use async functions on _Stlite_.\n- `st.write_stream()` should be used with an async generator function rather than a normal generator function. Due to the same reason as `st.spinner()` above, the normal generator function does not work well in the browser environment, while it still can be passed to `st.write_stream()`. The following is an example of `st.write_stream()` with an async generator function.\n\n  ```python\n  async def stream():\n      for i in range(10):\n          yield i\n          await asyncio.sleep(1)\n\n  st.write_stream(stream)\n  ```\n\n- There are some small differences in how (less common) data types of DataFrame columns are handled in `st.dataframe()`, `st.data_editor()`, `st.table()`, and Altair-based charts. The reason is that _Stlite_ uses the Parquet format instead of the Arrow IPC format to serialize dataframes (Ref: [#601](https://github.com/whitphx/stlite/pull/601)).\n- Packages including binary extensions (e.g. C/Rust/Fortran/etc) that are not built for the Pyodide environment cannot be installed. See https://pyodide.org/en/stable/usage/faq.html#why-can-t-micropip-find-a-pure-python-wheel-for-a-package for the details.\n- Package version resolution may fail in some cases [due to micropip's version resolution mechanism](https://github.com/pyodide/micropip/issues/103):\n  - `plotly` functions may fail when installed with `altair` as [#1302](https://github.com/whitphx/stlite/issues/1302#issuecomment-2668085164) describes in which case installing `plotly==5.*` may help.\n\nOther problems are tracked at GitHub Issues: https://github.com/whitphx/stlite/issues\nIf you find a new problem, please report it.\n\n## Top-level await\n\nTL;DR: Use top-level await instead of `asyncio.run()` on _Stlite_.\n\nUnlike 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.\n\nTop-level await can be useful in various situations.\n\n### Example 1: `asyncio.sleep()`\n\nOne 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.\n\nYou can use top-level await either for `asyncio.sleep()` directly or for an async function that contains `asyncio.sleep()` like the following:\n\n```python\nimport asyncio\nimport streamlit as st\n\nst.write(\"Hello, world!\")\nawait asyncio.sleep(3)\nst.write(\"Goodbye, world!\")\n```\n\n```python\nimport asyncio\nimport streamlit as st\n\nasync def main():\n    st.write(\"Hello, world!\")\n    await asyncio.sleep(3)\n    st.write(\"Goodbye, world!\")\n\nawait main()\n```\n\n### Example 2: `pyodide.http.pyfetch()`\n\nAnother common use case is accessing external resources. Pyodide provides a Python wrapper of browser's [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch), [`pyodide.http.pyfetch()`](https://pyodide.org/en/stable/usage/api/python-api/http.html#pyodide.http.pyfetch) for making HTTP requests. Since this method is async, top-level await is sometimes used to handle the response.\n\nHere's an example:\n\n```python\nimport pyodide.http\n\nurl = \"your_url_here\"\nresponse = await pyodide.http.pyfetch(url)\ndata_in_bytes = await response.bytes()\n```\n\n## Resources\n\n- [📖 Streamlit meets WebAssembly - stlite, by whitphx](https://www.whitphx.info/posts/20221104-streamlit-wasm-stlite/): 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.\n- [📺 \"Serverless Streamlit + OpenCV Python Web App Tutorial\", by 1littlecoder, YouTube](https://youtu.be/7Qja9ZAWcfw): A quick tutorial to develop an OpenCV image processing app with _Stlite_ that runs completely on browsers.\n- [📖 \"New library: stlite, a port of Streamlit to Wasm, powered by Pyodide\", Streamlit Community](https://discuss.streamlit.io/t/new-library-stlite-a-port-of-streamlit-to-wasm-powered-by-pyodide/25556):\n  The Stlite thread at the Streamlit online forum.\n- [📺 \"Stlite - Streamlit without Server - powered by Pyodide (WebAssembly)\", by 1littlecoder, YouTube](https://youtu.be/VQdktxgbmmg):\n  A quick tutorial with local app development and GitHub Pages deployment.\n- [📺 \"How to Convert a Streamlit App to an .EXE Executable\", by Fanilo Andrianasolo, YouTube](https://youtu.be/3wZ7GRbr91g):\n  A tutorial to convert a Streamlit app to an executable file with _Stlite_ and a demo to run it on an offline machine.\n- [📖 \"Is This the Easiest Way to Build Your Streamlit App?\", by Shantala Mukherjee](https://onlyweb.hashnode.dev/is-this-the-easiest-way-to-build-your-streamlit-app)\n- [📖 \"The Best Python Desktop App Framework?\", by Caleb Robey at Depot Analytics](https://www.depotanalytics.co/post/the-best-python-desktop-app-framework)\n- [📖 \"Python-Based Data Viz (With No Installation Required)\", by Sam Minot](https://towardsdatascience.com/python-based-data-viz-with-no-installation-required-aaf2358c881)\n- [📖 \"Converting Streamlit application to exe file\", by Neelasha Sen](https://ploomber.io/blog/streamlit_exe/)\n- [📖 \"Streamlit + Stlite: Beyond Data Science Applications\", by Saumitra Panchal](https://medium.com/@saumitrapanchal/streamlit-stlite-beyond-data-science-applications-23de64648883)\n- [📖 \"stlite: Serverless Streamlit — Run Your Apps in the Browser\", by Alan Jones](https://medium.com/codefile/stlite-serverless-streamlit-d1dcf5be35f8)\n\n## Samples\n\n### ⚡️Serverless Image Processing App\n\nImage processing with OpenCV works on the client side.\n\n- Repository📌: https://github.com/whitphx/stlite-image-processing-app\n- Online demo🎈: https://whitphx.github.io/stlite-image-processing-app/\n\n\u003cdetails\u003e\n  \u003csummary\u003eSee the tutorial video\u003c/summary\u003e\n\n[Serverless Streamlit + OpenCV Python Web App Tutorial](https://youtu.be/7Qja9ZAWcfw), crafted by [1littlecoder](https://www.youtube.com/c/1littlecoder).\n\n[![Serverless Streamlit + OpenCV Python Web App Tutorial](https://img.youtube.com/vi/7Qja9ZAWcfw/0.jpg)](https://youtu.be/7Qja9ZAWcfw)\n\n\u003c/details\u003e\n\n## Sponsors\n\n### Streamlit (Snowflake)\n\n[\u003cimg src=\"https://streamlit.io/images/brand/streamlit-mark-color.png\" height=\"100\" /\u003e](https://streamlit.io/) [\u003cimg src=\"https://docs.snowflake.com/_images/logo-snowflake-sans-text.png\" height=\"100\" /\u003e](https://www.snowflake.com/)\n\n### TestMu AI\n\n[\u003cpicture\u003e\u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://assets.testmuai.com/resources/images/logos/white-logo.png\"\u003e\u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://assets.testmuai.com/resources/images/logos/black-logo.png\"\u003e\u003cimg alt=\"TestMu AI logo\" src=\"https://assets.testmuai.com/resources/images/logos/white-logo.png\" height=\"85\"\u003e\u003c/picture\u003e](https://www.testmuai.com/?utm_medium=sponsor\u0026utm_source=stlite)\n\n### Hal9\n\n[\u003cimg src=\"https://hal9.com/logo/hal9-square-black.png\" height=\"50\" \u003e](https://hal9.com/)\n\nThey are sponsoring me on [GitHub Sponsors](https://github.com/sponsors/whitphx)!\n\n### RAKUDEJI Inc.\n\n[\u003cimg src=\"https://imagedelivery.net/uODi9j-67fGrJlC0UtMj5w/3c47faee-8dab-41fa-ded6-681bdc3e9500/desktop\" height=\"50\" \u003e](https://rakudeji.com/)\n\nThey are sponsoring me on [GitHub Sponsors](https://github.com/sponsors/whitphx)!\n\n## Support the project\n\n[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/D1D2ERWFG)\n\n[\u003cimg src=\"https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png\" alt=\"Buy Me A Coffee\" width=\"180\" height=\"50\" \u003e](https://www.buymeacoffee.com/whitphx)\n\n[![GitHub Sponsors](https://img.shields.io/github/sponsors/whitphx?label=Sponsor%20me%20on%20GitHub%20Sponsors\u0026style=social)](https://github.com/sponsors/whitphx)\n\nContact the author: [Twitter](https://twitter.com/whitphx) / [LinkedIn](https://www.linkedin.com/in/whitphx/).\n","funding_links":["https://github.com/sponsors/whitphx","https://patreon.com/whitphx","https://ko-fi.com/whitphx","https://www.buymeacoffee.com/whitphx","https://github.com/sponsors/whitphx)!","https://ko-fi.com/D1D2ERWFG"],"categories":["TypeScript","Uncategorized"],"sub_categories":["Uncategorized"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwhitphx%2Fstlite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwhitphx%2Fstlite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwhitphx%2Fstlite/lists"}