feat(ui): add PWA support with vite-plugin-pwa
- Add vite.config.pwa.ts extending the base config with VitePWA plugin - Generate PWA icons at build time from source logo via @vite-pwa/assets-generator - Add web app manifest with name, theme color, display overrides - Add Workbox runtime caching: NetworkFirst for API, CacheFirst for assets - Set navigateFallback to null to preserve server-side auth redirects - Server build uses build:pwa for PWA-enabled output; Electron/Tauri use the base build without PWA Signed-off-by: Jesper Derehag <jderehag@hotmail.com>
This commit is contained in:
@@ -62,6 +62,17 @@ You can configure the server using flags or environment variables:
|
|||||||
Use this only when access is already protected by another layer (SSO proxy, VPN, Coder workspace auth, etc.).
|
Use this only when access is already protected by another layer (SSO proxy, VPN, Coder workspace auth, etc.).
|
||||||
If you bind to `0.0.0.0` while skipping auth, anyone who can reach the port can access the API.
|
If you bind to `0.0.0.0` while skipping auth, anyone who can reach the port can access the API.
|
||||||
|
|
||||||
|
### Progressive Web App (PWA)
|
||||||
|
When running as a server CodeNomad can also be installed as a PWA from any supported browser, giving you a native app experience just like the Electron installation but executing on the remote server instead.
|
||||||
|
|
||||||
|
1. Open the CodeNomad UI in a Chromium-based browser (Chrome, Edge, Brave, etc.).
|
||||||
|
2. Click the install icon in the address bar, or use the browser menu → "Install CodeNomad".
|
||||||
|
3. The app will open in a standalone window and appear in your OS app list.
|
||||||
|
|
||||||
|
> **TLS requirement**
|
||||||
|
> Browsers require a secure (`https://`) connection for PWA installation.
|
||||||
|
> If you host CodeNomad on a remote machine, serve it behind a reverse proxy (e.g. Caddy, nginx) with a valid TLS certificate — self-signed certificates will not work.
|
||||||
|
|
||||||
### Data Storage
|
### Data Storage
|
||||||
- **Config**: `~/.config/codenomad/config.json`
|
- **Config**: `~/.config/codenomad/config.json`
|
||||||
- **Instance Data**: `~/.config/codenomad/instances` (chat history, etc.)
|
- **Instance Data**: `~/.config/codenomad/instances` (chat history, etc.)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build:ui && npm run prepare-ui && tsc -p tsconfig.json && node ./scripts/copy-auth-pages.mjs && npm run prepare-config",
|
"build": "npm run build:ui && npm run prepare-ui && tsc -p tsconfig.json && node ./scripts/copy-auth-pages.mjs && npm run prepare-config",
|
||||||
"build:ui": "npm run build --prefix ../ui",
|
"build:ui": "npm run build:pwa --prefix ../ui",
|
||||||
"prepare-ui": "node ./scripts/copy-ui-dist.mjs",
|
"prepare-ui": "node ./scripts/copy-ui-dist.mjs",
|
||||||
"prepare-config": "node ./scripts/copy-opencode-config.mjs",
|
"prepare-config": "node ./scripts/copy-opencode-config.mjs",
|
||||||
"dev": "cross-env CODENOMAD_DEV=1 CODENOMAD_SERVER_PASSWORD=codenomad-dev CLI_UI_DEV_SERVER=http://localhost:3000 tsx src/index.ts",
|
"dev": "cross-env CODENOMAD_DEV=1 CODENOMAD_SERVER_PASSWORD=codenomad-dev CLI_UI_DEV_SERVER=http://localhost:3000 tsx src/index.ts",
|
||||||
|
|||||||
1
packages/ui/.gitignore
vendored
1
packages/ui/.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
.vite/
|
.vite/
|
||||||
|
src/renderer/public/logo.png
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
"build:pwa": "vite build -c vite.config.pwa.ts",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"typecheck": "tsc --noEmit -p tsconfig.json"
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
||||||
},
|
},
|
||||||
@@ -30,11 +31,13 @@
|
|||||||
"solid-toast": "^0.5.0"
|
"solid-toast": "^0.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@vite-pwa/assets-generator": "^1.0.2",
|
||||||
"autoprefixer": "10.4.21",
|
"autoprefixer": "10.4.21",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
"tailwindcss": "3",
|
"tailwindcss": "3",
|
||||||
"typescript": "^5.3.0",
|
"typescript": "^5.3.0",
|
||||||
"vite": "^5.0.0",
|
"vite": "^5.0.0",
|
||||||
|
"vite-plugin-pwa": "^1.2.0",
|
||||||
"vite-plugin-solid": "^2.10.0"
|
"vite-plugin-solid": "^2.10.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
61
packages/ui/vite.config.pwa.ts
Normal file
61
packages/ui/vite.config.pwa.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { copyFileSync } from "fs"
|
||||||
|
import { defineConfig } from "vite"
|
||||||
|
import { VitePWA } from "vite-plugin-pwa"
|
||||||
|
import { resolve } from "path"
|
||||||
|
import baseConfig from "./vite.config"
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
...baseConfig,
|
||||||
|
plugins: [
|
||||||
|
...(baseConfig.plugins ?? []),
|
||||||
|
{
|
||||||
|
name: "copy-pwa-source-icon",
|
||||||
|
buildStart() {
|
||||||
|
// vite-pwa-assets requires the source image inside public/
|
||||||
|
copyFileSync(
|
||||||
|
resolve(__dirname, "src/images/CodeNomad-Icon.png"),
|
||||||
|
resolve(__dirname, "src/renderer/public/logo.png"),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VitePWA({
|
||||||
|
registerType: "autoUpdate",
|
||||||
|
injectRegister: "auto",
|
||||||
|
pwaAssets: {
|
||||||
|
preset: "minimal-2023",
|
||||||
|
image: "public/logo.png",
|
||||||
|
},
|
||||||
|
manifest: {
|
||||||
|
name: "CodeNomad",
|
||||||
|
short_name: "CodeNomad",
|
||||||
|
id: "/",
|
||||||
|
start_url: "/",
|
||||||
|
display: "standalone",
|
||||||
|
display_override: ["window-controls-overlay", "standalone"],
|
||||||
|
background_color: "#1a1a1a",
|
||||||
|
theme_color: "#1a1a1a",
|
||||||
|
},
|
||||||
|
workbox: {
|
||||||
|
navigateFallback: null,
|
||||||
|
runtimeCaching: [
|
||||||
|
{
|
||||||
|
urlPattern: /^\/api\/.*$/i,
|
||||||
|
handler: "NetworkFirst",
|
||||||
|
options: {
|
||||||
|
cacheName: "api-cache",
|
||||||
|
expiration: { maxEntries: 50, maxAgeSeconds: 60 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlPattern: /.*\.(?:js|css|png|jpg|jpeg|svg|webp|woff2?)$/i,
|
||||||
|
handler: "CacheFirst",
|
||||||
|
options: {
|
||||||
|
cacheName: "asset-cache",
|
||||||
|
expiration: { maxEntries: 200, maxAgeSeconds: 60 * 60 * 24 * 30 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user