xhs-minitool-creator 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +89 -0
  3. package/bin/create.mjs +321 -0
  4. package/package.json +39 -0
  5. package/src/index.mjs +9 -0
  6. package/src/pack.mjs +188 -0
  7. package/src/skill.mjs +188 -0
  8. package/src/validate.mjs +347 -0
  9. package/src/vite-preset.mjs +46 -0
  10. package/src/zip.mjs +190 -0
  11. package/template/.agents/skills/minitool-zip-builder/SKILL.md +47 -0
  12. package/template/.agents/skills/minitool-zip-builder/references/cross-platform-h5.md +69 -0
  13. package/template/.agents/skills/minitool-zip-builder/references/css-compatibility.md +171 -0
  14. package/template/.agents/skills/minitool-zip-builder/references/device-capabilities.md +169 -0
  15. package/template/.agents/skills/minitool-zip-builder/references/js-compatibility.md +61 -0
  16. package/template/.agents/skills/minitool-zip-builder/references/jsbridge-api.md +192 -0
  17. package/template/.agents/skills/minitool-zip-builder/references/performance-budget.md +131 -0
  18. package/template/.agents/skills/minitool-zip-builder/references/zip-artifact-spec.md +206 -0
  19. package/template/.agents/skills/minitool-zip-builder/scripts/audit_artifact.mjs +95 -0
  20. package/template/.agents/skills/minitool-zip-builder/scripts/audit_artifact.py +109 -0
  21. package/template/.agents/skills/minitool-zip-builder/skill-package.json +5 -0
  22. package/template/_gitignore +7 -0
  23. package/template/index.html +47 -0
  24. package/template/package.json +22 -0
  25. package/template/public/icons/icon-192.svg +4 -0
  26. package/template/public/icons/icon-512.svg +4 -0
  27. package/template/src/lib/storage.js +54 -0
  28. package/template/src/main.js +81 -0
  29. package/template/src/styles/app.css +229 -0
  30. package/template/vite.config.js +9 -0
