Skip to main content

Browser Progressive Web Apps (PWA)

A Hosanna Browser app can also be shipped as an installable Progressive Web App. The installed app still uses the Web DOM renderer, while Hosanna supplies live viewport, orientation, and lifecycle behavior for the web-pwa platform. The host application remains responsible for its product identity, launch choice, artwork, offline message, network policy, and web hosting.

PWA build scaffolding is available in @tantawowa/hosanna-tools 3.16.0 and newer.

Ownership​

LayerResponsibility
Hosanna UIDetect the installed browser runtime, expose web-pwa capabilities, use live viewport and DPR metrics, publish orientation/viewport changes, and provide web lifecycle state and launch URL.
Hosanna ToolsGenerate the manifest, registration module, versioned service worker, validated icon copies, and offline fallback from explicit app inputs.
Host applicationChoose the app and expression, own manifest identity, icons, offline presentation, platform-web CSS, API/media exclusions, and deployment policy.
Web hostServe the static build over HTTPS, keep mutable PWA files revalidatable, protect network-only routes from SPA fallback, and apply the intended service-worker scope.

index.html can remain a thin root-level Vite entry. Product styling and other Browser-only assets should live under the application's platforms/browser boundary. PWA generation is never enabled for packaged Tizen or webOS output.

Add the Browser Assets​

A typical app-owned layout is:

platforms/browser/
icons/
icon-192.png
icon-512.png
icon-maskable-512.png
apple-touch-icon.png
offline.html
style.css
web-pwa-policy.ts
index.html
vite.config.ts

Provide at least 192x192 and 512x512 install icons. A separate maskable icon is recommended for launchers that crop artwork. When an icon declares type: 'image/png' and a concrete size, the build verifies that the source is a valid PNG with matching dimensions.

The offline page should be a small, self-contained explanation of what the user can do next. It is a navigation fallback, not a promise that application data or streaming media is available offline.

Configure Vite​

Keep the product-specific inputs in the app's platform-web policy:

platforms/browser/web-pwa-policy.ts
import type { HosannaPwaPluginOptions } from '@tantawowa/hosanna-tools/vite';

export const webPwaPolicy: HosannaPwaPluginOptions = {
manifest: {
id: '/',
name: 'Example',
shortName: 'Example',
description: 'Watch and explore Example.',
startUrl: '/?app=example&expression=phone',
scope: '/',
display: 'standalone',
orientation: 'any',
backgroundColor: '#f4f6f8',
themeColor: '#123456',
icons: [
{
src: '/pwa-icons/icon-192.png',
source: 'platforms/browser/icons/icon-192.png',
sizes: '192x192',
type: 'image/png',
purpose: 'any',
},
{
src: '/pwa-icons/icon-512.png',
source: 'platforms/browser/icons/icon-512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any',
},
{
src: '/pwa-icons/icon-maskable-512.png',
source: 'platforms/browser/icons/icon-maskable-512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable',
},
{
src: '/pwa-icons/apple-touch-icon.png',
source: 'platforms/browser/icons/apple-touch-icon.png',
sizes: '180x180',
type: 'image/png',
purpose: 'any',
},
],
},
appleTouchIcon: '/pwa-icons/apple-touch-icon.png',
serviceWorker: {
offlineFallback: {
url: '/offline.html',
source: 'platforms/browser/offline.html',
},
networkOnly: ['/auth', '/media'],
cachePrefix: 'example-pwa',
},
};

Add the build-only plugin to vite.config.ts. Hosanna Tools intentionally exposes a Vite-compatible structural plugin type without taking a dependency on Vite itself. With Vite 6, keep the type assertion at this single package boundary:

vite.config.ts
import { defineConfig, type Plugin } from 'vite';
import { hosannaPwaPlugin } from '@tantawowa/hosanna-tools/vite';
import { webPwaPolicy } from './platforms/browser/web-pwa-policy';

function asVitePlugin(plugin: unknown): Plugin {
return plugin as Plugin;
}

export default defineConfig({
plugins: [
asVitePlugin(hosannaPwaPlugin(webPwaPolicy)),
],
});

All public URLs must be root-relative and stay on the application origin. Source files must be project-root-relative. The manifest startUrl must be inside its scope, and appleTouchIcon must refer to one of the manifest icons.

The main options are:

OptionPurpose
manifest.id, name, shortName, descriptionInstalled-app identity and description.
manifest.startUrl, scope, display, orientationLaunch URL and browser presentation contract.
manifest.backgroundColor, themeColor, langBrowser and launch-surface metadata.
manifest.iconsPublic src, local source, declared sizes, MIME type, and optional purpose.
appleTouchIconOptional Apple home-screen icon; it must also appear in manifest.icons.
serviceWorker.offlineFallbackPublic output URL and local source for failed navigations.
serviceWorker.networkOnlyAdditional same-origin path prefixes that must bypass all caches.
serviceWorker.cachePrefixProduct-specific prefix used for versioned cache names.
manifestFilename, serviceWorker.filename, serviceWorker.registrationFilenameOptional output-name overrides for the manifest, worker, and registration module.

The default output names are manifest.webmanifest, sw.js, and pwa-register.js. The plugin copies the offline page and icons, then injects the manifest, theme color, mobile-app metadata, Apple icon, and registration module into the root HTML document.

Choose Launch Defaults in the App​

Installation does not decide which product or layout expression to start. Choose a stable startUrl in the manifest, including any app and expression parameters that the product requires.

If a plain / discovery URL should use the same product defaults before the app is installed, apply those defaults in the app's platform-web entry or app initializer before calling getHosannaWebLaunchSearch. Hosanna deliberately does not choose an app ID or expression for the host.

pwa=true (or pwa=1) is an explicit capability hint that selects the PWA runtime while the app is open in a normal browser tab. An installed launch is detected automatically through standalone display mode, including the iOS standalone signal. pwa=false or pwa=0 explicitly keeps browser-web capabilities even in a standalone host.

The expression remains an app-owned presentation choice. A PWA can use web, phone, tablet, or tv; installation does not force a phone layout.

Installed Runtime Behavior​

Without a device preset or explicit design dimensions, a PWA uses live window.innerWidth and window.innerHeight as its Hosanna design surface. DPR supplies the default physical DPI. Resize and physical-orientation changes refresh the root scene and publish the current design resolution without rebuilding an unchanged view tree.

Explicit emulator URLs retain their fixed contract. Supplying device, designWidth, designHeight, displayWidth, displayHeight, width, or height prevents the live viewport from replacing those dimensions.

The Web lifecycle service reports visible documents as active, hidden documents as background, and exposes the full current URL as launch information. Use the shared lifecycle and device services in product code instead of adding direct document or window listeners to views.

See Browser Expression, DPI, and Touch Input for the complete geometry and input contract.

Cache and Update Policy​

The generated worker applies a deliberately narrow cache policy:

  • The offline page and manifest icons are precached.
  • Navigations are network-first and fall back to the offline page only when the network fails.
  • Only exact content-hashed files from the current Vite bundle are runtime-cached.
  • /api, /assets/meta/, /health, and /configurator are network-only by default.
  • Streaming manifests, video, audio, segments, subtitles, and the app's additional networkOnly prefixes bypass caches.
  • Requests must be same-origin, GET, and inside the configured scope before the worker considers them.

The cache version includes the manifest, offline assets, caching policy, and emitted bundle names and contents. Activation removes older caches with the same product prefix. The worker does not call skipWaiting or force a reload; the app can decide when to offer an update.

The registration module dispatches these window events:

EventDetail
hosanna:pwa-registrationRegistration succeeded; detail.registration contains the ServiceWorkerRegistration.
hosanna:pwa-update-availableA new worker reached installed while an older worker controls the page; detail.registration contains the registration.
hosanna:pwa-registration-errorRegistration failed; detail.error contains the error.

Host the Build​

Service workers require HTTPS in production; browsers also allow localhost for development. Serve the exact production build and apply these rules before the SPA fallback:

  1. Keep /, /index.html, /offline.html, /manifest.webmanifest, /sw.js, /pwa-register.js, icons, and runtime config revalidatable rather than immutable.
  2. Serve the manifest as application/manifest+json.
  3. Serve the worker from a path that covers the requested scope. If the hosting layout requires a broader scope, return the appropriate Service-Worker-Allowed header.
  4. Give content-hashed Vite assets long-lived immutable caching.
  5. Route APIs, health checks, media, and other network-only endpoints before the static SPA fallback.
  6. Keep credentials and confidential config out of browser bundles, manifests, workers, and offline assets.

Verify Before Release​

After the production build, serve its output through the release hosting configuration and verify:

  1. The manifest, registration module, worker, offline page, and every declared icon load with the intended MIME type and cache headers.
  2. The browser reports the app as installable and registers a worker with the expected scope.
  3. A reload is controlled by the worker and a failed navigation shows the offline page with its icon available.
  4. Hashed shell assets enter the runtime cache, while API, runtime-config, registration, and media URLs do not.
  5. Plain-browser, installed-window, resize, rotation, touch, keyboard, and explicit emulator launches keep the expected expression and dimensions.
  6. A new build reports an update without interrupting the currently controlled session.

Installability and an offline fallback do not replace normal Browser release tests. Continue to validate AppConfig, routes, networking, accessibility, media, storage, and target browsers.

Talk to us