Skip to content

feat: self-contained WASM export bundles for offline use - #10879

Merged
peter-gy merged 7 commits into
marimo-team:mainfrom
peter-gy:pgy/offline-wasm-exports
Sep 20, 2026
Merged

peter-gy merged 7 commits into
marimo-team:mainfrom
peter-gy:pgy/offline-wasm-exports

Conversation

@peter-gy

Copy link
Copy Markdown
Collaborator

Adds --offline to bundle Pyodide and notebook dependencies alongside HTML-WASM exports:

uv run --with playwright python -m playwright install chromium

uv run --with playwright marimo export html-wasm notebook.py \
  --offline --no-sandbox -o dist

Exporting requires internet access and Playwright/Chromium. Pyodide resolves browser-compatible dependencies without executing notebook cells. The generated folder uses relative URLs and can be moved or served offline.

Runtime URL configuration remains separate from bundling, so granular source overrides can be exposed later. Existing exports retain their hosted defaults.

The export still requires a reachable HTTP server. It does not add browser offline caching, and notebook data/API calls & widget JavaScript assets need separate handling.

Closes #9811.

Copilot AI lite review requested due to automatic review settings September 17, 2026 08:19
@peter-gy
peter-gy requested a review from akshayka as a code owner September 17, 2026 08:19
@vercel

vercel Bot commented Sep 17, 2026

Copy link
Copy Markdown

Deployment failed for project marimo-docs with the following error:

You don't have permission to create a Preview Deployment for this Vercel project: marimo-docs.

View Documentation: https://vercel.com/docs/accounts/team-members-and-roles

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@peter-gy peter-gy added the enhancement New feature or request label Sep 17, 2026
@codecov

codecov Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 1.19kB (0.0%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
marimo-esm 26.45MB 1.19kB (0.0%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: marimo-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/index-*.js 408 bytes 1.33MB 0.03%
assets/worker-*.js 228 bytes 109.43kB 0.21%
assets/save-*.js 550 bytes 105.84kB 0.52%
assets/__vite-*.js -5 bytes 93 bytes -5.1%
assets/__vite-*.js 5 bytes 98 bytes 5.38% ⚠️

Files in assets/index-*.js:

  • ./src/core/wasm/runtime-config.ts → Total Size: 527 bytes

  • ./src/core/wasm/rpc.ts → Total Size: 398 bytes

  • ./src/core/wasm/bridge.ts → Total Size: 13.33kB

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 20 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="marimo/_export/offline.py">

<violation number="1" location="marimo/_export/offline.py:162">
P2: When offline export runs as root or in a container, Chromium fails because `--no-sandbox` never reaches Playwright. Forward the CLI sandbox choice into `_resolve_packages` and launch Chromium with the corresponding no-sandbox option.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread frontend/src/core/wasm/runtime-config.ts Outdated
Comment thread docs/guides/publishing/self_host_wasm.md
Comment thread marimo/_cli/export/commands.py
Comment thread marimo/_export/offline.py
from playwright.async_api import async_playwright # type: ignore[import-not-found]

async with async_playwright() as playwright:
browser = await playwright.chromium.launch()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When offline export runs as root or in a container, Chromium fails because --no-sandbox never reaches Playwright. Forward the CLI sandbox choice into _resolve_packages and launch Chromium with the corresponding no-sandbox option.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At marimo/_export/offline.py, line 162:

<comment>When offline export runs as root or in a container, Chromium fails because `--no-sandbox` never reaches Playwright. Forward the CLI sandbox choice into `_resolve_packages` and launch Chromium with the corresponding no-sandbox option.</comment>

<file context>
@@ -0,0 +1,309 @@
+    from playwright.async_api import async_playwright  # type: ignore[import-not-found]
+
+    async with async_playwright() as playwright:
+        browser = await playwright.chromium.launch()
+        try:
+            page = await browser.new_page()
</file context>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This finding looks invalid. playwright already launches chromium with --no-sandbox by default. marimo's --sandbox flag controls Python environment isolation and does not affect browser sandboxing.

@peter-gy
peter-gy requested a review from mscolnick September 17, 2026 08:57
mscolnick
mscolnick previously approved these changes Sep 17, 2026

@mscolnick mscolnick left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a really great approach! great stuff

export interface WasmRuntimeConfig {
pyodideIndexUrl?: string;
pyodideLockfileUrl?: string;
pypiIndexUrl?: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is how we should be sending marimo version too (not required in this PR). This is much cleaner than shoving in the worker name.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we can do that in a follow up

Pass independent Pyodide, lockfile, and package-index URLs through the export template and both workers while preserving hosted defaults.
Resolve browser-compatible packages in Pyodide through Playwright, stage downloads, and preserve prior exports with content-addressed wheels and lockfiles.
Wire runtime bundling through both export paths, preserve local wheel handling, and document the setup and local HTTP hosting requirements.
Probe the actual headless browser before downloading assets and report its setup command when startup fails.
@peter-gy
peter-gy merged commit eed9fb1 into marimo-team:main Sep 20, 2026
42 of 44 checks passed
mscolnick pushed a commit that referenced this pull request Sep 21, 2026
Follow up on #10879.

With this, we now pass the marimo version through `WasmRuntimeConfig`
instead of encoding it in the worker name.

Both WASM workers use the bootstrap config as the version source and
their names remain stable while preserving the custom-controller
capability suffix.
peter-gy added a commit that referenced this pull request Oct 5, 2026
Follow-up to #10879. `--offline` can now build the bundle from internal
mirrors, for networks that can't reach jsDelivr or PyPI:

```bash
marimo export html-wasm notebook.py -o dist --offline \
  --pyodide-index-url https://mirror.example.com/pyodide/full/ \
  --pypi-index-url https://mirror.example.com/pypi/simple/
```

A mirrored Pyodide distribution brings its own `pyodide-lock.json`, and
marimo's own packages then come from the package index. Everything still
resolves from the lockfile first, so Pyodide-built pins like
`pydantic-core` hold with any index. The resolver fetches through
Python, so mirrors need no CORS headers, and the export stops early when
a mirror serves a different Pyodide version.

The bundle is now the dependency closure of what the notebook needs, so
a download that fails mid-export stops it instead of shipping a bundle
that won't boot.

Closes #10985.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Configurable base URLs for retrieving WASM/wheel assets in html-wasm exports (to support self-contained/offline use)

3 participants