Skip to content

browser: pyfun bundle, a Dom façade in the cookbook, and the Promise-to-Async note - #117

Merged
simontreanor merged 1 commit into
mainfrom
browser-target
Aug 30, 2026
Merged

browser: pyfun bundle, a Dom façade in the cookbook, and the Promise-to-Async note#117
simontreanor merged 1 commit into
mainfrom
browser-target

Conversation

@simontreanor

Copy link
Copy Markdown
Owner

Closes #111, the last of the fourth dogfooding session's nine issues (ROADMAP item 20).

pyfun bundle

pyfun bundle <entry.pyfun> -o <dir> [--asset <file>]... [--page <fragment.html>]

A static page that runs the program: index.html, the compiled Python (one main.py, or a project's whole .py tree), the assets, and pyfun-bundle.js, a module script that boots Pyodide from its CDN (the release the playground pins), stages the files into the in-browser file system, and runs the entry with stdout and stderr in two <pre> panes. --page puts the program's own markup above them. A type error stops the bundle before anything is written. The program is then a shareable link with no server, which is how two players get a game whose interface is the browser.

The Dom façade (cookbook)

examples/interop/browser/dom.pyfun is the first real consumer of the "publish a façade, import many" axis: extern import js, opaque Element/Event/Callback, instance-access externs for the methods (.replaceChildren(), .addEventListener()), and Pyodide's create_proxy behind Dom.proxy, so the lifetime detail lives in the façade, not the program. counter.pyfun is the consumer: a top-level let mut the click handler reassigns (a global in the emitted Python), and the handler is an ordinary one-parameter function that Python calls with the event, which is the calling convention #107 settled on. Emitted:

import dom
count = 0
def render(_):
    return dom.setText(dom.byId("count"), f"{count}")
def bump(_):
    global count
    count = count + 1
    return render(None)
dom.on(dom.byId("plus"), "click", dom.proxy(bump))
render(None)

The harness compile-checks the example as a project (tests/project.rs); running it needs a browser.

The Promise to Async bridge

Nothing to build: a JS promise is awaitable under Pyodide, so an extern over a JS async API is typed -> Async a and awaited with let! like any other. DESIGN.md §6 ("Browser target") records all three pieces; README gets the CLI line, the cookbook README the row, ROADMAP item 20 is closed.

Tests

tests/cli.rs: a project entry with an asset and a page fragment bundles to the expected files with the expected loader constants; a single file bundles as main.py; a type error writes nothing. tests/project.rs: the browser example type-checks and lowers with the expected shapes. cargo test, cargo clippy --all-targets and cargo fmt --check are clean.

…to-Async note (#111)

pyfun bundle <entry> -o <dir> [--asset f]... [--page fragment.html]
emits a static page: the compiled Python (one file or a project's
tree), the assets, a loader that boots Pyodide from the CDN the
playground pins and runs the entry with stdout/stderr on the page, and
an index.html around them, so a program is a shareable link with no
server. examples/interop/browser/ holds a typed Dom façade over
Pyodide's js module (opaque Element/Event/Callback, create_proxy behind
Dom.proxy) and a counter page as its consumer; the harness compile-
checks it, since running it needs a browser. A JS promise is awaitable
under Pyodide, so an extern over a JS async API is typed -> Async a and
nothing had to be built for the bridge. README, DESIGN §6, the cookbook
README and ROADMAP item 20 updated.
@simontreanor
simontreanor merged commit 4760d14 into main Aug 30, 2026
16 checks passed
@simontreanor
simontreanor deleted the browser-target branch August 30, 2026 18:31
@simontreanor simontreanor mentioned this pull request Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A browser target: pyfun bundle, a Dom façade, and a PromiseAsync bridge

1 participant