Self-hosting a pwapp
An app built on PhiWebs can be downloaded as a pwapp and run on any static
host with @phimajor-solutions/pwfabric-runner. The runner is a browser ES
module: it fetches the app’s declaration files, mounts the renderer, and adds
routing, navigation and visitor sign-in. No build step, no Node process.
How far a self-host goes depends on what the app needs:
| The app… | Runs where | What is on the wire |
|---|---|---|
| is static — pages, content, appearance | anywhere that serves files | nothing; the host is enough |
| reads or writes data — lists, forms, entities | anywhere, plus an API | the data contract — see Data and receipts |
| gates pages behind visitor sign-in | anywhere, plus an issuer | the session protocol — see Visitor sign-in |
The first row needs only this page. The other two need the base URL of an API
that speaks the documented contract. Pointing it at api.phiwebs.com is the
supported path; running your own implementation of the same contract is what
the BSL permits and what those two pages make possible.
What a downloaded app contains
PhiCo offers two files. Export gives a zip that is a complete, runnable
folder: the app files, the block bundles, and a copy of the runner. Download
gives only the .pwpack.tgz envelope, which is the app without an engine; it
is not runnable on its own. Self-hosting starts from the Export zip.
Inside it, the runner reads plain JSON files and resolves blocks from a manifest next to them:
index.html ← the shell: loads the runner and calls runApp()
assets/
runner.js ← @phimajor-solutions/pwfabric-runner, copied in
embed.js, theme.css ← renderer and theme
shims/ ← import-map shims the bundles resolve against
pwpack/
project.json ← the app: pages, routes, surfaces (required)
navigation.json ← menus (optional)
appearance.json ← theme tokens (optional)
auth.json ← sign-in declaration (optional)
connections.json ← data connections (optional)
blocks/
manifest.json ← which block bundles exist and where
… ← the bundles themselves
engines/
manifest.json ← connector engines (optional)The exact shape of each file is in The envelope.
Source: pwfabric-core/packages/runner/src/run-app.ts (the fetch list at
the top of runApp);
PhiWebs API (closed source): web-app-assembly-service.ts
(assembleWebApp, the file map the Export writes).
Mounting the runner
The Export zip already contains an index.html that does this; it is shown
here so you know what to keep when you replace the shell with your own page:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="./assets/theme.css" />
<!-- the import map the export wrote, if any, goes here -->
<script src="./assets/embed.js"></script>
<!-- only for path routing; see below -->
<!-- <meta name="pw-router" content="path" /> -->
</head>
<body>
<div id="root"></div>
<script type="module">
import { runApp } from './assets/runner.js'
runApp({ source: './pwpack', blocks: './blocks/', router: 'hash' })
</script>
</body>
</html>runApp() takes an options object. Every field has a default that matches
the folder layout above, so runApp() with no arguments is equivalent when
the files sit next to index.html.
| Option | Default | Meaning |
|---|---|---|
source | './pwpack' | folder path or absolute URL of the app files |
blocks | './blocks/' | folder holding the block manifest.json |
engines | './engines' | folder holding the connector-engine manifest.json |
router | 'hash' | 'hash', 'path' or 'auto' — see routing |
mount | '#root' | selector of the element the app renders into |
locale | 'en' | content locale of the delivered app ('en' or 'tr') |
Source: pwfabric-core/packages/runner/src/types.ts (RunAppOptions);
web-app-assembly-service.ts (buildShellHtml, the shell the Export writes).
Routing on a static host
The default router is hash: routes live after # in the URL, which works on
every static host without configuration.
path routing gives clean URLs but needs the host to serve index.html for
every path (an “SPA fallback”). Declare it in the page so the runner knows the
host is ready:
<meta name="pw-router" content="path" />auto picks path only when history.pushState works and that meta tag
is present; otherwise it falls back to hash. If you enable path without
the fallback, deep links return the host’s 404.
Source: pwfabric-core/packages/runner/src/types.ts (router),
pwfabric-core/packages/runner/src/router.ts.
Where the platform is still on the wire
A static app makes no request to PhiWebs. Two things do, and each has its own page:
- Data. When a surface binds to an entity, the runtime sends the query to the configured API base. What it sends, and what it expects back, is the data contract.
- Sign-in. When a page is auth-gated, the runner opens one of two doors, chosen by the app’s identity topology: a PKCE flow against an external OIDC issuer, or the platform’s own app-session endpoints. The endpoints, the token, and where the session lives are in the session protocol.
These pages are written from source and cite it. A citation that is a file
path points into the open engine (pwfabric-core), which you can read. A
citation marked closed source names the PhiWebs server component whose
behaviour the page describes; that code is not published, so the page states
what it does rather than where. Where the code leaves something open, the
page says so under Unverified rather than guessing.