@@ -0,0 +1,61 @@
1
+ # JavaScript 兼容性规范
2
+
3
+ > 小工具 JavaScript 的最低兼容基线是 **Android 8.1 出场 Chrome / WebView 61**。最终代码必须能在 Chrome 61 解析和运行;iOS 18.4+ 支持的额外能力只能用于能力检测后的增强路径。Chrome 61 完整支持 ES2017,最终代码以 ES2017 为构建目标。
4
+
5
+ ## 1. 语法基线
6
+
7
+ 最终 zip 中的 JS 须兼容 Chrome 61:
8
+
9
+ - 直接编写并交付 `.js` 时可使用 ES2017,包括 async / await、`Object.entries` / `Object.values`、`Array.prototype.includes`、`String.prototype.padStart` / `padEnd`。
10
+ - ES2018+ 语法须由构建链转译,例如对象 spread、异步迭代、可选链、空值合并、逻辑赋值、class 私有字段、static block、BigInt 字面量和 top-level await;更新的运行时 API 仍须做能力检测或提供必要的局部实现。
11
+ - 正则表达式避免依赖 lookbehind、Unicode property escapes 等较新的语法特性。
12
+
13
+ 语法不兼容会在脚本解析阶段直接失败,无法通过运行时 `if` 兜底。
14
+
15
+ ## 2. 有构建链与无构建链
16
+
17
+ ### 直接交付静态三件套
18
+
19
+ 没有现成构建链时,不为兼容性临时引入 Babel、core-js 或新的 npm 依赖;直接按 ES2017 编写。
20
+
21
+ ### 项目已有构建链
22
+
23
+ 可以在源码中使用构建链已支持的新语法。Vite 项目使用以下构建目标:
24
+
25
+ ```js
26
+ // vite.config.js
27
+ export default {
28
+ build: { target: ['es2017', 'chrome61'] },
29
+ }
30
+ ```
31
+
32
+ 最终产物须:
33
+
34
+ - 转译到 ES2017 / Chrome 61;
35
+ - 只把构建后的静态文件放进 zip,不带 `node_modules`、source map 或构建配置。
36
+
37
+ 转译只解决语法,不会自动补齐所有运行时 API。不要因为构建成功就假定新 API 可用。
38
+
39
+ ## 3. 运行时 API
40
+
41
+ - ES2017 内置 API 和基础 DOM API 可直接使用。
42
+ - `String.prototype.replaceAll`、`Array.prototype.at`、`Object.hasOwn`、`structuredClone` 等更新 API 不应直接作为唯一实现路径。
43
+ - 使用非基础 Web API 前先做能力检测;不可用时隐藏增强功能、使用简单替代实现或给出清晰提示。
44
+ - 只补功能实际需要的小型本地 fallback,不引入整套通用 polyfill;所有代码仍须随包离线交付。
45
+ - 能力检测基于对象 / 方法是否存在,不按 UA、机型或系统版本字符串分支。
46
+ - 不为被容器明确禁止的能力添加 polyfill;能力边界仍以 [device-capabilities.md](./device-capabilities.md) 为准。
47
+
48
+ ## 4. 跨内核行为
49
+
50
+ - 日期字符串使用明确的 ISO 格式或拆分为年月日构造,不依赖非标准字符串解析。
51
+ - 不依赖对象遍历顺序表达业务优先级;需要顺序时使用数组。
52
+ - 对 `Intl` 格式化结果、字体度量、滚动和软键盘行为保留布局余量,不把不同内核的细微差异当作固定输出。
53
+ - 触摸、滚动和安全区规则见 [cross-platform-h5.md](./cross-platform-h5.md)。
54
+
55
+ ## 5. 交付检查
56
+
57
+ - [ ] 直接交付的 JS 不超出 ES2017;使用更新语法时已有构建链负责转译
58
+ - [ ] 使用 Vite 时构建目标包含 `es2017` 和 `chrome61`,且 zip 中只保留最终静态文件
59
+ - [ ] 新 Web API 有能力检测和局部降级,不使用 UA 猜测能力
60
+ - [ ] 未把语法转译误认为运行时 API polyfill
61
+ - [ ] 未进行实际设备验证时标记“兼容性未实测”,不宣称已在真机通过
@@ -0,0 +1,192 @@
1
+ # 小工具 JSBridge API 规范
2
+
3
+ 容器会注入 **`window.xhs.miniTool.*`**,业务代码通过它调用 Native 能力。字段以本文档为准,表中未声明的字段不要传。
4
+
5
+ ## 调用约定
6
+
7
+ | 项 | 规则 |
8
+ | --- | --- |
9
+ | 入口 | `window.xhs.miniTool.<apiName>(options)` |
10
+ | 两种用法 | 不传回调 → 返回 `Promise`;传 `success` / `fail` / `complete` 任一 → 返回 `undefined`,经回调拿结果 |
11
+ | 成功 | Promise resolve / `success(result)`;关注业务字段(见各 API「结果」),无业务字段则结果为空 |
12
+ | 失败 | Promise reject / `fail(error)`;失败原因看 `error.errMsg`(形如 `<apiName>:fail …`),可能带 `errCode` |
13
+ | `complete` | 成功或失败都会回调,参数为对应的 result / error |
14
+ | 禁止 | 不要调用本文未列出的 API,不要传字段表未声明的字段 |
15
+
16
+ ## API 索引
17
+
18
+ | API | 说明 |
19
+ | --- | --- |
20
+ | [`postNote`](#postnote) | 发布笔记 |
21
+ | [`saveImageToPhotosAlbum`](#saveimagetophotosalbum) | 保存图片到系统相册 |
22
+ | [`openRedPage`](#openredpage) | 通用原生页面跳转 |
23
+ | [`writeTempFile`](#writetempfile) | base64 转临时文件 |
24
+
25
+ ## postNote
26
+
27
+ 发布笔记。
28
+
29
+ - **调用**:`window.xhs.miniTool.postNote(options)`
30
+
31
+ ### 使用规则
32
+
33
+ - `mediaInfo` 必填;`image_resources`(图文)、`video_resources`(视频)、`live_photo_resources`(实况)至少传一种,可同时传。
34
+ - 所有地址(`url` / `video_url` / `cover_url`)承载 base64 data:uri 或网络地址,格式由 Native 侧校验。
35
+
36
+ ### 请求参数
37
+
38
+ | 字段 | 类型 | 必填 | 说明 |
39
+ | --- | --- | :---: | --- |
40
+ | `title` | string | 否 | 标题,最长 20 |
41
+ | `content` | string | 否 | 正文,最长 1000 |
42
+ | `pageType` | `"video_publish" \| "photo_publish" \| "slides_edit"` | 否 | 页面类型 |
43
+ | `mediaInfo` | object | 是 | 见「mediaInfo」 |
44
+ | `tags` | string | 否 | 标签 |
45
+
46
+ **`mediaInfo`**
47
+
48
+ | 字段 | 类型 | 必填 | 说明 |
49
+ | --- | --- | :---: | --- |
50
+ | `image_resources` | `{ url }[]` | 否 | 图文,1–18 张;`url` 图片地址 |
51
+ | `video_resources` | `{ video_url, cover_url? }` | 否 | 视频;`video_url` 视频地址,`cover_url` 可选封面 |
52
+ | `live_photo_resources` | `{ url, video_url }[]` | 否 | 实况,1–18 张;`url` 动图封面,`video_url` 动图视频 |
53
+
54
+ ### 结果
55
+
56
+ 成功结果仅含通用 `errMsg`,无额外业务字段。
57
+
58
+ ### 示例
59
+
60
+ 图文笔记:
61
+
62
+ ```js
63
+ await window.xhs.miniTool.postNote({
64
+ title: "标题",
65
+ content: "正文",
66
+ pageType: "photo_publish",
67
+ mediaInfo: {
68
+ image_resources: [{ url: "data:image/png;base64,..." }],
69
+ },
70
+ });
71
+ ```
72
+
73
+ 视频笔记:
74
+
75
+ ```js
76
+ await window.xhs.miniTool.postNote({
77
+ pageType: "video_publish",
78
+ mediaInfo: {
79
+ video_resources: { video_url: "...", cover_url: "..." },
80
+ },
81
+ });
82
+ ```
83
+
84
+ ## saveImageToPhotosAlbum
85
+
86
+ 保存图片到系统相册。
87
+
88
+ - **调用**:`window.xhs.miniTool.saveImageToPhotosAlbum(options)`
89
+
90
+ ### 使用规则
91
+
92
+ - `filePath` 只接受 base64 data:uri 或本地路径,传网络地址(`http(s)://`)会失败。
93
+ - 已有 base64 时可先调 `writeTempFile` 换取本地 `filePath`,再传入本 API。
94
+ - 需由用户主动操作触发;首次调用可能弹系统相册权限。
95
+
96
+ ### 请求参数
97
+
98
+ | 字段 | 类型 | 必填 | 说明 |
99
+ | --- | --- | :---: | --- |
100
+ | `filePath` | string | 是 | 本地图片路径(base64 data:uri 或本地引用),不支持网络地址 |
101
+
102
+ ### 结果
103
+
104
+ 成功结果仅含通用 `errMsg`,无额外业务字段。
105
+
106
+ ### 示例
107
+
108
+ ```js
109
+ await window.xhs.miniTool.saveImageToPhotosAlbum({
110
+ filePath: "data:image/png;base64,...",
111
+ });
112
+ ```
113
+
114
+ ## openRedPage
115
+
116
+ 通用原生页面跳转。
117
+
118
+ - **调用**:`window.xhs.miniTool.openRedPage(options)`
119
+
120
+ ### 使用规则
121
+
122
+ - `type` 命中 Native 规则表白名单才放行,未命中直接失败;规则表由客户端维护。
123
+ - `params` 为语义参数,由规则表映射到目标页面;信任字段由客户端强制注入。
124
+ - 跳转会离开当前小工具页面,调用前应完成本地状态持久化。
125
+
126
+ ### 请求参数
127
+
128
+ | 字段 | 类型 | 必填 | 说明 |
129
+ | --- | --- | :---: | --- |
130
+ | `type` | string | 是 | 规则表 key(如 search / note / user…) |
131
+ | `params` | object | 否 | 语义参数(如 `{ keyword }`) |
132
+
133
+ ### 结果
134
+
135
+ 成功结果仅含通用 `errMsg`,无额外业务字段。
136
+
137
+ ### 示例
138
+
139
+ ```js
140
+ await window.xhs.miniTool.openRedPage({
141
+ type: "search",
142
+ params: { keyword: "连衣裙" },
143
+ });
144
+ ```
145
+
146
+ ## writeTempFile
147
+
148
+ base64 转临时文件,返回可传给其他 API 的 `filePath`。
149
+
150
+ - **调用**:`window.xhs.miniTool.writeTempFile(options)`
151
+
152
+ ### 使用规则
153
+
154
+ - 用于把内存中的 base64(Canvas 导出、选图预览等)落成本地文件,换取 `filePath`。
155
+ - **`data` 必须是完整 data:uri**,即 `data:<mime>;base64,<payload>` 开头(如 `data:image/png;base64,iVBORw0KGgo...`);只传裸 base64 字符串会失败。
156
+ - `canvas.toDataURL()` / `FileReader.readAsDataURL()` 的返回值已是 data:uri,**不要**再截取 `,` 之后的部分。
157
+ - 返回的 `filePath` 为临时文件,即用即弃。
158
+
159
+ ### 请求参数
160
+
161
+ | 字段 | 类型 | 必填 | 说明 |
162
+ | --- | --- | :---: | --- |
163
+ | `data` | string | 是 | 完整 data:uri(`data:<mime>;base64,<payload>`),不接受裸 base64 |
164
+
165
+ ### 结果
166
+
167
+ 成功结果在通用 `errMsg` 之外附带:
168
+
169
+ | 字段 | 类型 | 说明 |
170
+ | --- | --- | --- |
171
+ | `filePath` | string | 转换得到的临时文件路径 |
172
+
173
+ ### 示例
174
+
175
+ ```js
176
+ // canvas.toDataURL() 直接就是 data:uri,原样传入
177
+ const { filePath } = await window.xhs.miniTool.writeTempFile({
178
+ data: canvas.toDataURL("image/png"), // "data:image/png;base64,iVBORw0KGgo..."
179
+ });
180
+ await window.xhs.miniTool.saveImageToPhotosAlbum({ filePath });
181
+ ```
182
+
183
+ 错误用法:
184
+
185
+ ```js
186
+ // ✗ 裸 base64,会失败
187
+ await window.xhs.miniTool.writeTempFile({ data: "iVBORw0KGgo..." });
188
+ // ✗ 手动去掉了 data:uri 前缀
189
+ await window.xhs.miniTool.writeTempFile({
190
+ data: canvas.toDataURL("image/png").split(",")[1],
191
+ });
192
+ ```
@@ -0,0 +1,131 @@
1
+ # 小工具性能预算与降级规范
2
+
3
+ > 本文提供移动端 WebView 的设计预算与降级要求。Skill 只能检查静态产物和代码设计,不能从源码推断真实帧率、内存或真机表现;没有运行数据时必须明确标记“未实测”。
4
+
5
+ ## 目录
6
+
7
+ - §1 交付门禁
8
+ - §2 静态数据与长列表
9
+ - §3 图片、音频与视频
10
+ - §4 WebGL 资源与降级基线
11
+ - §5 运行时降级与兜底
12
+ - §6 交付检查
13
+
14
+ ---
15
+
16
+ ## 1. 交付门禁
17
+
18
+ | 项目 | 门禁 | 原因 |
19
+ | --- | --- | --- |
20
+ | 最终 zip | **不超过 10 MiB**;建议不超过 2 MiB | 10 MiB 是上传上限,不是性能目标 |
21
+ | 单个 `.html` / `.css` / `.js` / `.json` | 不设硬上限;超过 2 MiB 触发风险提示 | 大源码会增加读取、解析、编译和峰值内存;即使压缩后很小也一样 |
22
+ | 上述文本文件解压后合计 | 不设硬上限;超过 5 MiB 触发风险提示 | 用于发现把数据库或生成产物塞进代码包的情况 |
23
+ | 单条 Base64 | 解码后不超过 1 MiB;超过 100 KiB 提示风险,超过 1 MiB 视为大 Base64,必须改成独立包内文件 | Base64 体积约增加 1/3,还会产生字符串与解码副本 |
24
+
25
+ 先检查可用运行时,有哪个就用哪个;两个脚本均为单文件且只使用各自标准库,不要为了审计临时安装 Node、Python 或第三方依赖:
26
+
27
+ ```bash
28
+ # 有 Node 18+:脚本零第三方依赖
29
+ node <skill目录>/scripts/audit_artifact.mjs ./dist
30
+ node <skill目录>/scripts/audit_artifact.mjs ./tool.zip
31
+
32
+ # 有 Python 3:脚本只用标准库,并可检查 zip 内部文本
33
+ python3 <skill目录>/scripts/audit_artifact.py ./dist
34
+ python3 <skill目录>/scripts/audit_artifact.py ./tool.zip
35
+ ```
36
+
37
+ 两个运行时都可用时任选其一;Python 版可额外读取 zip 的文件清单与解压后体积。Node 版对产物目录做完整体积审计,对最终 zip 只检查包体,因此必须在压缩前先审计目录。脚本只依据文件系统和 zip 元数据做确定性检查:zip 超过 10 MiB 报 `ERROR`;zip 超过建议的 2 MiB、单个或合计文本较大报 `WARN`。
38
+
39
+ 脚本不解析或猜测 JS / HTML / CSS 语义。Base64、WebGL 结构与降级策略由本规范约束,并在 §6 按代码逻辑审查。仓库发布检查会把两个脚本复制到无项目依赖的临时目录实际执行;依赖 pip / npm 包或相对模块时发布失败。
40
+
41
+ 如果 Node 与 Python 都不可用,仍须人工完成 §6 清单,并至少:查看最终 zip 文件大小;确认 Base64 资源的生成来源与实际字节数;检查最大的 HTML / CSS / JS / JSON 是否混入大型数据;逐项检查 WebGL 预算与降级。交付说明中标记“未运行自动审计”及人工检查结果,不得直接声称审计通过。不要通过改后缀或运行时解压字符串来隐藏大数据,这只会把开销推迟到运行时。
42
+
43
+ ---
44
+
45
+ ## 2. 静态数据与长列表
46
+
47
+ `.js` / `.json` 是静态资源,不是数据库。**不要把数万条记录、完整业务库、日志或抓取结果生成成 JS 数组。** JSON 与 JS 分文件只能改善组织,不能消除下载、解析和内存成本。
48
+
49
+ 优先按以下顺序缩减:
50
+
51
+ 1. 只保留完成核心功能必需的字段和记录,删除重复字段、长描述和历史快照。
52
+ 2. 能预计算的统计、搜索索引和分类结果在构建期生成;不要在首屏对全量数据反复遍历。
53
+ 3. 大型只读数据集改为摘要、分段样例或让用户通过 `<input type="file">` 按需导入。若完整离线数据不可删且仍超门禁,应明确说明该需求不适合小工具,而不是继续打包。
54
+ 4. 用户产生或导入的数据写入 IndexedDB;IndexedDB 用于运行期持久化,不用于掩盖巨大的内置种子数据。
55
+
56
+ 渲染列表时:
57
+
58
+ - 首屏只创建可见项;长列表使用分页或虚拟滚动,不一次性拼接整份 `innerHTML`。
59
+ - 搜索输入按交互频率和数据规模采用防抖,避免每次按键都触发完整查询;最终输入应及时执行。数据量较大时可预先建立小型索引,避免反复扫描所有字段。
60
+ - 非首屏工作分批执行并主动让出主线程,避免把解析、计算和渲染集中在同一个同步流程中。
61
+ - 不在循环中反复读写布局属性;批量生成 DOM 后一次挂载。
62
+
63
+ ---
64
+
65
+ ## 3. 图片、音频与视频
66
+
67
+ - 小型图片、图标等可以使用 Base64;单条解码后超过 100 KiB 时应优先改为包内文件,超过 1 MiB 不允许内嵌。
68
+ - 图片按真机展示尺寸缩放并压缩,优先 WebP;不要为了 300 px 展示区域打包 4K 原图。
69
+ - 音视频先裁剪时长,再降低分辨率、帧率和码率。5 分钟视频通常不适合随小工具离线交付;优先改为短片段、封面 + 交互说明,或让用户运行时自行选择本地视频。
70
+ - 视频设置 `preload="metadata"` 或 `preload="none"`,提供 `poster`;未进入播放页前不要创建或解码媒体。
71
+ - 同时只保留必要的媒体实例;离开页面后暂停播放、清空不再使用的 `src` 并释放对象 URL。
72
+
73
+ `FileReader.readAsDataURL()` 可用于用户刚选择的小文件预览或 JSBridge 明确要求 data URI 的短暂转换;大文件不要把结果持久写回静态源码,优先用对象 URL 预览,并在不用时 `URL.revokeObjectURL()`。注意:即使体积很小,`<video>` / `<audio>` 的 `data:` 媒体源仍不受容器 CSP 支持,应引用包内媒体文件。
74
+
75
+ ---
76
+
77
+ ## 4. WebGL 资源与降级基线
78
+
79
+ WebGL 是可用能力,不代表所有设备都能稳定运行复杂场景。以下数值是生成代码时采用的保守设计预算,不是 Skill 对实际性能的测量结果。目标体验按 **30 FPS 可交互**设计,60 FPS 仅作为有运行数据支持时的高档增强。
80
+
81
+ 普通 Canvas 2D 不纳入本节的 GPU 分档要求;按实际展示尺寸创建画布、避免无意义的重复全量绘制即可。只有使用 WebGL 上下文时才执行以下资源预算、降档和 context 兜底。
82
+
83
+ ### 初始预算
84
+
85
+ | 指标 | 默认档 | 低档 / 降级档 |
86
+ | --- | --- | --- |
87
+ | WebGL drawing buffer DPR | `min(devicePixelRatio, 1.5)` | `1` |
88
+ | WebGL drawing buffer 像素数 | 不超过约 200 万 | 不超过约 100 万 |
89
+ | 单张纹理边长 | 不超过 2048 | 不超过 1024 |
90
+ | 估算纹理显存 | 不超过 64 MiB | 不超过 32 MiB |
91
+ | 每帧 draw call | 不超过 100 | 不超过 50 |
92
+ | 每帧三角形 | 不超过 100k | 不超过 50k |
93
+ | 帧率目标 | 稳定 30 FPS,设备有余量再升档 | 稳定 24–30 FPS |
94
+
95
+ 这些是移动 WebView 的保守初始预算。代码可以根据用户提供的设备数据调整;没有实测数据时保持预算与降级路径,不得声称已经达到某个帧率或通过真机性能验收。
96
+
97
+ ### 渲染规则
98
+
99
+ - 优先兼容 WebGL 1;使用 WebGL 2 特性时必须检测能力并提供 WebGL 1 或非 WebGL 兜底。
100
+ - 初始化先使用低 / 中档,不按高 DPR 直接创建最大缓冲区。尺寸变化时重新计算并继续受像素预算约束。
101
+ - 纹理使用实际需要的尺寸;复用纹理、材质、几何体和 framebuffer。估算 RGBA8 纹理最低占用:`宽 × 高 × 4`,mipmap 还会额外增加约 1/3。
102
+ - 合并可合并的几何与 draw call,视锥 / 距离裁剪不可见对象;粒子、阴影、后处理、透明叠加和实时反射必须能逐项关闭。
103
+ - shader 在初始化或切换场景时编译;纹理上传和模型解析分批进行,不在动画帧中首次集中完成。
104
+ - 动画循环内避免创建对象 / 数组 / 大字符串;禁止每帧 `readPixels()`、`toDataURL()`、大面积 `getImageData()` 或同步回读 GPU。
105
+ - 页面不可见时通过 `visibilitychange` 停止 `requestAnimationFrame`、媒体和定时器;恢复后重置时间差,避免补算大量帧。
106
+ - 处理 `webglcontextlost` / `webglcontextrestored`;context 丢失时停止渲染并展示轻量状态,不要无限重建。
107
+
108
+ ---
109
+
110
+ ## 5. 运行时降级与兜底
111
+
112
+ 不要依赖机型名单。以实际帧耗时和能力检测决定档位:
113
+
114
+ 1. 首次进入采用低 / 中档,逐步加载非必要效果。
115
+ 2. 运行时观测到持续掉帧或交互响应变差时,逐级降低 DPR、粒子数、阴影、后处理、可视距离和动画频率。没有运行数据时只确认降级路径存在,不判断是否达到触发条件。
116
+ 3. 降到最低档仍不可交互时,停止高成本循环并切换 Canvas 2D、静态图或简化 DOM 视图。
117
+ 4. `getContext()` 失败、shader 编译 / 链接失败或 context 反复丢失时,必须进入可理解的兜底界面;不能白屏、死循环重试或持续弹错。
118
+
119
+ 如果核心功能并不依赖 3D,默认选择 DOM / CSS / Canvas 2D。WebGL 应解决明确的视觉或计算需求,而不是作为普通表单、列表和信息展示的默认技术栈。
120
+
121
+ ---
122
+
123
+ ## 6. 交付检查
124
+
125
+ - [ ] 已按环境选择 Node / Python 自动审计;若两者均不可用,已记录人工审计结果与“未运行自动审计”
126
+ - [ ] 未把大数据集、日志或生成内容写进 JS / JSON,长列表已分页或虚拟化
127
+ - [ ] 单条 Base64 解码后不超过 1 MiB;超过 100 KiB 的条目已检查并优先改为独立文件
128
+ - [ ] 静态设计上未把大文件解析、全量 DOM 构建和大资源初始化集中在首屏同步流程;实际耗时没有运行数据时标记“未实测”
129
+ - [ ] WebGL 默认受 DPR、像素、纹理、draw call 和三角形预算约束
130
+ - [ ] WebGL 有动态降档、页面隐藏暂停、context lost 处理和非 WebGL 兜底
131
+ - [ ] 已区分静态检查与运行实测:有用户提供的运行数据时记录设备、首屏时间、帧率和降级情况;没有数据时标记“性能未实测”,不宣称真机性能合格
@@ -0,0 +1,206 @@
1
+ # 小工具 ZIP 静态包构建规范
2
+
3
+ > 小工具是基于离线 H5 的 app 形式,**纯本地、不联网**,所有资源须打包在 zip 内。窗口样式、导航栏、下拉刷新等外壳行为由**容器**统一控制,无需在包内声明。
4
+
5
+ ## 目录
6
+
7
+ - §1 目录结构与打包
8
+ - §2 支持的文件类型
9
+ - §3 资源加载规则(容器 CSP)
10
+ - §4 路径与引用规则
11
+ - §5 index.html 模板
12
+ - §6 打包前自检
13
+
14
+ ---
15
+
16
+ ## 1. 目录结构与打包
17
+
18
+ **唯一硬性要求:`index.html` 位于 zip 根目录作为入口。** 其余文件 / 文件夹随你组织 —— 可平铺,也可按需分目录(`assets/`、`images/`、`audios/` 等),用相对路径引用即可。
19
+
20
+ 平铺示例:
21
+
22
+ ```
23
+ tool.zip
24
+ ├── index.html # 必需 — 入口,必须在根目录
25
+ ├── main.js
26
+ ├── style.css
27
+ └── logo.png
28
+ ```
29
+
30
+ 分目录示例:
31
+
32
+ ```
33
+ tool.zip
34
+ ├── index.html # 必需 — 入口,必须在根目录
35
+ └── assets/
36
+ ├── style.css
37
+ ├── main.js
38
+ └── images/
39
+ └── ...
40
+ ```
41
+
42
+ | 路径 | 要求 | 说明 |
43
+ | --- | --- | --- |
44
+ | `index.html` | **必须在 zip 根目录** | 唯一入口;不可改名、不可放进子目录 |
45
+ | 其余文件 / 文件夹 | 自由 | JS / CSS / 图片 / 音频等,平铺或分目录皆可,相对路径引用 |
46
+
47
+ ### 禁止出现在 zip 内
48
+
49
+ - `node_modules`、`.git`、`.DS_Store`
50
+ - `*.map`、构建配置文件(`vite.config.*`、`webpack.config.*` 等)
51
+ - 把整个项目多包一层目录,导致 `index.html` 不在根(错误:`app/index.html`;正确:根目录就有 `index.html`)
52
+
53
+ ### 打包方式(关键)
54
+
55
+ **压缩的是「与 `index.html` 同级的那批文件」本身,不是它们所在的文件夹。** 必须先进入该目录再压缩当前目录内容,否则解压后会多套一层目录、`index.html` 不在根,容器无法加载。
56
+
57
+ ```bash
58
+ # ✅ 正确:进入目录,压缩目录“内容”(index.html 直接在 zip 根)
59
+ cd dist && zip -r ../tool.zip . -x '*.DS_Store'
60
+
61
+ # ❌ 错误:压缩目录“本身”,解压后多一层 dist/,index.html 变成 dist/index.html
62
+ zip -r tool.zip dist
63
+ ```
64
+
65
+ 解压后顶层应直接看到 `index.html`,而不是先看到一个文件夹再点进去。
66
+
67
+ ---
68
+
69
+ ## 2. 支持的文件类型
70
+
71
+ zip 内仅允许以下类型:
72
+
73
+ | 类型 | 用途 |
74
+ | --- | --- |
75
+ | `.html` | 入口,有且只有一个 `index.html` |
76
+ | `.css` | 样式文件 |
77
+ | `.js` | 脚本文件 |
78
+ | `.png` / `.jpg` / `.jpeg` / `.gif` / `.webp` / `.svg` | 图片资源 |
79
+ | `.woff` / `.woff2` | 字体文件 |
80
+ | `.json` | 小型静态数据 / 配置;不得作为大型内置数据库,体积门禁见 [performance-budget.md](./performance-budget.md) |
81
+
82
+ ---
83
+
84
+ ## 3. 资源加载规则(容器 CSP)
85
+
86
+ 容器对页面**如何加载各类资源**有强制约束。除包内文件外,按类型另允许 `data:` / `blob:` 等内存来源。
87
+
88
+ | 资源类型 | 允许 | 禁止 |
89
+ | --- | --- | --- |
90
+ | 脚本 `<script>` | 引用包内脚本 `<script src="./app.js">`(同源外链) | 内联 `<script>...</script>`;行内事件 `onclick="..."`;`javascript:` URI;`eval()` / `new Function()`;WebAssembly;外部域名 / `data:` / `blob:` 脚本 |
91
+ | 样式 `<style>` / `<link>` | 内联 `<style>`、行内 `style="..."`、包内样式表 | 外部域名样式表 |
92
+ | 图片 `<img>` / CSS 背景图 | 包内图片 `<img src="./a.png">`;`data:` URI(base64 内嵌);`blob:`(`createObjectURL` 内存对象,如选图预览) | 外部域名图片 |
93
+ | 字体 `@font-face` | 包内字体文件 | 外部域名字体 |
94
+ | 音视频 `<video>` / `<audio>` | 包内媒体文件 | 外部域名媒体、`data:` / `blob:` 媒体 |
95
+ | iframe / object | — | 全部禁止 |
96
+
97
+ 关键点:
98
+
99
+ - **脚本必须外置**:容器 CSP 的 `script-src` 不含 `unsafe-inline`,内联 `<script>...</script>`、行内事件 `onclick="..."`、`javascript:` URI 均不可用。JS 写进包内 `.js` 用 `<script src>` 引入,事件用 `addEventListener` 绑定。
100
+ - **脚本必须是经典脚本**:只用 `<script src="./app.js">`,**不要 `type="module"`**,JS 里也不要 `import` / `export`。zip 离线加载、无目录服务,module 的相对 `import` 解析不可靠,典型症状是「页面渲染出来但 JS 完全不执行」。要拆多个 JS 文件时按依赖顺序写多个 `<script src>`,靠 `window` 命名空间协作,并避免 top-level `await`。
101
+ - **脚本须兼容目标 WebView**:直接交付的 JS 可使用 ES2017;已有构建链可使用更新语法,但最终须转译为面向 Chrome 61 的 ES2017 产物,见 [js-compatibility.md](./js-compatibility.md)。
102
+ - **样式可内联**:`<style>` 与 `style="..."` 都能用,无需外置。
103
+ - **样式须兼容目标 WebView**:使用 Chrome 61 基线层保证核心布局,并通过能力检测启用现代 CSS 增强;只做功能点级回退,不维护两套完整 CSS,见 [css-compatibility.md](./css-compatibility.md)。
104
+ - **选图预览**:`<img src>` 配 `data:`(`FileReader.readAsDataURL`)或 `blob:`(`URL.createObjectURL`)均可显示;大图优先 `blob:` 并及时 `URL.revokeObjectURL()`。静态资源不得转成长 Base64 塞进源码,见 [performance-budget.md](./performance-budget.md)。
105
+ - 外部 CDN 一律加载不到,所有资源全部打包进小工具。
106
+
107
+ ---
108
+
109
+ ## 4. 路径与引用规则
110
+
111
+ | 规则 | 正确 | 错误 |
112
+ | --- | --- | --- |
113
+ | 资源引用 | `./assets/main.js` | `/assets/main.js`(绝对路径) |
114
+ | 入口 | 根目录 `index.html` | `src/index.html` |
115
+ | base | 不使用 | `<base href="...">` |
116
+ | 外部资源 | 下载后打进 zip 再相对引用 | 任何 `https://...` 在线引用 |
117
+ | 单页 | 一个 `index.html`,视图 JS 切换 | 多 HTML 页面站点 |
118
+
119
+ ---
120
+
121
+ ## 5. index.html 模板
122
+
123
+ ```html
124
+ <!DOCTYPE html>
125
+ <html lang="zh-CN">
126
+ <head>
127
+ <meta charset="UTF-8" />
128
+ <meta name="viewport"
129
+ content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
130
+ <title>应用标题</title>
131
+ <style>
132
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
133
+ html, body { height: 100%; }
134
+ body {
135
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
136
+ "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
137
+ -webkit-font-smoothing: antialiased;
138
+ -webkit-tap-highlight-color: transparent;
139
+ -webkit-user-select: none;
140
+ user-select: none;
141
+ }
142
+ </style>
143
+ <link rel="stylesheet" href="./assets/style.css" />
144
+ </head>
145
+ <body>
146
+ <!-- 内容 -->
147
+ <script src="./assets/main.js"></script>
148
+ </body>
149
+ </html>
150
+ ```
151
+
152
+ | 规则 | 原因 |
153
+ | --- | --- |
154
+ | viewport 含 `width=device-width, initial-scale=1.0, viewport-fit=cover` | 真机 + 模拟器布局与安全区 |
155
+ | 资源全为相对路径 `./assets/...` | 离线 zip 根为 `/` |
156
+ | 脚本外置 `<script src>`,用 `addEventListener` 绑事件 | 容器禁止内联脚本与行内事件 |
157
+ | 不用 `<base href>` | 破坏真机路径 |
158
+ | 不引用任何外部资源(图片 / CSS / JS / 字体) | 外部资源加载不到,须全部打进 zip |
159
+ | 不自建 CSP `<meta>` | 安全策略由容器统一管理 |
160
+
161
+ - `<title>` 仅影响文档标题;导航栏标题由容器 UI 配置。
162
+
163
+ ---
164
+
165
+ ## 6. 打包前自检
166
+
167
+ ### 包结构
168
+
169
+ - [ ] `index.html` 在 zip 根目录(不在任何子目录里)
170
+ - [ ] 解压后顶层直接是文件,**未多套一层目录**(压缩的是目录内容而非目录本身)
171
+ - [ ] 仅含支持的文件类型(见 §2),无开发垃圾文件(`node_modules` / `*.map` / 构建配置等)
172
+
173
+ ### index.html 与资源
174
+
175
+ - [ ] `<!DOCTYPE html>` + `lang="zh-CN"` + `charset=UTF-8`
176
+ - [ ] viewport 含 `width=device-width, initial-scale=1.0, viewport-fit=cover`
177
+ - [ ] 全部资源为相对路径,无 `http(s)://` 外部引用(图片、第三方库、字体等已打进 zip)
178
+ - [ ] 脚本全部外置:无内联 `<script>`、无 `onclick=` 等行内事件、无 `javascript:` / `eval` / `new Function`
179
+ - [ ] 脚本为经典脚本:无 `type="module"`,JS 内无 `import` / `export`
180
+ - [ ] JS 兼容 [js-compatibility.md](./js-compatibility.md):直接交付代码不超出 ES2017,或已有构建链生成面向 Chrome 61 的 ES2017 产物
181
+ - [ ] CSS 兼容 [css-compatibility.md](./css-compatibility.md):Chrome 61 基线可用,现代 CSS 通过合适的能力检测启用,并已检查最终构建产物
182
+ - [ ] 图片可用包内文件 / `data:` / `blob:`;音视频、字体仅用包内文件
183
+ - [ ] 无 `<base href>`、无 `<iframe>` / `<object>`、无自建 CSP `<meta>`
184
+
185
+ ### 端能力(见 [device-capabilities.md](./device-capabilities.md))
186
+
187
+ - [ ] 未使用不可用能力(网络请求、定位、剪贴板、传感器、Worker、WebRTC 等)
188
+ - [ ] 相机 / 麦克风 / 选图用法符合「用户手势触发 + 授权」
189
+ - [ ] 若使用 JSBridge:仅调用 [jsbridge-api.md](./jsbridge-api.md) 列出的 API,参数符合 schema
190
+
191
+ ### 正确性(静态自查)
192
+
193
+ - [ ] 被禁能力**无调用 / 残留**(按 device-capabilities.md 扫描清单逐项 grep)
194
+ - [ ] JS 无语法错误;关键逻辑通读无明显运行时报错(如调用未定义函数、引用 `null` DOM)
195
+ - [ ] 多文件 JS 依赖关系 / 加载顺序正确
196
+ - [ ] 页面引用的每个资源(脚本 / 样式 / 图片 / 音频)都已打进 zip,且路径正确
197
+ - [ ] 改写场景:仅替换被禁能力,未顺手改动其余业务逻辑与 UI
198
+ - [ ] 核心交互在代码层面自洽:事件有绑定、依赖的 DOM 存在、回调闭环完整
199
+
200
+ ### 体积
201
+
202
+ - [ ] 总包(zip)不超过 10MB(上限);为获得更好的加载体验,建议控制在 2MB 以内
203
+ - [ ] 单条 Base64 解码后不超过 1MiB;超过 100KiB 时优先改为独立包内文件
204
+ - [ ] 单个 HTML / CSS / JS / JSON 超过 2MiB、文本合计超过 5MiB 时已人工检查,确认没有把大型数据库 / 生成内容塞进代码包
205
+ - [ ] 已按 [performance-budget.md](./performance-budget.md) 的环境分支审计产物目录与最终 zip;无运行时时已完成人工门禁并明确记录
206
+ - [ ] 明显超出建议值时已按 [performance-budget.md](./performance-budget.md) 优化,而不是依赖高压缩率掩盖解压后的大源码 / 大数据