Dioxus PWA 实战指南:从自定义 index.html 到 Service Worker 与 Manifest 的完整配置
本文基于 Dioxus 仓库中的 PWA 示例工程 examples/10-integrations/pwa 展开,讲解如何用 Dioxus 与 Dioxus CLI 构建一个可安装、可离线访问的渐进式 Web 应用(PWA)。读完本文后,你将掌握 PWA 所必需的 Service Worker 注册与 Manifest 注入方式、Dioxus.toml 中 Web 端关键配置的取值含义,以及带版本控制的缓存策略如何实现,并可以直接把该示例工程当作自己项目的 PWA 模板。
为什么 PWA 在 Dioxus 中需要额外配置
Dioxus 本身是一个跨 Web、桌面与移动端的 Rust 应用框架,其 Web 端由 CLI 负责编译产物打包与 HTML 生成。但 PWA 功能依赖浏览器的 Service Worker(服务工作者)与 Web App Manifest(清单文件)两项机制,这两者目前无法完全由纯 Rust 侧完成——官方示例也明确说明"这不是 100% 纯 Rust 实现"。因此该示例的核心思路是:
- 在 Rust 侧只写普通的 Dioxus Web 应用(
dioxus::launch); - 通过自定义
index.html手动注入 Service Worker 注册脚本与 Manifest 引用; - 通过
public/静态目录提供manifest.json与sw.js,由 CLI 原样复制到构建产物中。
官方 README 同时指出,这个示例"非常适合作为你自己 PWA 项目的模板"。
运行前提与命令
在动手前需要安装 Dioxus CLI(不确定是否安装时可执行 cargo install dioxus-cli --locked)。随后在示例目录内:
dx serve:启动本地 Web 开发服务器,带热更新;dx build --release:执行发布构建,生成可直接部署到任意 Web 服务器的静态产物。
工程结构与文件职责
示例工程结构如下(与 README 中描述一致):
├── Cargo.toml
├── Dioxus.toml
├── index.html // 本示例必须提供自定义 HTML,用于加载 SW 和 manifest
├── LICENSE
├── public
│ ├── favicon.ico
│ ├── logo_192.png
│ ├── logo_512.png
│ ├── manifest.json // 清单文件,可按需修改
│ └── sw.js // Service Worker,实际项目必须自行修改
├── README.md
└── src
└── main.rs
各文件的定位很清晰:
- src/main.rs:最小 Dioxus 应用,仅用
dioxus::launch(app)启动一个渲染居中欢迎文案的app组件,与 PWA 逻辑完全解耦; - Cargo.toml:依赖
dioxus = { workspace = true, features = ["web"] },即只启用 Web 平台特性; - Dioxus.toml:CLI 的构建与资源目录配置;
- index.html:PWA 的"入口开关",负责注册 Service Worker、声明 Manifest;
- public/manifest.json 与 public/sw.js:PWA 的两块基石文件。
核心一:自定义 index.html 如何被 CLI 消费
PWA 示例中最关键的一步是提供自定义 index.html。示例中的完整内容如下:
<!DOCTYPE html>
<html>
<head>
<title>{app_title}</title>
<script>
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/{base_path}/assets/sw.js");
}
</script>
<link rel="manifest" href="/assets/manifest.json" />
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="UTF-8" />
</head>
<body>
<div id="main"></div>
</body>
</html>
其中几个占位符与路径的来历可以从 CLI 源码中得到印证:
{app_title}占位符:CLI 在生成 HTML 时会将Dioxus.toml中[web.app] title的取值替换进<title>标签。从源码结构看,这一行为实现在 packages/cli/src/build/web.rs 中——Self::replace_or_insert_before("{app_title}", "</title", &title, &mut html),即无论<title>标签是否已存在,都会替换或插入标题。本例中对应的标题值就是Dioxus.toml里的title = "dioxus | ⛺"。{base_path}占位符:用于支持应用部署在子路径下的场景。CLI 的base_path解析逻辑见 packages/cli/src/build/request.rs:优先取命令行参数--base-path,其次回退到Dioxus.toml的[web.app] base_path配置,未配置时按根路径处理。Service Worker 注册路径/{base_path}/assets/sw.js正是利用了这一点,保证 SW 文件位于scope可达范围内。public/目录的资源去向:Dioxus.toml中asset_dir = "public"声明了静态资源目录,构建时sw.js、manifest.json、图标等文件会被复制到产物目录(通常位于dist/assets/下),这就是 HTML 中以/assets/前缀引用它们的原因。
<div id="main"></div> 是 Dioxus Web 端挂载虚拟 DOM 的容器,与默认 HTML 模板一致。
核心二:Dioxus.toml 的 Web 端配置
示例的 Dioxus.toml 完整内容如下,逐项说明:
[application]
# 应用(项目)名称
name = "dioxus-pwa-example"
# Dioxus 应用的默认平台
# 可选值:desktop, web, mobile, ssr
default_platform = "web"
# `build` 与 `serve` 的产物输出目录
out_dir = "dist"
# 静态资源(public)目录
asset_dir = "public"
[web.app]
# HTML 标题(即替换 {app_title} 的值)
title = "dioxus | ⛺"
[web.watcher]
# watcher 触发时是否重新生成 index.html
reload_html = true
# watcher 监控哪些文件/目录
watch_path = ["src", "public"]
几个与 PWA 场景直接相关的点:
default_platform = "web":让dx serve/dx build默认面向 Web 平台,无需每次加--platform参数;asset_dir = "public":PWA 所需的manifest.json与sw.js放在该目录下,即可随构建一起分发;[web.watcher] watch_path包含"public":开发时修改public/下的静态文件(例如调整图标)也能触发热更新流程;reload_html = true表示 watcher 触发时会重新生成index.html——对依赖自定义 HTML 的 PWA 项目来说这是一个值得注意的行为。
核心三:manifest.json 字段逐项解析
Web App Manifest 决定应用"是否可安装"以及安装后的外观。示例 manifest.json 中出现的字段如下:
| 字段 | 示例值 | 含义 |
|---|---|---|
name |
"Dioxus" |
应用完整名称,安装提示与主屏显示用 |
short_name |
"Dioxus" |
空间受限处(如桌面图标)显示的短名 |
icons |
logo_192.png、logo_512.png(含 "sizes": "any" 条目) |
安装图标。512 图标同时声明了 "sizes": "any",用于可缩放场景(如 splash 屏) |
start_url |
"/" |
用户点击图标启动时打开的页面 |
id |
"/" |
应用的唯一标识,用于区分不同版本 |
scope |
"/" |
应用的"作用域",SW 与 Manifest 生效的 URL 范围 |
display |
"standalone" |
以独立窗口运行,隐藏浏览器 UI |
display_override |
["window-control-overlay", "standalone"] |
提示浏览器在支持时可叠加窗口控制条,回退到 standalone |
theme_color / background_color |
"#000000" / "#ffffff" |
工具栏主题色与启动背景色 |
dir / lang |
"ltr" / "en" |
文本方向与语言 |
orientation |
"portrait" |
启动时请求的屏幕方向 |
配套的图标文件为 public/logo_192.png 与 public/logo_512.png,外加 public/favicon.ico。README 明确提示"manifest 文件可按需自行编辑",实际项目中应替换为自己的应用名、图标与配色。
核心四:sw.js 的缓存策略逐段解读
示例 sw.js 是一份带完整注释的 Service Worker 参考实现,覆盖了 SW 生命周期的三个事件,可以逐段对照阅读:
1. 版本前缀与离线基线资源
var version = 'v1.0.0::';
var offlineFundamentals = [
// 在此添加你想要预先缓存的文件
'favicon.ico'
];
缓存名统一带 version 前缀(v1.0.0::fundamentals、v1.0.0::pages)。README 对此有明确提醒:sw.js 在实际项目中必须修改——你需要扩充 offlineFundamentals 清单,把真正需要离线的核心资源(HTML 入口、关键 JS/CSS 等)加入预缓存。注意其语义:安装阶段若任一资源下载失败,整个 Service Worker 安装会失败。
2. install 事件:预缓存
self.addEventListener("install", function (event) {
event.waitUntil(
caches.open(version + 'fundamentals')
.then(function (cache) {
return cache.addAll(offlineFundamentals);
})
);
});
event.waitUntil 阻塞安装流程直至 Promise 结算;cache.addAll 会依次请求并把 offlineFundamentals 中的所有资源写入版本化缓存。
3. fetch 事件:"缓存优先、网络刷新"策略
这是示例中最值得关注的策略,属于"progressive networking(渐进式网络)"模式:
- 非 GET 请求直接放行到网络,交给客户端处理失败重试;
- GET 请求先
caches.match(event.request)查缓存; - 命中缓存也照样发起网络请求:把网络响应的副本(
response.clone())写入version + 'pages'缓存,但本次响应优先返回缓存内容——即"立刻给出旧数据,后台刷新缓存",产生"最终新鲜"(eventually fresh)的响应; - 缓存与网络都失败时,进入
unableToResolve()兜底,程序化返回一个503 Service Unavailable的 HTML 响应,保证用户始终能看到有意义的错误页,而不是浏览器默认的错误界面。
4. activate 事件:清理旧缓存
self.addEventListener("activate", function (event) {
event.waitUntil(
caches.keys().then(function (keys) {
return Promise.all(
keys.filter(function (key) {
return !key.startsWith(version);
}).map(function (key) {
return caches.delete(key);
})
);
})
);
});
新 Worker 激活后,删除所有不以当前 version 为前缀的历史缓存。配合上面的版本前缀机制,升级 version 号即可让旧缓存一次性整体失效——这正是文件头注释所说"版本号在更新 worker 逻辑时很有用"的原因。
落地为生产项目的注意事项
结合示例与官方说明,把该模板用于真实项目时至少要做以下事情:
- 必须改写
sw.js:扩充offlineFundamentals预缓存清单,并按业务调整fetch策略(例如 API 请求可能希望"网络优先"而非"缓存优先");每次修改逻辑后递增version; - 按品牌改写
manifest.json与图标文件,start_url/scope需与站点实际部署路径一致;若部署在子路径下,同步配置 CLI 的base_path; - 保留自定义
index.html:SW 注册与 manifest 引用是 PWA 生效的前提,删除它会退化为普通 Web 应用; - 若希望尽可能贴近 100% 纯 Rust,官方 README 提到可以尝试
wasi-worker方案替换 JS 编写的 Service Worker 文件,但 JSON 清单文件仍然是必需的; - 构建发布流程不变:
dx build --release产出后部署到任意静态 Web 服务器即可,PWA 的生效前提是 HTTPS(浏览器对 Service Worker 的要求)。
参考文件索引
| 内容 | 路径 |
|---|---|
| 示例说明(本文依据的原始文档) | examples/10-integrations/pwa/README.md |
| 自定义 HTML 模板 | examples/10-integrations/pwa/index.html |
| CLI 配置 | examples/10-integrations/pwa/Dioxus.toml |
| 应用入口 | examples/10-integrations/pwa/src/main.rs |
| 清单文件 | examples/10-integrations/pwa/public/manifest.json |
| Service Worker | examples/10-integrations/pwa/public/sw.js |
| CLI 标题/base_path 处理源码 | packages/cli/src/build/web.rs、packages/cli/src/build/request.rs |
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00