sw.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. const CACHE_NAME = 'markdown-viewer-cache-v3.7.4';
  2. // PERF-011: Split precache into critical (local files) and lazy (CDN libraries)
  3. // Critical assets are precached during SW install for instant offline startup
  4. const CRITICAL_ASSETS = [
  5. './',
  6. './index.html',
  7. './script.js',
  8. './preview-worker.js',
  9. './styles.css',
  10. './sample.md',
  11. './assets/icon.jpg',
  12. './manifest.json'
  13. ];
  14. // CDN assets are cached lazily on first use via runtime cache-first strategy
  15. // This prevents the SW install from downloading ~5.4 MB of CDN resources upfront
  16. const CDN_ORIGINS = [
  17. 'cdnjs.cloudflare.com',
  18. 'cdn.jsdelivr.net'
  19. ];
  20. const NETWORK_FIRST_LOCAL_PATHS = new Set([
  21. '/',
  22. '/index.html',
  23. '/script.js',
  24. '/preview-worker.js',
  25. '/styles.css',
  26. '/sw.js'
  27. ]);
  28. self.addEventListener('install', event => {
  29. event.waitUntil(
  30. caches.open(CACHE_NAME)
  31. .then(cache => cache.addAll(CRITICAL_ASSETS))
  32. .then(() => self.skipWaiting())
  33. );
  34. });
  35. self.addEventListener('activate', event => {
  36. event.waitUntil(
  37. caches.keys().then(keys => Promise.all(
  38. keys.map(key => {
  39. if (key !== CACHE_NAME) {
  40. return caches.delete(key);
  41. }
  42. })
  43. )).then(() => self.clients.claim())
  44. );
  45. });
  46. self.addEventListener('fetch', event => {
  47. const url = new URL(event.request.url);
  48. const isLocal = url.origin === self.location.origin;
  49. const isCDN = CDN_ORIGINS.some(origin => url.hostname.includes(origin));
  50. if (isLocal) {
  51. const localPath = url.pathname.endsWith('/') ? '/' : url.pathname;
  52. const shouldUseNetworkFirst =
  53. event.request.mode === 'navigate' || NETWORK_FIRST_LOCAL_PATHS.has(localPath);
  54. if (shouldUseNetworkFirst) {
  55. event.respondWith(
  56. caches.open(CACHE_NAME).then(cache => {
  57. return fetch(event.request).then(networkResponse => {
  58. if (networkResponse && networkResponse.status === 200) {
  59. cache.put(event.request, networkResponse.clone());
  60. }
  61. return networkResponse;
  62. }).catch(() => cache.match(event.request));
  63. })
  64. );
  65. return;
  66. }
  67. // Stale-While-Revalidate strategy for non-code local assets
  68. event.respondWith(
  69. caches.open(CACHE_NAME).then(cache => {
  70. return cache.match(event.request).then(cachedResponse => {
  71. const fetchPromise = fetch(event.request).then(networkResponse => {
  72. if (networkResponse && networkResponse.status === 200) {
  73. cache.put(event.request, networkResponse.clone());
  74. }
  75. return networkResponse;
  76. }).catch(err => {
  77. console.warn('Background fetch failed for:', event.request.url, err);
  78. });
  79. return cachedResponse || fetchPromise;
  80. });
  81. })
  82. );
  83. } else if (isCDN) {
  84. // Cache-First strategy for stable third-party CDN libraries
  85. // PERF-011: CDN resources are cached on first use (lazy) rather than precached
  86. event.respondWith(
  87. caches.match(event.request)
  88. .then(cachedResponse => {
  89. if (cachedResponse) {
  90. return cachedResponse;
  91. }
  92. return fetch(event.request).then(response => {
  93. if (response && response.status === 200) {
  94. const responseToCache = response.clone();
  95. caches.open(CACHE_NAME).then(cache => {
  96. cache.put(event.request, responseToCache);
  97. });
  98. }
  99. return response;
  100. });
  101. })
  102. );
  103. } else {
  104. // Network-only for non-CDN external requests
  105. event.respondWith(fetch(event.request));
  106. }
  107. });