Files
rednote-tool-studio/vendor/minitool-zip-builder/references/performance-budget.md
T
2026-09-17 21:54:33 +08:00

132 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 小工具性能预算与降级规范
> 本文提供移动端 WebView 的设计预算与降级要求。Skill 只能检查静态产物和代码设计,不能从源码推断真实帧率、内存或真机表现;没有运行数据时必须明确标记“未实测”。
## 目录
- §1 交付门禁
- §2 静态数据与长列表
- §3 图片、音频与视频
- §4 WebGL 资源与降级基线
- §5 运行时降级与兜底
- §6 交付检查
---
## 1. 交付门禁
| 项目 | 门禁 | 原因 |
| --- | --- | --- |
| 最终 zip | **不超过 10 MiB**;建议不超过 2 MiB | 10 MiB 是上传上限,不是性能目标 |
| 单个 `.html` / `.css` / `.js` / `.json` | 不设硬上限;超过 2 MiB 触发风险提示 | 大源码会增加读取、解析、编译和峰值内存;即使压缩后很小也一样 |
| 上述文本文件解压后合计 | 不设硬上限;超过 5 MiB 触发风险提示 | 用于发现把数据库或生成产物塞进代码包的情况 |
| 单条 Base64 | 解码后不超过 1 MiB;超过 100 KiB 提示风险,超过 1 MiB 视为大 Base64,必须改成独立包内文件 | Base64 体积约增加 1/3,还会产生字符串与解码副本 |
先检查可用运行时,有哪个就用哪个;两个脚本均为单文件且只使用各自标准库,不要为了审计临时安装 Node、Python 或第三方依赖:
```bash
# 有 Node 18+:脚本零第三方依赖
node <skill目录>/scripts/audit_artifact.mjs ./dist
node <skill目录>/scripts/audit_artifact.mjs ./tool.zip
# 有 Python 3:脚本只用标准库,并可检查 zip 内部文本
python3 <skill目录>/scripts/audit_artifact.py ./dist
python3 <skill目录>/scripts/audit_artifact.py ./tool.zip
```
两个运行时都可用时任选其一;Python 版可额外读取 zip 的文件清单与解压后体积。Node 版对产物目录做完整体积审计,对最终 zip 只检查包体,因此必须在压缩前先审计目录。脚本只依据文件系统和 zip 元数据做确定性检查:zip 超过 10 MiB 报 `ERROR`;zip 超过建议的 2 MiB、单个或合计文本较大报 `WARN`。
脚本不解析或猜测 JS / HTML / CSS 语义。Base64、WebGL 结构与降级策略由本规范约束,并在 §6 按代码逻辑审查。仓库发布检查会把两个脚本复制到无项目依赖的临时目录实际执行;依赖 pip / npm 包或相对模块时发布失败。
如果 Node 与 Python 都不可用,仍须人工完成 §6 清单,并至少:查看最终 zip 文件大小;确认 Base64 资源的生成来源与实际字节数;检查最大的 HTML / CSS / JS / JSON 是否混入大型数据;逐项检查 WebGL 预算与降级。交付说明中标记“未运行自动审计”及人工检查结果,不得直接声称审计通过。不要通过改后缀或运行时解压字符串来隐藏大数据,这只会把开销推迟到运行时。
---
## 2. 静态数据与长列表
`.js` / `.json` 是静态资源,不是数据库。**不要把数万条记录、完整业务库、日志或抓取结果生成成 JS 数组。** JSON 与 JS 分文件只能改善组织,不能消除下载、解析和内存成本。
优先按以下顺序缩减:
1. 只保留完成核心功能必需的字段和记录,删除重复字段、长描述和历史快照。
2. 能预计算的统计、搜索索引和分类结果在构建期生成;不要在首屏对全量数据反复遍历。
3. 大型只读数据集改为摘要、分段样例或让用户通过 `<input type="file">` 按需导入。若完整离线数据不可删且仍超门禁,应明确说明该需求不适合小工具,而不是继续打包。
4. 用户产生或导入的数据写入 IndexedDB;IndexedDB 用于运行期持久化,不用于掩盖巨大的内置种子数据。
渲染列表时:
- 首屏只创建可见项;长列表使用分页或虚拟滚动,不一次性拼接整份 `innerHTML`。
- 搜索输入按交互频率和数据规模采用防抖,避免每次按键都触发完整查询;最终输入应及时执行。数据量较大时可预先建立小型索引,避免反复扫描所有字段。
- 非首屏工作分批执行并主动让出主线程,避免把解析、计算和渲染集中在同一个同步流程中。
- 不在循环中反复读写布局属性;批量生成 DOM 后一次挂载。
---
## 3. 图片、音频与视频
- 小型图片、图标等可以使用 Base64;单条解码后超过 100 KiB 时应优先改为包内文件,超过 1 MiB 不允许内嵌。
- 图片按真机展示尺寸缩放并压缩,优先 WebP;不要为了 300 px 展示区域打包 4K 原图。
- 音视频先裁剪时长,再降低分辨率、帧率和码率。5 分钟视频通常不适合随小工具离线交付;优先改为短片段、封面 + 交互说明,或让用户运行时自行选择本地视频。
- 视频设置 `preload="metadata"` 或 `preload="none"`,提供 `poster`;未进入播放页前不要创建或解码媒体。
- 同时只保留必要的媒体实例;离开页面后暂停播放、清空不再使用的 `src` 并释放对象 URL。
`FileReader.readAsDataURL()` 可用于用户刚选择的小文件预览或 JSBridge 明确要求 data URI 的短暂转换;大文件不要把结果持久写回静态源码,优先用对象 URL 预览,并在不用时 `URL.revokeObjectURL()`。注意:即使体积很小,`<video>` / `<audio>` 的 `data:` 媒体源仍不受容器 CSP 支持,应引用包内媒体文件。
---
## 4. WebGL 资源与降级基线
WebGL 是可用能力,不代表所有设备都能稳定运行复杂场景。以下数值是生成代码时采用的保守设计预算,不是 Skill 对实际性能的测量结果。目标体验按 **30 FPS 可交互**设计,60 FPS 仅作为有运行数据支持时的高档增强。
普通 Canvas 2D 不纳入本节的 GPU 分档要求;按实际展示尺寸创建画布、避免无意义的重复全量绘制即可。只有使用 WebGL 上下文时才执行以下资源预算、降档和 context 兜底。
### 初始预算
| 指标 | 默认档 | 低档 / 降级档 |
| --- | --- | --- |
| WebGL drawing buffer DPR | `min(devicePixelRatio, 1.5)` | `1` |
| WebGL drawing buffer 像素数 | 不超过约 200 万 | 不超过约 100 万 |
| 单张纹理边长 | 不超过 2048 | 不超过 1024 |
| 估算纹理显存 | 不超过 64 MiB | 不超过 32 MiB |
| 每帧 draw call | 不超过 100 | 不超过 50 |
| 每帧三角形 | 不超过 100k | 不超过 50k |
| 帧率目标 | 稳定 30 FPS,设备有余量再升档 | 稳定 24–30 FPS |
这些是移动 WebView 的保守初始预算。代码可以根据用户提供的设备数据调整;没有实测数据时保持预算与降级路径,不得声称已经达到某个帧率或通过真机性能验收。
### 渲染规则
- 优先兼容 WebGL 1;使用 WebGL 2 特性时必须检测能力并提供 WebGL 1 或非 WebGL 兜底。
- 初始化先使用低 / 中档,不按高 DPR 直接创建最大缓冲区。尺寸变化时重新计算并继续受像素预算约束。
- 纹理使用实际需要的尺寸;复用纹理、材质、几何体和 framebuffer。估算 RGBA8 纹理最低占用:`宽 × 高 × 4`,mipmap 还会额外增加约 1/3。
- 合并可合并的几何与 draw call,视锥 / 距离裁剪不可见对象;粒子、阴影、后处理、透明叠加和实时反射必须能逐项关闭。
- shader 在初始化或切换场景时编译;纹理上传和模型解析分批进行,不在动画帧中首次集中完成。
- 动画循环内避免创建对象 / 数组 / 大字符串;禁止每帧 `readPixels()`、`toDataURL()`、大面积 `getImageData()` 或同步回读 GPU。
- 页面不可见时通过 `visibilitychange` 停止 `requestAnimationFrame`、媒体和定时器;恢复后重置时间差,避免补算大量帧。
- 处理 `webglcontextlost` / `webglcontextrestored`;context 丢失时停止渲染并展示轻量状态,不要无限重建。
---
## 5. 运行时降级与兜底
不要依赖机型名单。以实际帧耗时和能力检测决定档位:
1. 首次进入采用低 / 中档,逐步加载非必要效果。
2. 运行时观测到持续掉帧或交互响应变差时,逐级降低 DPR、粒子数、阴影、后处理、可视距离和动画频率。没有运行数据时只确认降级路径存在,不判断是否达到触发条件。
3. 降到最低档仍不可交互时,停止高成本循环并切换 Canvas 2D、静态图或简化 DOM 视图。
4. `getContext()` 失败、shader 编译 / 链接失败或 context 反复丢失时,必须进入可理解的兜底界面;不能白屏、死循环重试或持续弹错。
如果核心功能并不依赖 3D,默认选择 DOM / CSS / Canvas 2D。WebGL 应解决明确的视觉或计算需求,而不是作为普通表单、列表和信息展示的默认技术栈。
---
## 6. 交付检查
- [ ] 已按环境选择 Node / Python 自动审计;若两者均不可用,已记录人工审计结果与“未运行自动审计”
- [ ] 未把大数据集、日志或生成内容写进 JS / JSON,长列表已分页或虚拟化
- [ ] 单条 Base64 解码后不超过 1 MiB;超过 100 KiB 的条目已检查并优先改为独立文件
- [ ] 静态设计上未把大文件解析、全量 DOM 构建和大资源初始化集中在首屏同步流程;实际耗时没有运行数据时标记“未实测”
- [ ] WebGL 默认受 DPR、像素、纹理、draw call 和三角形预算约束
- [ ] WebGL 有动态降档、页面隐藏暂停、context lost 处理和非 WebGL 兜底
- [ ] 已区分静态检查与运行实测:有用户提供的运行数据时记录设备、首屏时间、帧率和降级情况;没有数据时标记“性能未实测”,不宣称真机性能合格