ToB企服应用市场:ToB评测及商务社交产业平台

标题: Service Work离线体验与性能优化 [打印本页]

作者: 梦见你的名字    时间: 2025-1-13 03:38
标题: Service Work离线体验与性能优化
Service Work离线体验与性能优化

一、什么是 Service Worker?

Service Worker是一种可编程的网络代理,允许你拦截和处理惩罚应用发出的全部网络请求,包括拦截和处理惩罚网络请求、管理缓存和处理惩罚推送关照等。它使得开辟者能够在客户端实现强盛的新功能,比如离线支持、推送关照以及缓存管理。Service Worker 运行在独立于主欣赏器进程的背景下,因此不会影响页面的性能。它还能够为网页或应用程序提供雷同于本地应用的特性,它的重要目的是增强 Web 应用的离线体验和性能。
二、 Service Worker的应用场景

三、Service Worker的特点


四、Service Worker的生命周期

当一个Service Worker被注册成功后,它将开始它的生命周期,我们对Service Worker的利用一般都是在其生命周期里面进行的。Service Worker的生命周期分为这么几个状态 安装中, 安装后, 激活中, 激活后, 废弃。

五、Service Worker的缓存机制

Service Worker 技术不可或缺的一个方面是 Cache 接口,它是一种完全独立于 HTTP 缓存的缓存机制。可在 Service Worker 作用域和主线程作用域内访问 Cache 接口。
HTTP缓存会受到 HTTP 标头中指定的缓存指令的影响,而 Cache 接口可通过 JavaScript 进行编程。这意味着,网络请求的响应可以基于最得当指定网站的任何逻辑。例如:

六、Service Worker的简朴素践

   首先,在开始使用 Service Worker 之前,你必要确保用户的欣赏器支持这项技术。Service Worker 对象存在于navigator对象下,可以通过以下代码来检查
  1. if ('serviceWorker' in navigator) {
  2.   // 浏览器支持 Service Worker
  3.   
  4. } else {
  5.   console.log('Service Worker not supported');
  6. }
复制代码
   注册是启动 Service Worker 的第一步。通常是在主应用中通过 JavaScript 来完成这个过程,在主线程中调用navigator.serviceWorker.register()方法来注册 Service Worker:
    register 方法接受两个参数
第一个参数表示ServiceWork.js相对于origin的路径
第二个参数是 Serivce Worker 的设置项,可选填,其中比力重要的是 scope 属性,用来指定你想让 service worker 控制的内容的目次。 默认值为servicework.js所在的目次。这个属性所表示的路径不能在 service worker 文件的路径之上,默认是 Serivce Worker 文件所在的目次。 成功注册或返回一个promise。
  1. if ('serviceWorker' in navigator) {
  2.   // 浏览器支持 Service Worker
  3.    window.addEventListener('load', () => {
  4.      navigator.serviceWorker
  5.           .register('/service-worker.js')
  6.           .then((registration) => {
  7.             console.log('Service Worker registered with scope:', registration.scope)
  8.           })
  9.           .catch((error) => {
  10.             console.error('Service Worker registration failed:', error)
  11.           })
  12. })
  13. } else {
  14.   console.log('Service Worker not supported');
  15. }
复制代码
  此代码在主线程上运行,并执行以下利用:
1、由于用户初次访问网站发生在没有注册的 Service Worker 的情况下, 等到页面完全加载后再注册一个。 这样可以在 Service Worker 预缓存任何内容时避免带宽争用。
2、进行快速检查有助于避免在不支持此功能的欣赏器出现错误。
3、当页面完全加载且支持 Service Worker 时,注册 /service-worker.js。
     安装变乱发生在 Service Worker 初次安装时,每个 Service Worker 仅调用一次 install,并且在更新之前不会再次触发。
  1. self.addEventListener('install', event => {
  2.   event.waitUntil(
  3.     caches.open('缓存版本ID').then(cache => {
  4.       return cache.addAll([
  5.         '/',
  6.                   ...
  7.       ]);
  8.     })
  9.   );
  10. });
复制代码
  此代码会创建一个新的 Cache 实例并预缓存资产。
此处重点关注:event.waitUntil变乱
event.waitUntil 接受 promise; 并等候该 promise 得到办理。 该 promise 执行两项异步利用:
创建名为 ‘缓存版本ID’ 的新 Cache 实例。
创建缓存后 资源网址数组使用其异步缓存资源预缓存 addAll 方法。
如果传递给 event.waitUntil 的 promise 已拒绝。 如果发生这种情况,Service Worker 会被丢弃。
     如果注册和安装成功, Service Worker 激活,并且其状态变为 ‘activating’ ,你可以在这里执行清理旧版本资源的利用:
  1. self.addEventListener('activate', event => {
  2.   const cacheWhitelist = ['缓存版本ID'];
  3.   event.waitUntil(
  4.     caches.keys().then(cacheNames => {
  5.       return Promise.all(
  6.         cacheNames.map(cacheName => {
  7.           if (cacheWhitelist.indexOf(cacheName) === -1) {
  8.             return caches.delete(cacheName);
  9.           }
  10.         })
  11.       );
  12.     })
  13.   );
  14. });
复制代码
   通过监听Service Worker的 fetch 变乱来拦截网络请求,
调用 event 上的 respondWith() 方法来劫持当前servicework控制域下的 HTTP 请求,该方法会直接返回一个Promise 效果 ,这个效果就会是http请求的响应。上面代码中就一个简朴的逻辑,先劫持http请求,然后看看缓存中是否有这个请求的资源,如果有则直接返回,如果没有就去请求服务器上的资源。 event.respondWith 方法只能在 Service Worker 的 fetch 变乱中使用。
  1. self.addEventListener('fetch', event => {
  2.   event.respondWith(
  3.     caches.match(event.request).then(response => {
  4.       if (response) {
  5.         console.log('Serving from cache:', event.request.url);
  6.         return response;
  7.       }
  8.       console.log('Fetching from network:', event.request.url);
  9.       return fetch(event.request).then(networkResponse => {
  10.         if (networkResponse && networkResponse.ok) {
  11.           console.log('Caching new response:', event.request.url);
  12.           return caches.open('f69905188ac970f1').then(cache => {
  13.             cache.put(event.request, networkResponse.clone());
  14.             return networkResponse;
  15.           });
  16.         }
  17.         throw new Error('Network response not ok');
  18.       }).catch(error => {
  19.         console.error('Fetch failed:', error);
  20.         throw error;
  21.       });
  22.     })
  23.   );
  24. });
