Skip to content

Create shared PWA package for OpenPhysics sims #6

Description

@veillette

Summary

Most OpenPhysics sims duplicate the same PWA setup: Vite PWA config, manifest defaults, security headers, icon generation, and icon-related dependencies. Create a shared npm package, likely @openphysics/sim-pwa, to centralize that setup while leaving each sim responsible only for its app-specific metadata and source SVG icon.

This should reduce per-sim boilerplate, make future PWA/security header changes easier to roll out, and avoid committing generated icon assets where possible.

Current Repetition

Representative sims currently duplicate:

  • vite-plugin-pwa setup in vite.config.ts
  • securityHeaders for dev server and preview server
  • manifest defaults:
    • display: "standalone"
    • orientation: "landscape"
    • standard icon entries
    • includeAssets: ["favicon.ico", "icons/apple-touch-icon.png"]
  • Workbox defaults:
    • maximumFileSizeToCacheInBytes: 12 * 1024 * 1024
    • common glob patterns for JS/CSS/HTML/SVG/PNG/WOFF2
  • icon-generation script based on sharp and png-to-ico
  • direct dev dependencies on sharp, png-to-ico, tsx, and vite-plugin-pwa

Most sims already keep the real source asset at:

public/icons/icon.svg

The generated assets can be recreated from that SVG:

public/icons/apple-touch-icon.png
public/icons/icon-192.png
public/icons/icon-512.png
public/favicon.ico

Proposed Package

Create a package such as @openphysics/sim-pwa.

The package should provide:

  • openPhysicsPWA(options) — wraps VitePWA(...) with shared OpenPhysics defaults.
  • securityHeaders — shared COOP/COEP/CSP headers used by Vite dev and preview.
  • optional openPhysicsSimConfig(options) — a convenience helper that returns common Vite config.
  • openphysics-pwa icons — CLI command that generates PNG/ICO assets from the source SVG.

Safe Boundary

This package should stay at the Vite/PWA/build tooling layer.

It should affect:

  • vite.config.ts
  • generated PWA manifest config
  • Workbox/service worker config
  • icon generation
  • dev/preview security headers

It should not affect:

  • src/main.ts
  • src/brand.ts
  • src/splash.ts
  • src/assert.ts
  • src/init.ts
  • SceneryStack runtime startup order

SceneryStack Bootstrap Constraint

SceneryStack setup is sensitive to import order. Each sim must keep this import as the first import in src/main.ts:

import "./brand.js";

That preserves the existing bootstrap chain:

brand.ts -> splash.ts -> assert.ts -> init.ts

The shared PWA package should not try to abstract or reorder this startup path.

Package API Sketch

Example target usage in a sim:

import { defineConfig } from "vite";
import { openPhysicsPWA, securityHeaders } from "@openphysics/sim-pwa";

export default defineConfig({
  base: "./",
  build: {
    target: "es2024",
  },
  server: {
    headers: securityHeaders,
  },
  preview: {
    headers: securityHeaders,
  },
  plugins: [
    openPhysicsPWA({
      name: "Doppler Effect",
      shortName: "Doppler",
      description: "An interactive simulation of the Doppler Effect using SceneryStack.",
      themeColor: "#2575ba",
    }),
  ],
});

For sims with additional offline assets, allow extensions:

openPhysicsPWA({
  name: "Wave Composer",
  shortName: "WaveComposer",
  description: "A real-time voice-analysis simulation.",
  themeColor: "#1a1a2e",
  extraGlobPatterns: ["**/*.{wav,mp3,ogg}"],
});

Or allow a Workbox override:

openPhysicsPWA({
  name: "Wave Composer",
  shortName: "WaveComposer",
  description: "A real-time voice-analysis simulation.",
  themeColor: "#1a1a2e",
  workbox: {
    globPatterns: ["**/*.{js,css,html,svg,png,woff2,wav,mp3,ogg}"],
  },
});

Manifest Defaults

The package should generate this standard manifest shape:

{
  name,
  short_name: shortName,
  description,
  theme_color: themeColor,
  background_color: backgroundColor ?? "#000000",
  display: "standalone",
  orientation: "landscape",
  icons: [
    {
      src: "icons/icon-192.png",
      sizes: "192x192",
      type: "image/png",
    },
    {
      src: "icons/icon-512.png",
      sizes: "512x512",
      type: "image/png",
    },
    {
      src: "icons/icon.svg",
      sizes: "any",
      type: "image/svg+xml",
      purpose: "maskable",
    },
  ],
}

Icon CLI

The package should expose a bin command:

{
  "bin": {
    "openphysics-pwa": "./dist/cli.js"
  }
}

CLI usage:

openphysics-pwa icons

Default behavior:

  • read public/icons/icon.svg
  • write public/icons/apple-touch-icon.png at 180x180
  • write public/icons/icon-192.png at 192x192
  • write public/icons/icon-512.png at 512x512
  • write public/favicon.ico with 16/32/48/64 PNG layers

Potential options:

openphysics-pwa icons --source public/icons/icon.svg
openphysics-pwa icons --public-dir public
openphysics-pwa icons --check

--check could fail if generated assets are missing or stale, useful for CI later.

NPM Script Setup

Each sim can replace its local generator script with package-provided commands.

Minimal setup:

{
  "scripts": {
    "icons": "openphysics-pwa icons",
    "build": "npm run icons && tsc && vite build"
  },
  "devDependencies": {
    "@openphysics/sim-pwa": "^0.1.0"
  }
}

Keep existing dev/start behavior lightweight:

{
  "scripts": {
    "start": "vite",
    "dev": "vite",
    "preview": "vite preview"
  }
}

Recommended full script set after migration:

{
  "scripts": {
    "start": "vite",
    "dev": "vite",
    "build": "npm run icons && tsc && vite build",
    "preview": "vite preview",
    "lint": "biome check .",
    "format": "biome format --write .",
    "fix": "biome check --write .",
    "check": "tsc --noEmit && tsc -p tsconfig.scripts.json --noEmit",
    "icons": "openphysics-pwa icons",
    "clean": "rm -rf dist",
    "prepare": "git rev-parse --is-inside-work-tree >/dev/null 2>&1 && git config core.hooksPath .githooks || true"
  }
}

If generated icons are not committed, build should run npm run icons first. npm start can remain vite because dev mode can tolerate generating icons manually when needed.

Alternative if dev should always have fresh icons:

{
  "scripts": {
    "prestart": "npm run icons",
    "start": "vite",
    "predev": "npm run icons",
    "dev": "vite"
  }
}

However, the simpler default should probably be to generate icons during build, not every dev server start.

Dependency Strategy

The shared package can own:

  • sharp
  • png-to-ico
  • vite-plugin-pwa

Consider using peer dependencies for tooling that should remain aligned across sims:

  • vite
  • typescript

This avoids accidentally pulling multiple Vite versions into each sim.

After migration, individual sims should no longer need direct dependencies on:

  • sharp
  • png-to-ico
  • possibly vite-plugin-pwa, depending on package design

They may still need tsx if they have other TypeScript scripts. If generate-icons.ts is the only use, tsx can potentially be removed too.

Migration Plan

  1. Create @openphysics/sim-pwa package.
  2. Implement exported Vite helpers and the openphysics-pwa icons CLI.
  3. Migrate TemplateSingleSim first as the reference implementation.
  4. Validate:
    • npm run icons
    • npm run check
    • npm run build
    • npm start
  5. Migrate one representative real sim, such as DopplerEffect.
  6. Migrate a special-case sim such as WaveComposer to validate custom Workbox globs.
  7. Batch migrate the remaining sims.
  8. Remove duplicated scripts/generate-icons.ts files.
  9. Remove redundant direct dependencies from each sim.
  10. Update docs in TemplateSingleSim and shared org guidance.

Acceptance Criteria

  • A shared package exists and is usable from a sim repo.
  • TemplateSingleSim uses the shared package for PWA config and icon generation.
  • At least one real sim builds and starts after migration.
  • SceneryStack bootstrap imports remain unchanged.
  • Generated PNG/ICO assets can be recreated from public/icons/icon.svg.
  • The package supports sims with extra Workbox asset types.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions