☰
Uniapp + PWA 离线文档工具实现:Service Worker 缓存与更新策略
2026/10/8 14:43:32 网站建设 项目流程

1. 场景复盘:为什么文档类工具是离线化的最佳试验田

Uniapp + PWA 这个组合最打动我的地方,是它能让离线文档工具在断网环境下依旧打开,同时把缓存策略和内容同步这两件事落到具体代码里。我最初做这个项目,是给一套企业内部产品写在线帮助文档。产品本身的 Web 端、小程序端都已经用 Uniapp 维护,文档模块自然也想复用同一套代码。但文档工具有一个非常硬的诉求:用户可能在电梯里、高铁上、客户现场打开帮助页,网络状况完全不可控。要是文档页面一断网就白屏,那这个工具基本等于摆设。

1.1 现场演示翻车与“离线优先”需求

我真正被刺激到,是一次现场演示。客户那边会议室 WiFi 时好时坏,我点开一个 API 参考页面,等了十几秒,浏览器弹出一个“无法连接到服务器”的默认错误页。当时只能尴尬地切到手机热点,页面才刷出来。演示结束以后我就想,文档这种内容,命中率其实非常集中:用户翻来翻去就是首页、目录、最近更新的几篇、搜索命中那几篇。真正需要的是“第一次打开过之后,第二次即使断网也能看”。

离线文档工具的价值就在这:不是要求所有内容都塞进本地,而是把用户实际会反复看的那些内容,用可靠策略缓存下来,让阅读行为不依赖网络。和视频、直播这类强实时业务不同,文档是静态内容为主,更新频率低,新鲜度要求也没那么敏感,天然适合做离线。产品手册、API 参考、帮助中心、内部知识库,都属于这一类。

1.2 为什么选 Uniapp + PWA 而不是纯 Web 或原生 App

很多人会问,直接用纯 Web + PWA 不就行了,为什么还要套一层 Uniapp?这个问题我实际操作下来感受很深。如果项目只需要 H5,纯 Web 确实更轻。但我们团队已经有 Uniapp 的小程序和 App 端,文档模块如果单独用纯 Web 写一遍,以后维护成本就是三套。Uniapp 统一了 Vue 语法和路由,H5 端编译出来本身就是一个标准 SPA,完全有能力挂上 Service Worker,这是它能做 PWA 的前提。

另外,原生 App 离线存储方案能力更强,比如把文档写入 SQLite 或者本地文件,但上架、打包、审核的链路比 PWA 重得多。PWA 的优势是你还没有把它当成一个 App 去分发,用户只要用 HTTPS 访问过一次,浏览器就能把它变成可安装、可离线的应用。对工具型产品来说,这个转化成本极低。

这里还要提一个很容易混的点:Uniapp 和 Uniapp X。如果你是新项目,要搞清楚这两者差异。Uniapp 目前仍然是 Vue 3 + Vite 这套生态,H5 端可以用成熟的前端 PWA 方案;Uniapp X 是另一条基于 UTS 的跨端技术栈,它强调原生渲染性能,但 H5 端的兼容特性不能默认等价。我这个项目记录的是 Uniapp + Vue 3 + Vite 的落地链路,如果你用的是 Uniapp X,关键能力必须单独在目标浏览器里验证。

1.3 离线范围控制:不是把整个站点都塞进浏览器缓存

做离线工具最忌讳的就是“离线即全量”。文档站点可能几百篇甚至几千篇文章,把全部 Markdown、图片、附件都预缓存,首屏加载会变得非常慢,用户第一次访问体验直接崩。我建议把离线范围分三层来控制。

第一层是应用外壳,也就是 JS、CSS、HTML、LOGO 这类站点运行必需的资源,必须离线可用。第二层是核心文档内容,只缓存用户实际访问过的文档正文和目录索引,没访问过的内容等用户点开时再按策略加入缓存。第三层是做容量上限,缓存超过一定条目或大小就淘汰最久未用的数据。这个思路和控制浏览器缓存有点像,都是空间换时间,但要有一个明确的边界。

