refactor: Use stable cache name with incremental asset updates
Instead of invalidating the entire cache on every deployment (via git hash
in cache name), use a stable cache name and leverage Vite's content hashes
in filenames.
Changes:
- Use fixed cache name 'chatwoot-assets-v1' instead of 'chatwoot-{git-hash}'
- Add cleanupStaleAssets() to remove old assets not in current manifest
- Unchanged files (same content hash) stay cached across deployments
- Only new/changed files are fetched
- Remove unused getCacheVersion() and CACHE_VERSION references
This significantly reduces bandwidth on deployments since most assets
don't change between releases.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
0d21a711e7
commit
38232a73dc
@@ -1,12 +1,13 @@
|
||||
/* eslint-disable no-restricted-globals, no-use-before-define, no-undef */
|
||||
|
||||
// Build-time injected values (replaced by Vite define at build time)
|
||||
const CACHE_VERSION = __CACHE_VERSION__;
|
||||
const ASSET_ORIGIN = __ASSET_ORIGIN__;
|
||||
const ASSET_PATH = __ASSET_PATH__;
|
||||
const ASSET_MANIFEST = __ASSET_MANIFEST__;
|
||||
|
||||
const CACHE_NAME = `chatwoot-${CACHE_VERSION}`;
|
||||
// Stable cache name - Vite includes content hashes in filenames,
|
||||
// so unchanged files keep the same URL and don't need refetching
|
||||
const CACHE_NAME = 'chatwoot-assets-v1';
|
||||
|
||||
// Paths that should never be cached (API, auth, real-time, etc.)
|
||||
const EXCLUDED_PATHS = [
|
||||
@@ -153,24 +154,45 @@ self.addEventListener('install', () => self.skipWaiting());
|
||||
self.addEventListener('activate', event => {
|
||||
event.waitUntil(
|
||||
Promise.all([
|
||||
// Delete old caches
|
||||
caches
|
||||
.keys()
|
||||
.then(names =>
|
||||
Promise.all(
|
||||
names
|
||||
.filter(
|
||||
name => name.startsWith('chatwoot-') && name !== CACHE_NAME
|
||||
)
|
||||
.map(name => caches.delete(name))
|
||||
)
|
||||
),
|
||||
// Clean up stale assets not in current manifest
|
||||
cleanupStaleAssets(),
|
||||
// Take control of all clients
|
||||
self.clients.claim(),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Remove cached assets that aren't in the current manifest.
|
||||
* This keeps unchanged files (same content hash) while removing old versions.
|
||||
*/
|
||||
async function cleanupStaleAssets() {
|
||||
if (!ASSET_MANIFEST?.length) return;
|
||||
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
const cachedRequests = await cache.keys();
|
||||
const baseUrl = ASSET_ORIGIN || `${self.location.origin}${ASSET_PATH}`;
|
||||
|
||||
// Build set of valid asset URLs from current manifest
|
||||
const validUrls = new Set(
|
||||
ASSET_MANIFEST.map(asset => `${baseUrl}${asset.url}`)
|
||||
);
|
||||
|
||||
// Delete cached entries that are assets but not in current manifest
|
||||
const deletions = cachedRequests
|
||||
.filter(request => {
|
||||
const url = new URL(request.url);
|
||||
const isAssetPath =
|
||||
url.pathname.startsWith('/packs/assets/') ||
|
||||
url.pathname.startsWith('/vite-dev/assets/');
|
||||
// Only clean up asset files, keep other cached items (like /app shell)
|
||||
return isAssetPath && !validUrls.has(request.url);
|
||||
})
|
||||
.map(request => cache.delete(request));
|
||||
|
||||
await Promise.all(deletions);
|
||||
}
|
||||
|
||||
self.addEventListener('message', async event => {
|
||||
if (event.data?.type === 'PREFETCH_ASSETS') {
|
||||
await prefetchAssets();
|
||||
|
||||
@@ -2,29 +2,8 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
const { build } = require('vite');
|
||||
|
||||
/**
|
||||
* Get the cache version for service worker
|
||||
* Priority: CHATWOOT_VERSION env var > git commit hash > timestamp
|
||||
*/
|
||||
function getCacheVersion() {
|
||||
if (process.env.CHATWOOT_VERSION) {
|
||||
return process.env.CHATWOOT_VERSION;
|
||||
}
|
||||
|
||||
try {
|
||||
const gitHash = execSync('git rev-parse --short HEAD', {
|
||||
encoding: 'utf8',
|
||||
}).trim();
|
||||
return `git-${gitHash}`;
|
||||
} catch (error) {
|
||||
console.warn('Could not get git hash, using timestamp');
|
||||
return `v${Date.now()}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate asset manifest from Vite build output
|
||||
* Returns array of { url, revision } for JS/CSS files
|
||||
@@ -75,13 +54,11 @@ function generateAssetManifest() {
|
||||
async function buildServiceWorker() {
|
||||
console.log('🔨 Building service worker...');
|
||||
|
||||
const cacheVersion = getCacheVersion();
|
||||
const assetOrigin = process.env.ASSET_CDN_HOST || '';
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
// In production assets are in /packs/, in development they're in /vite-dev/
|
||||
const assetPath = isProduction ? '/packs/' : '/vite-dev/';
|
||||
|
||||
console.log(`📦 Cache version: ${cacheVersion}`);
|
||||
console.log(`🌐 Asset origin: ${assetOrigin || '(local)'}`);
|
||||
console.log(`📁 Asset path: ${assetPath}`);
|
||||
console.log(`🏭 Environment: ${isProduction ? 'production' : 'development'}`);
|
||||
@@ -144,7 +121,6 @@ async function buildServiceWorker() {
|
||||
},
|
||||
},
|
||||
define: {
|
||||
__CACHE_VERSION__: JSON.stringify(cacheVersion),
|
||||
__ASSET_ORIGIN__: JSON.stringify(assetOrigin),
|
||||
__ASSET_PATH__: JSON.stringify(assetPath),
|
||||
__ASSET_MANIFEST__: JSON.stringify(assetManifest),
|
||||
@@ -161,7 +137,7 @@ async function buildServiceWorker() {
|
||||
'utf8'
|
||||
);
|
||||
|
||||
// Read the push handlers template
|
||||
// Read the push handlers
|
||||
const pushHandlers = fs.readFileSync(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
@@ -170,21 +146,14 @@ async function buildServiceWorker() {
|
||||
'utf8'
|
||||
);
|
||||
|
||||
// Replace version placeholder in push handlers
|
||||
const processedHandlers = pushHandlers.replace(
|
||||
/__CACHE_VERSION__/g,
|
||||
cacheVersion
|
||||
);
|
||||
|
||||
// Combine them
|
||||
const finalServiceWorker = `/* Service Worker for Chatwoot - Generated at build time */
|
||||
/* Cache version: ${cacheVersion} */
|
||||
/* Generated: ${new Date().toISOString()} */
|
||||
/* Precached assets: ${assetManifest.length} */
|
||||
|
||||
${runtimeBundle}
|
||||
|
||||
${processedHandlers}
|
||||
${pushHandlers}
|
||||
`;
|
||||
|
||||
// Write to public/sw.js
|
||||
|
||||
Reference in New Issue
Block a user