| Header | Link |
|---|---|
| Purpose | Purpose |
| Use Cases | Use Cases |
| End-to-End Example | End-to-End Example |
| Toolchain | Toolchain |
| Commands | Commands |
| Rust Web API | Rust Web API |
| routes.toml | routes.toml |
| Deploy Note | Deploy Note |
| Support Matrix | Support Matrix |
| Web Save Data | Web Save Data |
| Troubleshoot | Troubleshoot |
The web target compiles your game to WebAssembly and emits a plain static bundle β index.html, boot.js, app.js, app_bg.wasm, and an assets/ folder β that you can drop on any static host. Use it to ship a browser playable for an itch.io jam, host a demo on Cloudflare Pages, or embed a game on your own site. Rendering runs on WebGPU through a browser canvas runner, and user:// saves map automatically to browser localStorage. The perro CLI builds the bundle for you; you do not hand-write any wasm glue.
- Ship a jam game to itch.io.
perro build --target webwrites.output/web/; zip the folder contents (withindex.htmlat the zip root) and upload as an HTML game. A single/route is the safe fit for itch's iframe embed. - Iterate in the browser without redeploying.
perro dev --target web --host 127.0.0.1 --port 8000builds a dev bundle, starts a built-in static server, and opens your browser. - Check release-like frame time in the browser.
perro dev --target web --releaseserves the release web bundle instead of the debug one. - Multi-page web build with real URLs. Add a
routes.tomlmapping eachhrefto ascene, then deploy.output/web/to a host that serves route folders, such as Cloudflare Pages. - Navigate between scenes from a UI button on the web. Give a
UiButtonaweb = { href = "/docs" }block; the button pushes the browser route on web and is parsed-but-ignored on native. - Persist player saves in the browser. Write to
user://save/slot1.json; on web it lands inlocalStorage, base64-encoded so binary saves survive.
Choose web when link-based distribution and browser reach outweigh native-only APIs and platform constraints. Keep gameplay code target-neutral; isolate web navigation, storage expectations, and unavailable native services at explicit boundaries.
Use perro dev --target web for browser behavior and a release web build for
size/performance decisions. Native dev results do not establish browser memory,
input, persistence, or frame pacing.
# One-time: install the web build toolchain.
rustup target add wasm32-unknown-unknown
# Iterate locally: build a dev bundle, serve it, and open the browser.
perro dev --path D:\GameProjects\MyGame --target web
# Cook the release web bundle into .output/web/.
perro build --path D:\GameProjects\MyGame --target web
# itch.io: zip the CONTENTS of .output/web/ (not the folder itself),
# confirm index.html sits at the zip root, then upload as an HTML game.See itch.io and Cloudflare Pages below for host-specific deploy notes.
Req:
- web build toolchain installed
rustup target add wasm32-unknown-unknownperro CLI create web bundle for you.
Dev:
perro dev --path <project_dir> --target web [--profile] [--release] [--host <addr>] [--port <num>]Build:
perro build --path <project_dir> --target web [--profile]Flow:
- Sync
res/**/*.rs->.perro/scripts/src/*.gen.rs. - Run static asset pipeline.
- Create generated web project bundle in dev mode by default.
- Write dev bundle into
<project_dir>/.output/web-dev/. - Start built-in static server.
- Open default browser to
http://<host>:<port>/.
Notes:
--releaseuse release web bundle for dev server.- default host
127.0.0.1 - default port
8000 - dev web path use static embedded runtime, ! native dynamic runner
Bundle files:
index.htmlboot.jsapp.jsapp_bg.wasm
Flow:
- Sync
res/**/*.rs. - Run static asset pipeline.
- Create generated web project bundle in release mode.
- Write bundle into
<project_dir>/.output/web/.
Build command ! start local server. Host bundle on any static file host.
Use perro_web in runtime-side Rust code:
use perro_web::{
current_href,
get_args,
init_router,
pop_route,
push_route,
split_href,
split_query_args,
storage::{
load_cookie_bytes,
load_local_bytes,
load_session_bytes,
remove_cookie,
remove_local,
remove_session,
save_cookie_bytes,
save_local_bytes,
save_session_bytes,
},
take_pending_route_change,
};API map:
init_router()-> init browser router onwasm32current_href()-> ret current normalized browser pathget_args()-> ret current query arg arr like["arg1", "arg2"]push_route("/docs")-> push browser history + queue route chgpop_route()-> cal browser back pathsplit_href("/docs/api")-> ret path seg arrsplit_query_args("?arg1&arg2")-> ret query arg arrtake_pending_route_change()-> ret next queued href on route chgsave_local_bytes(key, data)-> wr browserlocalStorageload_local_bytes(key)-> rd browserlocalStorageremove_local(key)-> rm browserlocalStoragekeysave_session_bytes(key, data)-> wr browsersessionStorageload_session_bytes(key)-> rd browsersessionStorageremove_session(key)-> rm browsersessionStoragekeysave_cookie_bytes(key, data)-> wr browser cookie valload_cookie_bytes(key)-> rd browser cookie valremove_cookie(key)-> rm browser cookie
Native path:
- all
perro_webroute fns use no-op stub - all
perro_web::storage::*fns retUnsupported current_href()->Noneget_args()->Nonepush_route()+pop_route()->falsetake_pending_route_change()->None
Route norm:
- add leading
/if miss - trim trailing
/except root/ - strip query
?x=1+ hash#part
Ex:
"docs"=>"/docs""/docs/"=>"/docs""/docs?tab=api#top"=>"/docs"
Arg split ex:
"/docs?arg1&arg2"=>get_args() = ["arg1", "arg2"]"/docs?a=1&b=2"=>get_args() = ["a=1", "b=2"]"/"=>[]"/docs"=>split_href(...) = ["docs"]"/docs/api"=>split_href(...) = ["docs", "api"]
Web prj route map use opt sibling fle of project.toml:
my_game/
|- project.toml
|- routes.toml
`- res/
Fmt:
[[route]]
href = "/"
name = "home"
scene = "res://routes/home.scn"
[[route]]
href = "/docs"
name = "docs"
scene = "res://routes/docs.scn"Rules:
- use
[[route]]array tbl hrefreqnamereqscenereqhrefuse exact match only- dynamic params + wildcard path ! support
Boot + runtime flow:
- web boot cal
perro_web::init_router() - runtime rd browser path frm
current_href() - if route match, runtime load route
scene - each frame runtime poll
take_pending_route_change() - if href match, runtime swap root scene 2 route scene
Miss cfg path:
- if
routes.tomlmiss, runtime mk default route cfg - default route map use
/=>project.main_scene
Miss href path:
- boot miss -> fall back 2
/ - if
/miss too, fall back 2project.main_scene - later push/pop miss -> runtime kp current scene
UiButton support opt web cfg block:
[UiButton]
text = "Docs"
web = { href = "/docs" }
[/UiButton]
Click flow on web:
- button click cal
perro_web::push_route(href) - runtime route poll find new href
- runtime load scene frm
routes.toml - normal button click evt still fire
Click flow on native:
web = { ... }parse ok- route push ! run
- normal button click evt still fire
Web build output use plain static files.
Output shape:
/index.html/boot.js/app.js/app_bg.wasm/assets/...- each
routes.tomlentry also emit route html like/docs/index.html
Route note:
- route html + static assets use relative file refs
- route nav + route fetch use root-absolute href like
/docs - host bundle at site root like
https://game.example.com/ - subdir mount like
https://example.com/games/my-game/! fit now
Use case:
- good fit 4 root-only web build
- best fit if prj use only
/route
Upload flow:
- run
perro build --path <project_dir> --target web - open
<project_dir>/.output/web/ - zip contents of dir, ! dir itself
- chk zip root has
index.html - upload zip as HTML game
Notes:
- itch host game in iframe
- itch doc ask relative paths in uploaded files
- Perro asset refs fit that rule
- Perro route nav ! fit itch multi-page embed well cuz route hrefs use
/docsroot style - if you use
routes.toml+ web route buttons, expect route nav/direct deep link issues on itch
Safe itch path now:
- use single
/route - keep nav in-canvas/in-game, ! browser route path
- treat itch build as 1 entry page
Use case:
- good fit 4 full web deploy
- good fit 4 multi-route
routes.toml
Deploy flow:
- run
perro build --path <project_dir> --target web - deploy
<project_dir>/.output/web/as Pages static output - use Pages project root/custom domain root, ! subdir mount
Why this fit:
- Pages serve matching html file on route path
/docs/index.htmlauto-serve on/docs/- Pages also redirect html file paths 2 extension-less route paths
- if top-level
404.htmlmiss, Pages default SPA fallback map unknown paths ->/
Optional _redirects:
- add only if you want custom canonical/legacy path rules
- put
_redirectsin.output/web/
Ex:
/docs /docs/ 301
/old-home / 301
| Area | Status | Notes |
|---|---|---|
perro dev --target web |
Supported | create web dev bundle + start local server + open browser |
perro build --target web |
Supported | create web release bundle into .output/web/ |
--profile on web |
Supported | pass thru on dev + build |
--release on dev --target web |
Supported | use release web bundle for dev server |
--host / --port on web dev |
Supported | local server cfg |
--ui-profile on web dev |
Not yet | cli reject |
--csv-profile on web dev |
Not yet | cli reject |
--console on web build |
Not supported | cli reject |
| Area | Status | Notes |
|---|---|---|
| Browser canvas runner | Supported | web wnd + resize sync path |
| WebGPU renderer | Supported | req browser + GPU adapter |
| Static embedded assets | Supported | web dev + web build both use static embedded path |
| Native dynamic dev runner | Not used on web | web dev path ! use .perro/dev_runner |
| Browser w/o WebGPU adapter | Not supported | boot fail like No available adapters. |
| Native script dylib load | Not supported | compile out on wasm |
| Steamworks | Not supported | native-only path |
| EXE/path-relative native behaviors | Not supported | browser path ! use native exe model |
| Area | Status | Notes |
|---|---|---|
| Keyboard + mouse | Supported | browser wnd path |
Native gamepad backend (gilrs / hidapi) |
Not supported | compile out on wasm |
Native Joy-Con backend (btleplug) |
Not supported | compile out on wasm |
| Area | Status | Notes |
|---|---|---|
| Static scenes + static resources | Supported | main web content path |
| DLC disk/runtime path | Not yet verified | treat as unsupported for now |
| Hot dynamic asset/script load frm disk | Not supported | browser path use embedded bundle |
| Case | Perf shape | Notes |
|---|---|---|
Native perro dev |
Best dev iteration | dynamic file path + native runner |
Web perro dev --target web |
Slower build/start than native dev | rebuild web bundle + browser boot |
Web perro dev --target web --release |
Better runtime perf than web debug | use if frame time matter |
Web perro build --target web |
best web perf path | release web bundle + static bundle |
| Native build | best overall perf | no browser + no wasm bridge cost |
More perf detail:
- web dev use static embedded runtime path, so behavior match web build more than native dev
- debug web build run much slower than release web build
- browser perf depend on WebGPU driver + browser quality + GPU
- browser main-thread limits + web runtime bridge add cost vs native
- use native build 4 final perf baselines
- use web release path 4 browser perf baselines
Native perro dev:
- run
.perro/dev_runner - read project files frm disk
- use native wnd/app path
Web perro dev --target web:
- build web bundle frm
.perro/project - serve static files frm
.output/web-dev - boot browser canvas runner
- use static embedded assets + static script registry
Native perro build:
- output exe into
.output/
Web perro build --target web:
- output browser bundle into
.output/web/
user:// auto-map on web target.
Path map:
- native
user://save/slot1.json=> OS user data dir - web
user://save/slot1.json=> browserlocalStorage - key fmt:
perro:user:<ProjectName>:data:save/slot1.json
Notes:
- web path use
localStoragenow - vals store as base64 so binary data work
- same project name => same browser save namespace
- diff origins/domains keep diff browser storage
- browser clear/site-data clear => save gone
sessionStorage+ cookie path ! auto-bind 2user://- use
perro_web::storage::*if you need session-only or cookie-backed vals
Pick storage:
user://.../localStorage-> save files, settings, long-lived user datasessionStorage-> tmp per-tab state- cookie -> tiny server-visible vals, auth-ish bridge, legacy web flows
Limits:
localStorage+sessionStoragesize small vs disk save- cookie size very small, send on HTTP req, use only 4 tiny vals
- IndexedDB ! wire yet
Common errs:
web build toolchain missing- install req web build tools
No available adapters.- browser/WebGPU path fail
- chk real desktop browser + GPU support