101 lines
3.2 KiB
JavaScript
101 lines
3.2 KiB
JavaScript
import { createHash } from 'crypto';
|
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
import { dirname } from 'path';
|
|
|
|
export const BOOTSTRAP_MANIFEST_FILTER = 'all vector manifest entries except maps.googleapis.com/maps/vt tile payloads';
|
|
|
|
function escapeRegExp(value) {
|
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
}
|
|
|
|
export function isVectorTilePayloadUrl(rawUrl) {
|
|
try {
|
|
const url = new URL(rawUrl);
|
|
return (
|
|
url.host === 'maps.googleapis.com'
|
|
&& (
|
|
(url.pathname === '/maps/vt' && url.searchParams.has('pb'))
|
|
|| url.pathname.startsWith('/maps/vt/pb=')
|
|
)
|
|
);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function makeVectorBootstrapEntries(entries) {
|
|
// bootstrap-manifest.json is derived from the full vector manifest. It keeps
|
|
// startup resources such as SDK chunks, fonts, styles, configs, and vt icons,
|
|
// but excludes /maps/vt tile payloads so service worker install never has to
|
|
// parse the full 50k+ entry vector manifest.
|
|
return (entries || [])
|
|
.filter((entry) => entry?.url && entry?.file && !isVectorTilePayloadUrl(entry.url))
|
|
.sort((a, b) => a.url.localeCompare(b.url));
|
|
}
|
|
|
|
export function makeBootstrapCacheName(entries) {
|
|
const seed = entries
|
|
.map((entry) => [
|
|
entry.url,
|
|
entry.file,
|
|
entry.status,
|
|
entry.size,
|
|
entry.bodyHash,
|
|
].join('\t'))
|
|
.join('\n');
|
|
const hash = createHash('sha1').update(seed).digest('hex').slice(0, 12);
|
|
return `map-bootstrap-cache-${hash}`;
|
|
}
|
|
|
|
export function writeVectorBootstrapManifest({
|
|
entries,
|
|
outputPath,
|
|
source,
|
|
sourceCacheName,
|
|
}) {
|
|
const bootstrapEntries = makeVectorBootstrapEntries(entries);
|
|
const cacheName = makeBootstrapCacheName(bootstrapEntries);
|
|
const manifest = {
|
|
version: 1,
|
|
source,
|
|
sourceCacheName,
|
|
cacheName,
|
|
filter: BOOTSTRAP_MANIFEST_FILTER,
|
|
entries: bootstrapEntries,
|
|
};
|
|
mkdirSync(dirname(outputPath), { recursive: true });
|
|
writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
|
|
return { cacheName, entries: bootstrapEntries, manifest };
|
|
}
|
|
|
|
export function updateServiceWorkerCacheName({
|
|
swPath,
|
|
constName,
|
|
cacheName,
|
|
prefix,
|
|
logPrefix,
|
|
preserveExistingSuffix = true,
|
|
}) {
|
|
if (!existsSync(swPath)) {
|
|
console.warn(`${logPrefix} 未找到 ${swPath},无法同步 ${constName}`);
|
|
return false;
|
|
}
|
|
const src = readFileSync(swPath, 'utf-8');
|
|
const pattern = new RegExp(`const ${constName} = '(${escapeRegExp(prefix)}[0-9a-f]{12})([^']*)';`);
|
|
let nextCacheName = cacheName;
|
|
const next = src.replace(pattern, (_match, _oldCacheName, suffix) => {
|
|
// The manifest hash captures data changes; any suffix after the hash is a
|
|
// code-level invalidation marker such as "-swiftshader-v2". Preserve it so
|
|
// data refresh scripts do not accidentally re-enable stale patched caches.
|
|
nextCacheName = preserveExistingSuffix ? `${cacheName}${suffix || ''}` : cacheName;
|
|
return `const ${constName} = '${nextCacheName}';`;
|
|
});
|
|
if (next === src) {
|
|
console.warn(`${logPrefix} 未能在 map-sw.js 中找到 ${constName},跳过同步`);
|
|
return false;
|
|
}
|
|
writeFileSync(swPath, next);
|
|
console.log(`${logPrefix} map-sw.js ${constName} -> ${nextCacheName}`);
|
|
return true;
|
|
}
|