Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Cloudflare Pages deploy

The project publishes several static subparts to a single Cloudflare Pages project named imgui-bundle, reachable at https://imgui-bundle.pages.dev/:

PathContents
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.pdfPDF 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:

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:

Unlisted previews

A preview is a build shown to a few people before its public deploy, at an unlisted address (e.g. /explorer/<name>/).

One-time setup

  1. Install wrangler (needs Node.js 18+):

    npm install -g wrangler
  2. Create an API token at https://dash.cloudflare.com/profile/api-tokens using the Edit Cloudflare Workers template (it covers Pages too).

  3. Export credentials in your shell (or your shell rc):

    export CLOUDFLARE_API_TOKEN=...
    export CLOUDFLARE_ACCOUNT_ID=...
  4. Verify: wrangler whoami should 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_staging

CI workflow

.github/workflows/cf_pages_deploy.yml mirrors the local flow and is triggered manually from the Actions tab (workflow_dispatch). It runs:

  1. just pyodide_setup_local_build + just pyodide_build (builds the wheel)

  2. just ibex_build (builds Emscripten binaries; slowest step, typically 30–60 min because IMMVISION_FETCH_OPENCV=ON rebuilds OpenCV for wasm)

  3. just doc_build_cf (jupyter-book HTML + PDF, needs Typst)

  4. just cf_stage (composes the staging tree)

  5. 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.whl

This 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 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://localhost:8787/julia_points, with a local database that holds the seed. Point 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.

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:

Pitfalls