复制代码
  
  七、Service Worker资源缓存-插件自动生成

   通过上述资料可知资料缓存必要设置缓存ID和所必要的缓存文件路径,而每次打包的文件名都是混淆之后的,人工写是非常不实际,以是我们可以通过插件帮我们自动生成Service Worker文件自动插入到dist目次下
    此处我是采用vite提供的插件钩子函数拿到构建后的文件列表,自动生成service-worker.js
  1. import path from 'path'
  2. import * as fs from 'fs'
  3. import * as crypto from 'crypto'
  4. // 定义插件选项类型
  5. interface ManifestPluginOptions {
  6.   outputPath: string
  7.   version?: string
  8.   serviceWorkerFileName?: string
  9. }
  10. export default function ServiceWorkerManifestPlugin(options: ManifestPluginOptions) {
  11.   const { outputPath, version, serviceWorkerFileName } = options
  12.   // 生成随机版本号
  13.   const generateRandomVersion = (): string => {
  14.     return crypto.randomBytes(8).toString('hex')
  15.   }
  16.   const manifestVersion = version || generateRandomVersion()
  17.   // 使用默认的 service-worker.js 文件名,如果没有传入自定义文件名
  18.   const serviceWorkerPath = `/${serviceWorkerFileName || 'service-worker.js'}`
  19.   // 递归遍历目录并获取所有文件路径
  20.   const getAllFiles = (dirPath: string, relativePath: string = ''): string[] => {
  21.     let files: string[] = []
  22.     const entries = fs.readdirSync(dirPath, { withFileTypes: true })
  23.     for (const entry of entries) {
  24.       const fullPath = path.join(dirPath, entry.name)
  25.       const relativeFullPath = path.join(relativePath, entry.name)
  26.       if (entry.isDirectory()) {
  27.         files = files.concat(getAllFiles(fullPath, relativeFullPath))
  28.       } else {
  29.         files.push(`/${relativeFullPath}`)
  30.       }
  31.     }
  32.     return files
  33.   }
  34.   // 生成 service-worker.js 文件内容
  35.   const generateServiceWorkerContent = (cachedFiles: string[], manifestVersion: string): string => {
  36.     return `
  37. self.addEventListener('install', event => {
  38.   event.waitUntil(
  39.     caches.open('${manifestVersion}').then(cache => {
  40.       return cache.addAll([
  41.         ${cachedFiles.map((file) => `'${file}'`).join(',\n')}
  42.       ]);
  43.     })
  44.   );
  45. });
  46. //Service Worker监听到fetch事件。
  47. self.addEventListener('fetch', event => {
  48.   event.respondWith(
  49.   // 缓存中是否存在请求资源:检查缓存中是否有匹配的请求资源。
  50.     caches.match(event.request).then(response => {
  51.       // 从缓存返回资源:如果缓存中有匹配资源,直接返回该资源。
  52.       if (response) {
  53.         console.log('Serving from cache:', event.request.url);
  54.         return response;
  55.       }
  56.       console.log('Fetching from network:', event.request.url);
  57.       // 发起网络请求:如果缓存中没有匹配资源,则发起网络请求。
  58.       return fetch(event.request).then(networkResponse => {
  59.         // 缓存新资源:如果网络请求成功,则将新资源缓存。
  60.         if (networkResponse && networkResponse.ok) {
  61.           console.log('Caching new response:', event.request.url);
  62.           // 缓存新响应:如果网络请求成功且响应状态为OK,则将响应缓存。
  63.           return caches.open('${manifestVersion}').then(cache => {
  64.             cache.put(event.request, networkResponse.clone());
  65.             return networkResponse;
  66.           });
  67.         }
  68.         // 如果响应状态不是OK,则抛出错误。
  69.         throw new Error('Network response not ok');
  70.       }).catch(error => {
  71.         console.error('Fetch failed:', error);
  72.         throw error;
  73.       });
  74.     })
  75.   );
  76. });
  77. self.addEventListener('activate', event => {
  78.   const cacheWhitelist = ['${manifestVersion}'];
  79.   event.waitUntil(
  80.     caches.keys().then(cacheNames => {
  81.       return Promise.all(
  82.         cacheNames.map(cacheName => {
  83.           if (cacheWhitelist.indexOf(cacheName) === -1) {
  84.             return caches.delete(cacheName);
  85.           }
  86.         })
  87.       );
  88.     })
  89.   );
  90. });
  91. `
  92.   }
  93.   // 修改 index.html 文件
  94.   const modifyIndexHtml = (indexPath: string, serviceWorkerPath: string): void => {
  95.     try {
  96.       let indexContent = fs.readFileSync(indexPath, 'utf-8')
  97.       // 确保 <html> 标签存在
  98.       if (indexContent.includes('<html')) {
  99.         // 添加 Service Worker 注册脚本
  100.         const serviceWorkerRegistrationScript = `
  101. <script>
  102.   if ('serviceWorker' in navigator) {
  103.     // 浏览器支持 Service Worker
  104.     window.addEventListener('load', () => {
  105.       navigator.serviceWorker.register('${serviceWorkerPath}')
  106.         .then(registration => {
  107.           console.log('Service Worker registered with scope:', registration.scope);
  108.         })
  109.         .catch(error => {
  110.           console.error('Service Worker registration failed:', error);
  111.         });
  112.     });
  113.   } else {
  114.     console.log('Service Worker not supported');
  115.   }
  116. </script>
  117. `
  118.         // 将脚本插入到 </head> 标签之前
  119.         indexContent = indexContent.replace('</head>', `${serviceWorkerRegistrationScript}</head>`)
  120.         fs.writeFileSync(indexPath, indexContent)
  121.         console.log('index.html modified successfully.')
  122.       } else {
  123.         console.warn('index.html does not contain a <html> tag.')
  124.       }
  125.     } catch (error) {
  126.       console.error('Failed to modify index.html:', error)
  127.     }
  128.   }
  129.   return {
  130.     name: 'manifest-plugin', // 必须的,将会在 warning 和 error 中显示
  131.     writeBundle() {
  132.       try {
  133.         const cachedFiles = getAllFiles(outputPath)
  134.         // 确保 service-worker.js 也被缓存
  135.         if (!cachedFiles.includes(serviceWorkerPath)) {
  136.           cachedFiles.push(serviceWorkerPath)
  137.         }
  138.         // 生成 service-worker.js 文件内容
  139.         const serviceWorkerContent = generateServiceWorkerContent(cachedFiles, manifestVersion)
  140.         // 写入 service-worker.js 文件
  141.         const serviceWorkerFilePath = path.join(outputPath, serviceWorkerPath.replace(/^\//, ''))
  142.         fs.writeFileSync(serviceWorkerFilePath, serviceWorkerContent)
  143.         console.log('service-worker.js generated successfully.')
  144.         // 修改 index.html 文件
  145.         const indexPath = path.join(outputPath, 'index.html')
  146.         if (fs.existsSync(indexPath)) {
  147.           modifyIndexHtml(indexPath, serviceWorkerPath)
  148.         } else {
  149.           console.warn('index.html not found in the output directory.')
  150.         }
  151.       } catch (error) {
  152.         console.error('Failed to write bundle:', error)
  153.       }
  154.     },
  155.   }
  156. }
复制代码
八、Service Worker调试与监控

   通过Chrome DevTools可看到我们的文件被正确的缓存,且通过Application工具管理我们的Service Workers



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4