2. Service Worker 上岗前必须想清楚的几个问题

Service Worker 是 PWA 离线能力的核心,但很多从普通 Web 开发转过来的同学,容易拿它和浏览器 HTTP 缓存做对比。这里要强调一个认知:Service Worker 不是替代浏览器缓存,它是一个位于页面和网络之间的独立线程,可以拦截页面发出的请求,再由我们自己决定请求是走网络还是走本地缓存。

2.1 Service Worker 的生命周期和角色

Service Worker 主要有三个事件:install、activate、fetch。install 发生在第一次注册安装,适合把基础资源写入缓存,比如应用外壳;activate 在安装完成后触发,适合清理旧版本缓存;fetch 则是在任何页面请求发出时触发,我们在这里写缓存策略。

理解生命周期特别重要,因为很多“改了代码不生效”的坑,其实都出在生命周期上。安装了一个新版本 Service Worker 后,它不会立刻控制当前页面,而是进入 waiting 状态,只有当前页面刷新并且旧的 Service Worker 释放后,新的才会接管。如果不处理,用户可能连续刷新几次都还在跑旧逻辑,感觉就像线上没发布一样。

我通常这样给团队解释:Service Worker 就像一个快递柜管理员。用户请求内容时,管理员可以决定这个包裹是刚从仓库取的,还是直接从柜子里拿给你的。管理员换班时,老管理员要把手里正在处理的活儿交代清楚,新管理员才能正式接手。这个“交接”过程就是 activate 和 waiting 的关系。

2.2 在 Uniapp H5 中注册 Service Worker 的正确姿势

Uniapp 的页面只会编译成 H5、小程序、App 中对应的产物。Service Worker 只有 H5 端存在,小程序和 App 里没有 navigator 这个概念,所以注册代码必须用条件编译包起来。以 Vue 3 + Vite 的 Uniapp 项目为例,通常放在 main.ts 里:

// #ifdef H5 if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js').then((reg) => { console.log('ServiceWorker registered:', reg.scope); }).catch((err) => { console.warn('ServiceWorker registration failed:', err); }); }); } // #endif

这里要注意两个细节。第一,注册时机放在 window.load 事件里,不阻塞首屏渲染。文档工具的场景允许页面先加载,Service Worker 慢慢就位。第二,sw.js 的路径决定了它的作用域,放在站点根目录就默认控制整个站点。如果放在 /docs/sw.js,那只能控制 /docs 下面的请求。

2.3 HTTPS 不是可选项,是硬前提

Service Worker 只能在安全上下文中运行,也就是 HTTPS 页面,或者 localhost。所以本地调试还算方便,Chrome 会默认把 localhost 当成安全上下文。但一旦上线,必须保证网站有有效的 HTTPS 证书。我之前踩过一个小坑:测试环境用 IP 地址加端口访问,Service Worker 始终注册失败,浏览器控制台只提示“不支持的上下文”,换成域名加证书后一切正常。

另外,manifest 文件也一样。PWA 的可安装性依赖 Web App Manifest,虽然这个文件本身不一定强制 HTTPS,但整个页面必须是安全上下文,否则后面所有能力都白搭。Uniapp 项目通过 manifest.json 配置应用信息,但 H5 端真正使用的 PWA manifest,是部署到静态服务器上的 webmanifest 文件,这个我在第 5 部分再详细展开。

3. 缓存策略分层:静态资源、文档正文、接口数据各回各家

缓存策略是整个离线文档工具里最容易被低估的部分。很多初版实现就一句话:把所有 GET 请求都 Cache First,文档倒是能离线打开了,但内容更新后用户永远看到旧版本。更合理的做法不是一概而论,而是按照资源的性质分层处理。

3.1 分层决策和三种基础策略

我习惯把文档工具的请求分成三类:静态资源、文档正文、接口数据。静态资源是 JS、CSS、图片、字体;文档正文是 Markdown 转出来的 HTML 或 JSON;接口数据是文档目录树、搜索建议、文章列表这类动态请求。它们适合的缓存策略完全不同,核心区别在于“内容更新的紧迫程度”。

