feat: Add service worker for asset caching
- Add Workbox-based service worker to cache JS, CSS, and fonts - JS/CSS cached for 30 days, fonts for 1 year - Support for CDN assets via ASSET_CDN_HOST - Version-based cache invalidation on deployments - Automatic cache cleanup for old versions - Build service worker during asset precompilation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
8b230c6920
commit
d34df7c314
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
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()}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the service worker
|
||||
*/
|
||||
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';
|
||||
|
||||
console.log(`📦 Cache version: ${cacheVersion}`);
|
||||
console.log(`🌐 Asset origin: ${assetOrigin || '(local)'}`);
|
||||
console.log(`🏭 Environment: ${isProduction ? 'production' : 'development'}`);
|
||||
|
||||
// In development mode, just copy the push-handlers-only version
|
||||
if (!isProduction) {
|
||||
console.log(
|
||||
'⚠️ Development mode: Using push notifications only (no caching)'
|
||||
);
|
||||
|
||||
const devServiceWorker = fs.readFileSync(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'../app/javascript/service-worker/push-handlers-only.js'
|
||||
),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.resolve(__dirname, '../public/sw.js'),
|
||||
devServiceWorker
|
||||
);
|
||||
|
||||
console.log('✅ Development service worker created at public/sw.js');
|
||||
return;
|
||||
}
|
||||
|
||||
// Production mode: Build with Workbox runtime
|
||||
try {
|
||||
console.log('📦 Building Workbox runtime bundle...');
|
||||
|
||||
// Build the Workbox runtime bundle
|
||||
await build({
|
||||
configFile: false,
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [],
|
||||
},
|
||||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry: path.resolve(
|
||||
__dirname,
|
||||
'../app/javascript/service-worker/sw-runtime.js'
|
||||
),
|
||||
formats: ['iife'],
|
||||
name: 'WorkboxRuntime',
|
||||
fileName: () => 'sw-runtime.js',
|
||||
},
|
||||
outDir: path.resolve(__dirname, '../tmp/sw-build'),
|
||||
emptyOutDir: true,
|
||||
minify: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
inlineDynamicImports: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
define: {
|
||||
__CACHE_VERSION__: JSON.stringify(cacheVersion),
|
||||
__ASSET_ORIGIN__: JSON.stringify(assetOrigin),
|
||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||
},
|
||||
logLevel: 'warn',
|
||||
});
|
||||
|
||||
console.log('✅ Workbox runtime bundle built');
|
||||
|
||||
// Read the built runtime
|
||||
const runtimeBundle = fs.readFileSync(
|
||||
path.resolve(__dirname, '../tmp/sw-build/sw-runtime.js'),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
// Read the push handlers template
|
||||
const pushHandlers = fs.readFileSync(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'../app/javascript/service-worker/push-handlers.js'
|
||||
),
|
||||
'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()} */
|
||||
|
||||
${runtimeBundle}
|
||||
|
||||
${processedHandlers}
|
||||
`;
|
||||
|
||||
// Write to public/sw.js
|
||||
fs.writeFileSync(
|
||||
path.resolve(__dirname, '../public/sw.js'),
|
||||
finalServiceWorker
|
||||
);
|
||||
|
||||
console.log('✅ Service worker built successfully at public/sw.js');
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to build service worker:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the build
|
||||
buildServiceWorker().catch(err => {
|
||||
console.error('❌ Unhandled error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user