mirror of
https://github.com/lotsoftick/openclaw_client.git
synced 2026-08-14 00:48:07 +00:00
174 lines
6.6 KiB
TypeScript
174 lines
6.6 KiB
TypeScript
import { defineConfig, loadEnv, type Plugin } from 'vite'
|
|
import react from '@vitejs/plugin-react'
|
|
import { VitePWA } from 'vite-plugin-pwa'
|
|
|
|
/**
|
|
* Dev-only middleware that hands back a "killswitch" service worker at /sw.js.
|
|
*
|
|
* Problem it solves: the packaged `openclaw_client start` server registers a
|
|
* real PWA service worker that precaches the built bundle. Later, when you
|
|
* stop that service and run `npm run dev` on the same origin, the browser
|
|
* still has the old SW registered and keeps serving stale /assets/*.js
|
|
* chunks from its precache — dev edits (and redux-logger) never appear
|
|
* until you hard-reload or manually unregister.
|
|
*
|
|
* On every navigation the browser refetches /sw.js to check for updates. By
|
|
* responding with a different script here, we force the browser to install
|
|
* this new SW, which immediately unregisters itself, wipes all caches, and
|
|
* navigates every open tab back to its current URL — so the next load hits
|
|
* Vite cleanly with no further user action.
|
|
*
|
|
* Scoped to dev only (`apply: 'serve'`); the plugin is inert during `build`,
|
|
* so the real Workbox-generated /sw.js ships in production.
|
|
*/
|
|
function serviceWorkerKillswitch(): Plugin {
|
|
const SW_SOURCE = `/* Auto-generated by vite.config.ts: replaces any stale PWA service
|
|
* worker left over from a previous \`openclaw_client start\` run. On
|
|
* activation it wipes all caches, unregisters itself, and force-navigates
|
|
* every open tab so the next load hits Vite cleanly. */
|
|
self.addEventListener('install', () => { self.skipWaiting(); });
|
|
self.addEventListener('activate', (event) => {
|
|
event.waitUntil((async () => {
|
|
try {
|
|
const keys = await caches.keys();
|
|
await Promise.all(keys.map((k) => caches.delete(k)));
|
|
} catch (_e) { /* best effort */ }
|
|
try { await self.registration.unregister(); } catch (_e) { /* best effort */ }
|
|
const clients = await self.clients.matchAll({ type: 'window' });
|
|
for (const client of clients) {
|
|
try { client.navigate(client.url); } catch (_e) { /* ignore */ }
|
|
}
|
|
})());
|
|
});
|
|
`;
|
|
return {
|
|
name: 'openclaw:sw-killswitch',
|
|
apply: 'serve',
|
|
configureServer(server) {
|
|
server.middlewares.use('/sw.js', (_req, res) => {
|
|
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
res.setHeader('Cache-Control', 'no-store');
|
|
res.setHeader('Service-Worker-Allowed', '/');
|
|
res.end(SW_SOURCE);
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
// https://vite.dev/config/
|
|
export default defineConfig(({ mode }) => {
|
|
const env = loadEnv(mode, process.cwd(), '')
|
|
const port =
|
|
Number(process.env.CLIENT_PORT) ||
|
|
Number(env.CLIENT_PORT) ||
|
|
Number(env.VITE_CLIENT_PORT) ||
|
|
18800
|
|
|
|
return {
|
|
plugins: [
|
|
react(),
|
|
serviceWorkerKillswitch(),
|
|
VitePWA({
|
|
registerType: 'autoUpdate',
|
|
includeAssets: [
|
|
'logo_128.png',
|
|
'icons/apple-touch-icon.png',
|
|
'icons/icon-192.png',
|
|
'icons/icon-512.png',
|
|
'icons/icon-512-maskable.png',
|
|
],
|
|
manifest: {
|
|
name: 'OpenClaw Client',
|
|
short_name: 'OpenClaw',
|
|
description: 'Chat interface for OpenClaw AI agents',
|
|
theme_color: '#0b0b0b',
|
|
background_color: '#0b0b0b',
|
|
display: 'standalone',
|
|
orientation: 'any',
|
|
start_url: '/',
|
|
scope: '/',
|
|
icons: [
|
|
{
|
|
src: '/icons/icon-192.png',
|
|
sizes: '192x192',
|
|
type: 'image/png',
|
|
purpose: 'any',
|
|
},
|
|
{
|
|
src: '/icons/icon-512.png',
|
|
sizes: '512x512',
|
|
type: 'image/png',
|
|
purpose: 'any',
|
|
},
|
|
{
|
|
src: '/icons/icon-512-maskable.png',
|
|
sizes: '512x512',
|
|
type: 'image/png',
|
|
purpose: 'maskable',
|
|
},
|
|
],
|
|
},
|
|
workbox: {
|
|
// workbox-build 7.x + terser has a known hang during SW minification
|
|
// (https://github.com/GoogleChrome/workbox/issues/3245). The SW is
|
|
// already small (<20KB), so skipping minification is a safe tradeoff.
|
|
mode: 'development',
|
|
// Precache static build output (JS, CSS, fonts, images) but
|
|
// **not** index.html. The shell is fetched fresh on every
|
|
// load so:
|
|
// 1. `window.__OPENCLAW_CONFIG__` (injected by serve.mjs
|
|
// from `req.headers.host`) always matches the page's
|
|
// hostname — critical when accessing the same install
|
|
// via localhost, LAN IP, and Tailscale.
|
|
// 2. After an `openclaw_client update`, the new
|
|
// hashed-asset references in index.html are picked up
|
|
// immediately instead of clients pinning to the old
|
|
// precached shell until the SW happens to update.
|
|
globPatterns: ['**/*.{js,css,svg,png,ico,woff2}'],
|
|
globIgnores: ['**/index.html', 'index.html'],
|
|
// The main app bundle currently weighs ~3.4MB unminified due to MUI + markdown.
|
|
// 6MB headroom avoids nuisance build failures when it grows slightly.
|
|
maximumFileSizeToCacheInBytes: 6 * 1024 * 1024,
|
|
// No `navigateFallback` — every SPA route is fetched from
|
|
// the network so updated index.html ships immediately. The
|
|
// tradeoff: the very first navigation after going offline
|
|
// returns whatever the browser cached on the previous load.
|
|
navigateFallback: null,
|
|
navigateFallbackDenylist: [/^\/api/, /^\/ws/],
|
|
runtimeCaching: [
|
|
{
|
|
// API is on a different origin (port 18802) but be explicit
|
|
// in case anyone proxies everything through one host.
|
|
urlPattern: ({ url }) => url.pathname.startsWith('/api/'),
|
|
handler: 'NetworkOnly',
|
|
},
|
|
{
|
|
urlPattern: ({ url }) => url.pathname.startsWith('/ws'),
|
|
handler: 'NetworkOnly',
|
|
},
|
|
],
|
|
cleanupOutdatedCaches: true,
|
|
},
|
|
devOptions: {
|
|
// Keep the SW off during `npm run dev` — otherwise its
|
|
// navigateFallback intercepts Vite's ESM requests (/main.tsx,
|
|
// /@react-refresh, /@vite/client, /manifest.webmanifest) and
|
|
// returns index.html, breaking HMR with MIME-type errors.
|
|
// Test install/offline via `npm run build && npm run preview`.
|
|
enabled: false,
|
|
type: 'module',
|
|
navigateFallback: 'index.html',
|
|
},
|
|
}),
|
|
],
|
|
server: {
|
|
port,
|
|
host: '0.0.0.0',
|
|
},
|
|
preview: {
|
|
port,
|
|
host: '0.0.0.0',
|
|
},
|
|
}
|
|
})
|