How it works¶
sharepack is a four-stage build pipeline plus a small browser runtime.
The pipeline¶
Collect — walk the project directory. Source, templates, config, static assets (images, fonts, stylesheets), and SQLite databases are kept; caches, virtualenvs, logs, and VCS directories are ignored. Secrets are scrubbed (below). Everything kept is read as bytes.
Detect — find the framework. The Django adapter looks for
manage.pyand reads the settings module out of it (--settings-moduleoverrides this); it also parsesSTATIC_URLand warns about non-SQLite database backends. The Flask and FastAPI adapters scan entry modules (app.py,main.py, …) for an app instantiation (--app module:variableoverrides). All adapters warn about incompatible dependencies found inrequirements.txt.Encode — base64 every file into one JSON payload.
Emit — splice the payload, a Python boot script, and framework metadata into an HTML template. The output is one self-contained file.
What gets scrubbed and skipped¶
At build time, before anything is embedded:
.env,.env.local,.env.production,.env.developmentare excluded.Files whose names look credential-like (
secret,token,password,credential,apikey,api_key) are excluded, unless they are.pyor.htmlsource files.SECRET_KEY = "..."insettings.pyis replaced with a throwaway value.Files with extensions sharepack doesn’t recognize are skipped and reported — run
--dry-runto see the full list with reasons, and use--include/--excludeglobs to adjust.
Every scrub and skip is printed in the build report. Nothing is silently dropped.
The browser runtime¶
When the recipient opens the file:
The page loads the Pyodide runtime (~10 MB, cached by the browser) from the jsDelivr CDN and installs Django with micropip from PyPI. This is the only time the network is used.
The boot script writes every bundled file into the WASM virtual filesystem at
/app/and boots the framework through a server-free transport:Django —
django.setup()plusdjango.test.Client, Django’s own request machinery repurposed as the transport.Flask — the app’s
test_client(), whose built-in cookie jar makes sessions and flash messages work.FastAPI — a minimal in-process ASGI driver (the usual test client needs threads, which WebAssembly doesn’t have): it builds the request scope by hand, follows redirects, and keeps its own cookie jar so sessions survive.
anyio’s threadpool is patched to run inline.
Every link click and form submit is intercepted in JavaScript and routed through that transport. The response HTML is rendered into an iframe. There is no port, no socket, and no server anywhere.
Static files¶
References to STATIC_URL paths in rendered HTML (<link href="/static/…">,
<img src="/static/…">, script tags) are resolved through Django’s
staticfiles finders inside the WASM filesystem, converted to blob URLs, and
rewritten before the page is displayed. Stylesheets, images, and fonts work
without collectstatic.
Content types¶
Responses are dispatched on their content type: HTML renders normally, JSON pretty-prints, plain text renders as text, images display, and anything binary becomes a download link.
Honest caveats¶
The artifact is a demo, not a deployment:
CSRF checks are disabled and
DEBUG = Trueis forced at boot, so form posts work and errors are readable. Never treat the bundle as a hardened build of your app.Anyone who has the file has your bundled code and data. The scrubber removes obvious secrets, but review
--dry-runoutput before sharing something sensitive.Each recipient gets a private copy. Writes persist across clicks but reset on reload; viewers never see each other’s changes.
Prior art¶
WordPress Playground (the pattern), stlite (same idea for Streamlit), and django_webassembly (the proof of concept this productizes).