常用的三种基础策略分别是 Cache First、Stale-While-Revalidate、Network First。我用一个表格说明各自适用场景。

策略成功路径适用场景风险点
Cache First命中缓存直接返回,不请求网络文件名带 hash 的 JS/CSS、图片如果文件名不变,内容会一直旧
Stale-While-Revalidate先返回缓存,同时后台请求网络更新缓存文档正文、文章列表有轻微延迟,但用户无感知
Network First先请求网络,失败再回退缓存目录树、搜索接口、版本检查弱网下延迟较高,需要超时控制

Cache First 看起来简单,但它要求资源 URL 必须与内容版本绑定。静态资源打包工具会生成 hash 文件名,所以适合。文档正文如果也用 Cache First,那发布新文档后,只要 URL 不变,用户就永远读不到更新,这是很多 PWA 项目翻车的根源。

3.2 静态资源:文件名 Hash + Cache First 的组合

静态资源缓存策略的核心逻辑是“让 URL 变化代替手工清缓存”。Vite 在构建时会把 JS、CSS 文件名变成index-a1b2c3.js这种带内容 hash 的名字,文件内容一变,URL 就变,Service Worker 自然会把旧缓存和新资源区分开。

针对这类资源,我会在 Service Worker 的 fetch 事件里这样处理:

async function cacheFirst(request) { const cache = await caches.open('docs-static-v1'); const cached = await cache.match(request); if (cached) return cached; const response = await fetch(request); if (response.ok) { cache.put(request, response.clone()); } return response; }

有一个例外是 index.html。入口 HTML 文件不能走 Cache First,因为它的内容决定了你加载哪些带 hash 的静态资源,如果 HTML 被旧缓存卡住,JS 更新再快也没用。入口文件建议走 Network First,或者说每次请求都先问网络,网络不可用才用缓存。

3.3 文档正文:Stale-While-Revalidate 是最稳妥的选择

文档正文对应的请求数据是“读多写少”,用户访问频率高,但内容更新不频繁。如果完全 Network First,离线和弱网场景下体验不好;如果完全 Cache First,一篇文档更新后要等老用户重启浏览器才生效。Stale-While-Revalidate 是折中中很稳的方案。

它的逻辑是:命中缓存就先把缓存内容返回给页面,页面马上能展示;同时后台发一个真实网络请求,拉取最新内容并更新缓存。下次访问时拿到的就是新内容。这个策略能保证首次访问速度,也不会让旧内容在本地赖着不走。核心代码大致如下:

async function staleWhileRevalidate(request) { const cache = await caches.open('docs-content-v1'); const cached = await cache.match(request); const network = fetch(request) .then((response) => { if (response.ok) { cache.put(request, response.clone()); } return response; }) .catch(() => cached); return cached || network; }

需要注意,如果文档正文不是 HTML,而是类似 JSON 的数据文件,那决策逻辑一样,只是缓存命名空间要区分开,避免不同类型的数据混在一个 Cache Storage 里,后续清理困难。

3.4 接口数据:Network First 加超时兜底

文档工具里还有一类接口数据不能盲目走缓存,比如当前用户是否有某篇高级文档的权限、搜索接口的实时结果、服务端最新版本号。这些数据如果离线展示过期内容,可能给用户造成误导。但纯 Network First 在弱网环境又容易让页面一直转圈。我的做法是给网络请求加一个超时,比如 3 秒内没返回,就回退到上一次缓存。

