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

Diagram

Rendering…

Interface surface

package.json scripts

json
"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

bash
bun run build
python axis_pwa_server.py
# HOST / PORT env; default 0.0.0.0:8081 → dist/

Behavior (axis_pwa_server.py):

RuleDetail
RootServes dist/
Cache/assets/* → immutable long cache; else no-cache
GET /health (/healthz)200 text/plain ok — never SPA-fallback to index.html
OPTIONS204 with CORS headers (not 501)
/favicon.icoServes assets/icon-192.png when present
SPA fallbackUnknown 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/*.jsExample dynamic plugins
/vendor/*.whlpynescript + antlr wheels
/pyodide/v0.29.5/**Self-hosted Pyodide
/pyodide/pynescript_runtime.pyBrowser run bridge

Internals

PathRole
package.jsonBuild scripts
axis_pwa_server.pyThreading HTTP SPA server
src/engines/catalog.tsAsset URL expectations
LEGACY.mdProduct path vs old shell

Makefile note

make pages-deploy may historically deploy the wrong tree — prefer:

bash
bun run build
wrangler pages deploy dist --project-name=axis

Build-time flags (Vite)

Env varEffect
VITE_APP_VERSIONStamps the reported version (CI/Docker); falls back to the repo VERSION file
VITE_MAINTENANCE_MODE1 / 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_DEBUG1 / 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

bash
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

  1. Service workers / offline — product path uses Vite PWA assets; the legacy root sw.js was removed in 2.21.0. Only public/sw.js ships.
  2. Relative pyodide index — resolves against location.origin.
  3. Do not hand-edit dist — regenerate from build.

Failure modes

SymptomCause
Blank routes on refreshServer lacks SPA fallback
Pyodide HTML wheel errorFallback rewriting .whl or missing vendor
Missing pluginspublic/plugins not copied (check Vite publicDir)

See also