The project publishes several static subparts to a single Cloudflare Pages project
named imgui-bundle, reachable at https://
| Path | Contents |
|---|---|
doc/ | Jupyter-book documentation (this book); |
/playground/ | Pyodide playground (Python examples in the browser) |
/min_pyodide_app/ | Minimal Pyodide template (single-file demo) |
/explorer/ | Dear ImGui Bundle Explorer (Emscripten build with OpenCV) |
/local_wheels/ | Shared imgui_bundle-*.whl, loaded by the pyodide subparts |
doc/assets/imgui_bundle_book.pdf | PDF export of the documentation |
The site’s _headers file is in the website resources repository (pthom/imgui_bundle_website_resources, fetched into docs/clone_website_resources/ by just cf_resources_sync), at imgui-bundle.pages.dev/_headers. cf_stage copies it to the staging root. It sets:
Cross-Origin-Opener-Policy/Cross-Origin-Embedder-Policyon/explorer/*only: site-wide COI would break the playground, which pulls Pyodide and CodeMirror from CDNs that don’t setCross-Origin-Resource-Policy. A third-party script on the explorer’s pages loads only if its server sends that header, or with thecrossoriginattribute if it allows CORS.Content-Type: application/wasmon/explorer/*.data: the emscripten asset bundles (application/octet-stream) are not in the CF edge’s compressible list, and CF Pages strips a user-setContent-Encoding. Emscripten ignores the type of a.datafile.
How the deploy works¶
Cloudflare Pages replaces the whole site on every upload, so we maintain a
local composed staging directory (_cf_staging/, gitignored)
that holds the union of all subparts. just cf_stage refreshes the staging
tree; just cf_deploy uploads it via wrangler.
The trade-off: on every deploy the other subparts must already be present
(built) on disk. cf_stage does not build them — it only copies:
Pyodide wheel:
just pyodide_buildmust have run (output inpyodide_projects/projects/local_wheels/).Ibex binaries:
just ibex_buildmust have run (output inbuild_ibex_ems/bin/).Jupyter-book docs:
just doc_build_cfmust have run (output indocs/book/_build/html/).
Unlisted previews¶
A preview is a build shown to a few people before its public deploy, at an unlisted address (e.g. /explorer/<name>/).
Put it in
_cf_previews/(gitignored), which mirrors the site’s tree:_cf_previews/explorer/<name>/is served at/explorer/<name>/.cf_stagecopies it back into the staging after everything else, so a local deploy keeps it. It is not in the sitemap.A deploy from CI (below) has no
_cf_previews/: it removes the previews from the site.Remove a preview’s folder once it is public.
One-time setup¶
Install wrangler (needs Node.js 18+):
npm install -g wranglerCreate an API token at https://
dash .cloudflare .com /profile /api -tokens using the Edit Cloudflare Workers template (it covers Pages too). Export credentials in your shell (or your shell rc):
export CLOUDFLARE_API_TOKEN=... export CLOUDFLARE_ACCOUNT_ID=...Verify:
wrangler whoamishould print the account.
Local workflow¶
# Build every subpart (slow: ibex with OpenCV takes 30-60 min).
# `cf_stage_prepare` runs: pyodide_setup_local_build + pyodide_build
# + ibex_build + doc_build_cf
just cf_stage_prepare
# Stage everything into _cf_staging, then deploy
just cf_stage
just cf_deploy
# Test the composed site locally before deploying
just cf_serve_local # http://localhost:8764/
# → COOP/COEP headers are applied only to /explorer/* (mirrors prod)
# → /explorer/*.data is served uncompressed (the site compresses it at the edge)
# Inspect the staging tree (gitignored)
ls _cf_staging/
# Nuke staging (next deploy re-uses on-disk build outputs)
rm -rf _cf_stagingCI workflow¶
.github/workflows/cf_pages_deploy.yml mirrors the local flow and is
triggered manually from the Actions tab (workflow_dispatch). It runs:
just pyodide_setup_local_build+just pyodide_build(builds the wheel)just ibex_build(builds Emscripten binaries; slowest step, typically 30–60 min becauseIMMVISION_FETCH_OPENCV=ONrebuilds OpenCV for wasm)just doc_build_cf(jupyter-book HTML + PDF, needs Typst)just cf_stage(composes the staging tree)npm install -g wrangler+just cf_deploy(uploads)
Required GitHub Actions secrets: CLOUDFLARE_API_TOKEN,
CLOUDFLARE_ACCOUNT_ID (same values used locally).
Pyodide wheel: deploy URL and filename references¶
The Pyodide wheel built by just pyodide_build is rsync’d into
_cf_staging/local_wheels/ by cf_stage and ends up at:
https://imgui-bundle.pages.dev/local_wheels/imgui_bundle-<VERSION>-cp314-cp314-pyemscripten_2026_0_wasm32.whlThis URL points at the latest Pages deploy, not a versioned archive.
Cloudflare Pages replaces the entire site on every upload, so previous
wheels disappear when a new version is deployed. We do not promise
stability for this URL: it is convenient for quick experiments, but
docs / demos that ship to end users tell readers to download the wheel
and self-host it (GitHub release assets work as a download source even
though they cannot be passed to micropip.install directly — no CORS).
When to update wheel filename references¶
Every time pyproject.toml / CMakeLists.txt version changes (or the wheel
platform tag changes per the runbook in
ci_scripts/pyodide_local_build/config_versions_pyodide.sh), the hardcoded
wheel filenames in HTML/JS/doc pages must be updated. Find them with:
rg "imgui_bundle.*\.whl" --glob '!external' --glob '!builds' --glob '!dist' --glob '!*.whl' --glob '!.pyodide_build'Glob-only references (*pyemscripten*.whl in justfile and the GitHub
workflows) do not need updating on a version bump — only on a tag rename.
The demos’ API (a Worker, deployed apart)¶
Some demos store data on a server. The Julia map (playground: explorables/julia_map) lists the values of c that its users share, and their votes. They live in a Cloudflare Worker with a D1 database (Cloudflare’s SQLite):
The Worker:
imgui-bundle-api, at https://imgui -bundle -api .pthomet .workers .dev /julia _points. Its code: cloudflare/julia_points/(src/index.ts, its routes are listed at the top;wrangler.jsonc).The database:
imgui_bundle_julia_points. Its tables:schema.sql; its first points:seed.sql.The demo’s side:
julia_points.py, next tojulia_map.py(POINTS_APIis the Worker’s address).
The Worker is deployed on its own, only when it changes: just demo_julia_points_deploy (after a type check, just demo_julia_points_check). The site’s deploy does not touch it, and it does not touch the site.
Local tests: just demo_julia_points_dev runs the Worker at http://POINTS_API there while testing. The demo on the desktop and the local playground (pyodide_projects/serve_cors.py) both reach it.
The admin mode: the Worker’s secret ADMIN_TOKEN opens the routes that hide a point, show it again, or delete it once hidden. The demo shows them when it finds the token in JULIA_POINTS_ADMIN (an environment variable on the desktop, localStorage in the browser). The token is not in this repository.
Set it:
cd cloudflare/julia_points && wrangler secret put ADMIN_TOKEN.ADMIN_TOKENis the secret’s name: the token goes at the prompt.wrangler secret listshows the names.Locally:
cloudflare/julia_points/.dev.vars(ignored by git), with a lineADMIN_TOKEN=<any value>.
The database in production: wrangler d1 execute imgui_bundle_julia_points --remote --command "SELECT ...", from cloudflare/julia_points. The free tier allows 100 000 requests a day.
Search engines¶
After a change that search results should show soon (a page’s title, its description), ask for a recrawl:
Bing and DuckDuckGo (which uses Bing’s index):
just site_recrawl <url>..., through IndexNow. It covers the pages ofimgui-bundle.pages.devand of the ImGui Explorer (pthom.github.io/imgui_explorer/).Google: in Search Console, “URL Inspection”, then “Request indexing”. The property of
imgui-bundle.pages.devis verified by thegoogle<id>.htmlfile of the website resources.
Pitfalls¶
Cloudflare Pages refuses a file over 25 MiB. A stray folder can push a packed file over it: a
.mypy_cache/left in a demo folder gets packed into the explorer’s.data(run mypy from the repo root, and remove such caches beforejust cf_stage).A missing file answers 404, with the site’s
404.htmlpage (from the website resources).After a deploy, check the live site: the book (
/doc/) and its PDF (/doc/assets/imgui_bundle_book.pdf), the playground and itsexamples/examples_docs.json, the explorer,/llms.txt.