Build and serve
Vite production build, dist layout, axis_pwa_server.py SPA rules, and asset caching.
This page
Abstract
Production AXIS assets are a static site produced by Vite (bun run build → dist). Serve them with any static host, Cloudflare® Pages, or the repo’s axis_pwa_server.py SPA-aware server.
Critical requirement: real binary assets for Pyodide (.wasm, .whl, .zip) must not be rewritten to index.html.
Conceptual model
Rendering…
Interface surface
package.json scripts
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "bun test tests/ worker/tests/",
"test:coverage:gate": "bun run test:coverage && bun scripts/check-coverage.mjs 95",
"test:security": "bun test tests/security/",
"test:e2e:smoke": "playwright test --grep @smoke",
"test:all": "bun run test:coverage:gate && bun run test:security"
axis_pwa_server.py
bun run build
python axis_pwa_server.py
# HOST / PORT env; default 0.0.0.0:8081 → dist/
Behavior (axis_pwa_server.py):
| Rule | Detail |
|---|---|
| Root | Serves dist/ |
| Cache | /assets/* → immutable long cache; else no-cache |
GET /health (/healthz) | 200 text/plain ok — never SPA-fallback to index.html |
OPTIONS | 204 with CORS headers (not 501) |
/favicon.ico | Serves assets/icon-192.png when present |
| SPA fallback | Unknown paths → /index.html |
| Never rewrite | /health, /favicon.ico, /assets/, /plugins/, /vendor/, /pyodide/, or extensions including .js/.css/.whl/.wasm/.zip/.py/.json… |
This SPA exception list exists because Pyodide + micropip die with opaque BadZipFile when HTML is returned for a wheel URL. The engine’s assertZipAsset detects that class of failure early.
Expected public assets
| Path (public → dist) | Purpose |
|---|---|
/plugins/*.js | Example dynamic plugins |
/vendor/*.whl | pynescript + antlr wheels |
/pyodide/v0.29.5/** | Self-hosted Pyodide |
/pyodide/pynescript_runtime.py | Browser run bridge |
Internals
| Path | Role |
|---|---|
package.json | Build scripts |
axis_pwa_server.py | Threading HTTP SPA server |
src/engines/catalog.ts | Asset URL expectations |
LEGACY.md | Product path vs old shell |
Makefile note
make pages-deploy may historically deploy the wrong tree — prefer:
bun run build
wrangler pages deploy dist --project-name=axis
Build-time flags (Vite)
| Env var | Effect |
|---|---|
VITE_APP_VERSION | Stamps the reported version (CI/Docker); falls back to the repo VERSION file |
VITE_MAINTENANCE_MODE | 1 / true / yes → the build boots only the maintenance page (src/ui/MaintenancePage.tsx: dark screen, flickering HOOX mark, major-release teaser) instead of the chart workspace / editor popout |
VITE_AXIS_DEBUG | 1 / true / yes → detailed chart-action and engine logs ([axis:chart] / [axis:engine] in DevTools) plus a rotating localStorage ring (axis.debug.log.*, 4×64 KiB). Alias VITE_DEBUG. Forming ticks and live deltas are sampled (~1/s). Same switch is available at runtime in Settings → General → Debug logs (localStorage.axis.debug); the env flag wins. Download from Settings, System Logs, or window.__AXIS_DEBUG__.download() |
Both are inlined by Vite at build time — set them in .env (local/CI overrides) or export them in the build environment, then rebuild. Existing process env vars outrank .env files, which is how e2e forces the maintenance gate off (playwright.config.ts → webServer.env). The flag is documented in .env.example.
Worked example — static desk demo
bun install
bun run build
PORT=8081 python axis_pwa_server.py
# another terminal
make run # Flask :5002
Open http://127.0.0.1:8081, engine endpoint http://127.0.0.1:5002.
Invariants & edge cases
- Service workers / offline — product path uses Vite PWA assets; the legacy root
sw.jswas removed in 2.21.0. Onlypublic/sw.jsships. - Relative pyodide index — resolves against
location.origin. - Do not hand-edit dist — regenerate from build.
Failure modes
| Symptom | Cause |
|---|---|
| Blank routes on refresh | Server lacks SPA fallback |
| Pyodide HTML wheel error | Fallback rewriting .whl or missing vendor |
| Missing plugins | public/plugins not copied (check Vite publicDir) |