news 2026/5/1 10:17:44

一种基于 Service Worker 的渐进式渲染方案的基本原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一种基于 Service Worker 的渐进式渲染方案的基本原理

流式SSR就是一种渐进式渲染,在传统的页面加载流程是:请求 → 等待 → 渲染。而渐进式渲染的思路是:

  1. 立即展示缓存的页面快照(即使是旧内容)
  2. 后台请求最新的页面内容
  3. 无缝替换为最新内容

这样用户感知到的加载时间接近于零,体验类似于原生 App。

前面笔者的文章中,提到关于H5页面的快照是客户端做的。本篇文章讲述一种基于 Service Worker 的渐进式渲染方案的原理,简单来讲就是将客户端的工作挪到了service worker中。通过给站点开启一个后台运行的service worker(service worker可以独立于webview运行在后台),在service worker中劫持包括主文档在内的网络请求,对文档内容进行存储,并修改返回。

技术方案设计

整体架构

┌─────────────┐ │ 用户访问 │ └──────┬──────┘ │ ▼ ┌─────────────────┐ │ Service Worker │ ◄─── 拦截请求 └────┬────────┬───┘ │ │ │ └─────────┐ ▼ ▼ ┌─────────┐ ┌──────────┐ │ 缓存快照 │ │ 网络请求 │ └────┬────┘ └─────┬────┘ │ │ └────────┬────────┘ ▼ ┌─────────────┐ │ 流式替换 │ └─────────────┘

核心代码实现

1. HTML 页面注册 Service Worker

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>渐进式渲染示例</title> </head> <body> <h1>Hello World</h1> <script data-snapshot> (function () { const swEnabled = location.search.indexOf('x-sw=false') < 0; // 注册 Service Worker swEnabled && navigator.serviceWorker && navigator.serviceWorker.register('/sw.js') .then(function (registration) { console.log('Service Worker 注册成功:', registration); }) .catch(function (error) { console.log('Service Worker 注册失败:', error); }); // 如果禁用,则注销 Service Worker !swEnabled && navigator.serviceWorker && navigator.serviceWorker.getRegistration(location.href).then((r) => { r && r.unregister(); }); }()); </script> </body> </html>

关键点说明:

  • data-snapshot属性标记这是快照阶段需要保留的脚本
  • 支持通过?x-sw=false参数禁用 Service Worker
  • 禁用时会自动注销已注册的 Service Worker
2. Service Worker 核心逻辑

// sw.js self.addEventListener('fetch', (event) => { // 只拦截主文档请求 if (event.request.destination !== 'document') { return; } // 支持禁用功能 if (event.request.url.indexOf('x-sw=false') >= 0) { event.waitUntil(caches.delete('my-cache')); return; } event.respondWith(handleFetch(event.request)); }); self.addEventListener('install', (event) => { console.log('Service Worker 安装'); self.skipWaiting(); // 立即激活 });

3. 脚本过滤策略

function replaceScripts(text, regularStream) { return text.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, (match) => { // 快照阶段:只保留 data-snapshot 脚本 // 正式阶段:只保留普通脚本 if (match.indexOf('data-snapshot') >= 0) { return regularStream ? '' : match; } return regularStream ? match : ''; }); }

为什么要过滤脚本?

  • 快照阶段:避免执行业务逻辑脚本(可能依赖未加载的资源)
  • 正式阶段:避免重复执行初始化脚本
4. 流式渲染核心

function withSnapshot(snapshot, request) { return new Response(new ReadableStream({ start(controller) { const encoder = new TextEncoder(); const decoder = new TextDecoder(); // 第一步:立即输出快照 controller.enqueue(encoder.encode(snapshot)); let firstStream = true; // 第二步:请求最新内容 fetchAndStore(request).then((response) => { const reader = response.body.getReader(); function push() { reader.read().then(({ done, value }) => { if (done) { controller.close(); return; } if (firstStream) { firstStream = false; // 第三步:清空页面 controller.enqueue(encoder.encode( '<script>document.head.innerHTML = "";document.body.innerHTML = "";</script>' )); // 第四步:注入最新内容 const text = decoder.decode(value); const head = text.match(/<head>([\s\S]*?)<\/head>/i); const body = text.match(/<body>([\s\S]*?)<\/body>/i); if (head && body) { controller.enqueue(encoder.encode( `<script>document.head.innerHTML = '${head[1].trim().replace(/\n/g, '')}'</script>` )); controller.enqueue(encoder.encode(replaceScripts(body[1], true))); } } else { controller.enqueue(value); } push(); }); } push(); }); }, })); }

为什么要清空 DOM?

  • 快照内容和最新内容可能结构不同
  • 直接追加会导致内容重复
  • 清空后重新注入,确保页面状态一致

为什么用 innerHTML 注入?

  • 流式响应中,我们无法直接操作 DOM
  • 只能通过推送<script>标签让浏览器执行 JavaScript
  • innerHTML是最简单的 DOM 替换方式
5. 缓存管理

Service Worker 的缓存存储在 Cache Storage API 中,这是浏览器提供的专门用于 Service Worker 的持久化存储空间。实际上,不需要关心物理位置,因为浏览器完全管理这些文件。

function fetchAndStore(request) { return fetch(request) .then((networkResponse) => { if (networkResponse.ok) { // 克隆响应用于缓存 const cacheResponse = networkResponse.clone(); caches.open('my-cache').then((cache) => { cache.put(request, cacheResponse); }); } return networkResponse; }); } function handleFetch(request) { return caches.match(request) .then((response) => { if (response) { // 有缓存:先展示快照,再更新 return readResponseText(response).then((snapshot) => { return withSnapshot(snapshot, request); }); } // 无缓存:直接请求 return fetchAndStore(request); }); }

为什么要 clone 响应?

  • Response 对象的 body 只能读取一次(流的特性)
  • 需要一份给缓存,一份给浏览器
  • clone()创建独立的副本

工作流程详解

首次访问(无缓存)

用户访问 → Service Worker 拦截 → 无缓存 → 网络请求 → 返回内容 → 存入缓存

二次访问(有缓存)

用户访问 ↓ Service Worker 拦截 ↓ 读取缓存快照(去除普通脚本) ↓ 立即返回快照内容 ← 用户看到页面 ↓ 后台发起网络请求 ↓ 清空 DOM ↓ 注入最新 head 和 body ↓ 更新缓存

注意事项

上述只讲述了该方案的基本原理,实际应用要考虑更多的因素如App 环境兼容性、缓存策略、基础设施依赖等,下面是方案对比:

维度客户端方案Service Worker 方案
首次访问拦截✅ 可以拦截❌ 无法拦截
跨平台能力❌ 需要各端适配✅ Web 标准,通用
更新速度⚠️ 需要发版✅ 实时生效
开发成本⚠️ 需要端上开发⚠️ 需要 Web 开发
维护成本❌ 多端维护✅ 单一维护
灵活性⚠️ 受限于客户端版本✅ 完全可控
降级能力⚠️ 需要发版回滚✅ 秒级降级

总结:

  • 如果你的业务是纯 Web 应用(PWA) → Service Worker 是最佳选择
  • 如果你的业务在 App 内 → 优先考虑客户端方案
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/1 1:42:56

大语言模型推理极致优化:TensorRT-LLM高性能推理实践指南

大语言模型推理极致优化&#xff1a;TensorRT-LLM技术详解与云上实践指南&#xff0c;系统性地介绍了如何使用 TensorRT-LLM 优化大语言模型推理性能。 一、背景与挑战 大语言模型&#xff08;LLM&#xff09; 是基于海量数据预训练的超大规模深度学习模型&#xff0c;其基础是…

作者头像 李华
网站建设 2026/5/1 10:12:10

C#实现HC32L130 CRC16校验

要在 C# 中实现与小华 HC32L130 MCU 匹配的 CRC16 校验&#xff0c;需先明确HC32L130 的 CRC16 参数规则&#xff0c;再基于该规则编写 C# 代码。一、HC32L130 的 CRC16 参数解析从你提供的文档和 MCU 代码可提取核心参数&#xff1a;参数项具体值 / 规则多项式\(x^{16}x^{12}x…

作者头像 李华
网站建设 2026/5/1 10:12:04

告别局域网!SimpleMindMap+cpolar 让思维导图协作更自由

文章目录 前言1. Docker一键部署思维导图2. 本地访问测试3. Linux安装Cpolar4. 配置公网地址5. 远程访问思维导图6. 固定Cpolar公网地址7. 固定地址访问 前言 SimpleMindMap 是一款可以自己部署的思维导图工具&#xff0c;能用来画项目计划、产品架构、会议纪要等&#xff0c;…

作者头像 李华
网站建设 2026/5/1 8:55:11

mootdx开源工具:通达信数据读取的完整解决方案

mootdx开源工具&#xff1a;通达信数据读取的完整解决方案 【免费下载链接】mootdx 通达信数据读取的一个简便使用封装 项目地址: https://gitcode.com/GitHub_Trending/mo/mootdx 在金融数据分析和量化交易领域&#xff0c;通达信软件提供了丰富的市场数据资源。mootdx…

作者头像 李华
网站建设 2026/5/1 8:55:15

5分钟搞定wiliwili:从启动失败到流畅播放的完整解决方案

5分钟搞定wiliwili&#xff1a;从启动失败到流畅播放的完整解决方案 【免费下载链接】wiliwili 专为手柄控制设计的第三方跨平台B站客户端&#xff0c;目前可以运行在PC全平台、PSVita、PS4 和 Nintendo Switch上 项目地址: https://gitcode.com/GitHub_Trending/wi/wiliwili…

作者头像 李华
网站建设 2026/5/1 6:04:29

Editly容器化部署:革新视频创作工作流的终极方案

在当今数字内容爆炸的时代&#xff0c;视频创作已成为个人表达和企业营销的重要方式。然而&#xff0c;传统视频编辑软件复杂的安装过程、版本依赖冲突以及跨平台兼容性问题&#xff0c;让许多创作者望而却步。Editly容器化部署方案应运而生&#xff0c;彻底改变了这一现状&…

作者头像 李华