async function networkFirstWithTimeout(request, timeoutMs = 3000) { const cache = await caches.open('docs-api-v1'); const timer = new Promise((resolve) => setTimeout(resolve, timeoutMs)); try { const response = await Promise.race([fetch(request), timer]); if (response && response.ok) { cache.put(request, response.clone()); return response; } const cached = await cache.match(request); if (cached) return cached; // 都没有则返回一个预置的兜底数据 return Response.json({ ok: false, offline: true }); } catch (error) { const cached = await cache.match(request); return cached || Response.json({ ok: false, offline: true }); } }

这里的“兜底”不一定是空数据。对文档目录树这种基础信息,我会在构建时生成一个默认目录快照,作为离线时的兜底数据,保证用户至少能浏览到历史目录。这个预置快照文件也可以放到预缓存列表里,页面断网时体验会好很多。

3.5 手写 Service Worker 还是直接用 Workbox

手写 Service Worker 最大的好处是你能看到每一行逻辑,适合文档工具这种不复杂的场景。但等到资源种类变多,需要处理预缓存清单、运行时缓存、路由匹配、过期清理时,手写容易漏。Workbox 是 Google 提供的 Service Worker 工具库,把缓存策略封装成现成方法,配合 Vite 生态时可以直接用 vite-plugin-pwa,它会基于 Workbox 自动生成 Service Worker。

我的建议是:如果项目是长期维护的正式产品,直接用 vite-plugin-pwa,把精力放在策略参数上;如果只是想验证 PWA 离线的可行性,先从手写一个只有二十几行的 sw.js 开始,跑通了再换工具,会更容易理解底层原理。

4. 内容同步:新文档上线后,旧缓存如何优雅让位

缓存策略解决的是“离线能不能读”的问题,内容同步解决的是“在线了,新的内容能不能覆盖旧内容”的问题。这一点比缓存更让运维同事头疼。文档站点发布一篇新文章后,用户本地可能还留着旧版本,如果同步机制设计得不好,新内容发布一周都触达不到部分用户。

4.1 更新检查的三种触发方式

Service Worker 会在 install 事件触发时,重新下载一遍 sw.js / 预缓存清单,判断是否有新版本。但浏览器不会频繁自动检查,所以页面端要主动触发检查。我在实际项目里用了三种方式。

第一种是应用启动时主动检查。Uniapp 的 App.vue 的 onLaunch 里调用一次registration.update(),如果用户是当天第一次打开页面,马上就能检查到新版本。

第二种是页面重新可见时检查。用户从后台切回页面,触发visibilitychange事件时,再调用一次 update。这个场景很关键,因为文档工具经常被用户挂在后台,过几个小时切回来,如果没有这个机制,用户会以为一直是最新版本。

第三种是定时检查。对于长期打开不关闭的 PC 端站点,我习惯设置 30 分钟一次定时器。文档内容更新不频繁,30 分钟已经足够,不需要更密集。

// #ifdef H5 if ('serviceWorker' in navigator) { const regPromise = navigator.serviceWorker.getRegistration(); regPromise.then((reg) => { if (reg) { setInterval(() => reg.update(), 30 * 60 * 1000); } }); } // #endif

4.2 用户可见的“新版已就绪”提示流程

更新流程不能悄无声息,也不能强制打断用户。我的方案是,当检测到新的 Service Worker 进入 waiting 状态时,弹一个确认框告诉用户“文档内容已更新”,用户确认后再刷新页面。Uniapp 的 H5 端可以直接用 uni.showModal,代码路径上是浏览器原生的确认框,但在 Uniapp 语法下写起来一致:

if (reg.waiting) { uni.showModal({ title: '发现新版本', content: '文档内容已更新,是否立即刷新?', success: (res) => { if (res.confirm) { reg.waiting.postMessage({ type: 'SKIP_WAITING' }); } } }); }

Service Worker 里面需要监听这个 message,调用 skipWaiting,让新版本立即激活。

self.addEventListener('message', (event) => { if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting(); } });

还要在页面里监听 controllerchange 事件。这个事件表示 Service Worker 已经切换成功,此时刷新页面才能加载新版本:

navigator.serviceWorker.addEventListener('controllerchange', () => { window.location.reload(); });

如果你想让更新无缝一点,可以跳过弹窗,直接在新版本激活后自动刷新。但对文档工具体验来说,用户可能正在读一篇重要文档,突然刷新会打断阅读,我最终还是选择了显式确认的方式。

4.3 增量同步的边界:什么时候才需要做 diff

很多团队一听到“内容同步”,第一反应就是做增量同步,给每篇文档算 diff,只传输变更部分。但对 PWA 来说,浏览器层面已经做了一层增量:预缓存清单里记录的是带 hash 的资源 URL,文档文件变了,hash 变了,Service Worker 更新的那一刻,浏览器只会下载变更后的新文件,未变更的文件会继续沿用缓存。这种机制在静态资源层面已经天然完成增量同步。

真正需要自己设计增量逻辑的场景是,文档数据存在 IndexedDB 里,以前端数据表形式维护,比如用户笔记、批注、阅读进度。这类数据跨端同步才需要自定义版本号、时间戳或分段 hash。我做离线文档工具的经验是,第一版不要一上来就设计复杂的 diff 协议,先用“版本号 + 全量覆盖更新”跑通流程,等数据量真的到了需要省流量的程度,再引入增量机制,那时候你会更清楚该对哪些字段做 diff,而不是为了做 diff 而 diff。

5. 从创建项目到打包部署的完整链路

前面四部分聊的是原理和策略,这一部分把代码链路完整串一遍。我默认你用的是 Uniapp CLI 创建的 Vue 3 + Vite 项目,因为 Uniapp 的 H5 构建链路和 Vite 深度绑定,很多配置改起来比 HBuilderX 图形界面更可控。

5.1 用 Vite + TypeScript 初始化 Uniapp 项目

命令行创建带 TypeScript 支持的 Uniapp 项目很简单,直接用官方模板:

npx degit dcloudio/uni-preset-vue#vite-ts offline-docs cd offline-docs npm install npm run dev:h5

这个模板默认是 Vue 3 + Vite + TypeScript。如果你以前用 HBuilderX 新建项目,类型支持和 CLI 构建习惯会有点差异。我推荐用 CLI 方式,因为后面要改 vite.config.ts、引入 vite-plugin-pwa,CLI 项目改起来更顺手,也方便进 CI/CD 流程。

5.2 manifest.json 与 H5 平台配置

Uniapp 的 manifest.json 是全局配置文件,很多新手容易忽略 h5 节点下的配置。它决定 H5 端路由模式、页面标题、开发服务器 HTTPS 等。

下面是一个精简示例:

{ "name": "离线文档工具", "appid": "__UNI__OFFLINEDOCS", "versionName": "1.0.0", "versionCode": "100", "h5": { "title": "离线文档工具", "router": { "mode": "hash" }, "devServer": { "https": true } } }

需要注意的是,manifest.json 里的 versionName 主要影响 App 端和小程序端,H5 端并不直接在运行时暴露这个字段。如果你想要 H5 端显示版本号,需要额外注入,这个下面会讲到。

另外,PWA 可安装性还需要一个标准的 Web App Manifest 文件,放在项目 public 目录下,比如 public/manifest.webmanifest:

{ "name": "离线文档工具", "short_name": "离线文档", "start_url": "/", "display": "standalone", "background_color": "#ffffff", "theme_color": "#4FC08D", "icons": [ { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" } ] }

然后在 index.html 里引入:

<link rel="manifest" href="/manifest.webmanifest" /> <meta name="theme-color" content="#4FC08D" />

这里有个很容易忽略的点:Uniapp H5 构建时,index.html 会作为模板,所以可以直接改这个文件,不用额外配置 HtmlWebpackPlugin。

5.3 接入 vite-plugin-pwa 或手写 Service Worker

如果生产项目想省事,我更推荐用 vite-plugin-pwa。先安装:

npm install -D vite-plugin-pwa

然后在 vite.config.ts 里配置:

import { defineConfig } from 'vite'; import uni from '@dcloudio/vite-plugin-uni'; import { VitePWA } from 'vite-plugin-pwa'; export default defineConfig({ plugins: [ uni(), VitePWA({ registerType: 'autoUpdate', manifest: false, workbox: { globPatterns: ['**/*.{js,css,html,svg,png,json}'], runtimeCaching: [ { urlPattern: /\/api\/doc\/.*/i, handler: 'NetworkFirst', options: { cacheName: 'docs-api', expiration: { maxEntries: 100, maxAgeSeconds: 60 * 60 * 24 * 7 } } } ] } }) ] });

需要提醒的是,vite-plugin-pwa 默认会往页面注入 Service Worker 注册代码,但 Uniapp 的 H5 端编译流程有时会有兼容问题。如果发现注册没有生效,就把injectRegister设成 null,自己在 main.ts 里写前面那套注册逻辑,然后把 registerType 改成 autoUpdate 或者配合手动提示流程使用。

5.4 H5 端版本号获取与更新参数

很多人问 Uniapp H5 端怎样获取版本号。如果你在 H5 里调用uni.getSystemInfoSync().appVersion,得到的通常是当前浏览器的版本,而不是你产品的发布版本。正确做法是在构建时把 package.json 的 version 注入成全局变量。

在 vite.config.ts 里加上:

import { defineConfig } from 'vite'; import { version } from './package.json'; export default defineConfig({ define: { __APP_VERSION__: JSON.stringify(version) } });

然后在 TypeScript 项目里声明全局变量,避免报错:

declare const __APP_VERSION__: string;

页面里就可以用了:

console.log('当前文档版本:', __APP_VERSION__);

版本号不只是拿来显示,还可以用来辅助更新检查。每次发布后,版本号变了,配合定时执行registration.update(),能让“新版就绪”的提示更可靠。

5.5 打包产物、日志清理与各端边界

H5 端打包命令是:

npm run build:h5

产物默认在dist/build/h5目录。部署到 nginx 时,静态资源缓存头建议按下面方式区分:

location / { root /opt/offline-docs; try_files $uri $uri/ /index.html; } location /sw.js { add_header Cache-Control "no-cache, no-store, must-revalidate"; } location /index.html { add_header Cache-Control "no-cache"; } location /assets/ { add_header Cache-Control "public, max-age=31536000, immutable"; }

sw.js 绝对不能长缓存,否则 Service Worker 永远更新不了。index.html 也不能长缓存,否则加载的静态资源 hash 可能和本地缓存不匹配。

生产环境经常会遇到“uniapp 不打印日志信息”的需求。Uniapp 开发阶段 console 日志很多,但发布时要清理。如果用的是 Vite 构建,最直接的办法是在 esbuild 配置里把 console 和 debugger 去掉:

export default defineConfig({ esbuild: { drop: process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [] } });

这比在代码里写一堆条件编译干净得多,因为它是构建期行为,开发环境不受影响。

再补充一下“uniapp 怎么打包”的边界。如果你的目标是 H5 + PWA,npm run build:h5之后部署静态文件就可以了;如果目标是上架安卓应用市场,那要走云打包或本地打包,生成 apk/aab,和本文的 PWA 链路不是一回事。Uniapp 的打包能力确实是多端的,但 PWA 只在 H5 端生效,不要混淆。

6. 线上 PWA 清单与真实踩坑记录

策略配好了,构建能出包,不代表上线就一定没问题。PWA 的很多坑只有在真实网络环境、真实浏览器、真实发布流程里才会暴露。我在项目上线过程中踩了不少,整理成下面几个部分。

6.1 用 Lighthouse 自查哪些指标

最常用的检查工具是 Chrome DevTools 里的 Lighthouse。跑之前先把构建产物部署到一台测试服务器上,并且一定用 HTTPS 访问,然后切到无痕窗口再跑,避免之前访问残留的 Service Worker 干扰结果。

Lighthouse 的 PWA 类别主要看:页面是否可安装、是否启用 Service Worker、是否支持离线启动、是否响应移动端。文档工具最容易出问题的地方是离线启动,因为 Service Worker 必须在离线情况下能返回一个可用的页面壳子,而不是直接报网络错误。

我用表格总结一下最常见的几个失败点:

表现常见原因处理方式
离线时页面白屏index.html 没有预缓存把入口 HTML 加入预缓存清单
配置了 SW 但 Lighthouse 检测不到sw.js 路径和作用域不一致务必放在构建根目录,scope 覆盖全站
更新后用户一直是旧版本没有处理 skipWaiting监听 waiting 状态并提示刷新
HTTPS 正常仍报 insecure context使用了 IP 地址访问换成有效域名和证书

6.2 Safari、微信内置浏览器与国产浏览器差异

Service Worker 的兼容性不能只看 Chrome。iOS Safari 从 11.3 开始支持 Service Worker,但部分 API 行为和桌面 Chrome 不一致。还有一点,iOS 的“添加到主屏幕”是否完全走 PWA 模式,取决于 web app manifest 和 apple-touch-icon 是否配置完整。只做 Chrome 调试,真机一测容易翻车。

微信内置浏览器对 Service Worker 的支持并不可靠,很多版本里注册会失败。如果你主要把文档链接分享在微信里,一定要做一个降级方案:检测到 npx 不支持 Service Worker 时,至少保证普通 HTTP 访问能用,最多是离线能力失效,不能让页面直接打不开。这也是为什么我的策略始终强调“回退”,而不是把离线当成唯一路径。

国产 Android 浏览器的内核差异同样需要真机验证,尤其是 X5 内核和厂商自带浏览器。处理办法是把 Service Worker 的注册代码包在能力检测里,不支持的环境自动走普通网络加载,至少不影响基本使用。

6.3 各端差异化实现:自定义分享、开发者工具插件与应用商店的边界

Uniapp 的项目最终往往会同时发布 H5、小程序、App 三端,离线能力只是 H5 端的方案。很多人会拿“uniapp自定义分享好友”这类小程序能力来对比 H5。小程序端实现自定义分享,一般是在按钮上配置 open-type,或者调用 uni.share;H5 端则用 Web Share API 或者复制链接。两者不在一个技术体系里,但是同一套业务逻辑。

如果要在微信开发者工具里调试小程序,需要在 HBuilderX 里配置“uniapp 微信小程序开发者工具插件”路径,运行后自动打开微信开发者工具。这个插件只是调试链路的配置,和 PWA 没有关系,别在排查 H5 问题时被它干扰。

至于“uniapp上架安卓应用市场”,那是另一个流程。云打包会生成原生安装包,离线能力可以靠本地存储方案实现,但它和 PWA 的浏览器缓存完全是两码事。真正想要浏览器层面的安装体验,还是只有 PWA 这条路。这个边界想清楚,才不会在技术选型上走偏。

6.4 复盘后我建议盯紧的五个动作

项目复盘时我总结出五个最值得盯的动作,分享给做同类项目的同学。

第一,用真实域名加 HTTPS 部署测试环境,不要用 IP。IP 环境下 Service Worker 注册会被拒,很多排查时间都浪费在这。

第二,每次发布都手动更新一次版本号,并且把版本号显示到文档页脚或设置页。这样用户反馈“看不到新内容”时,你可以立刻判断他本地是哪个版本。

第三,离线检查不要只看 Chrome DevTools 的离线开关,要真机开飞行模式验证一次。Service Worker 在移动浏览器上的行为往往比桌面端更严格。

第四,缓存策略别追求一步到位,先让离线能跑,再根据真实访问数据调整哪些资源走 Cache First,哪些走 Network First。过度设计会拖慢上线节奏。

第五,给不支持 Service Worker 的环境留一条普通网络访问的退路。PWA 对文档工具来说是增强体验,不应该成为所有用户访问文档的唯一入口。

我自己在项目里最深刻的体会是:离线文档工具的技术难点,不是怎么把页面缓存下来,而是怎么在内容更新时让用户端的状态始终保持一致。把静态资源、文档正文、接口数据三条缓存路径分清楚,把版本号变成更新机制里的一等成员,整个项目后半段的维护会轻松非常多。这套链路跑通之后,后续想扩展搜索离线索引、阅读记录同步都会顺手很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询