10 KiB
10 KiB
小工具 ZIP 静态包构建规范
小工具是基于离线 H5 的 app 形式,纯本地、不联网,所有资源须打包在 zip 内。窗口样式、导航栏、下拉刷新等外壳行为由容器统一控制,无需在包内声明。
目录
- §1 目录结构与打包
- §2 支持的文件类型
- §3 资源加载规则(容器 CSP)
- §4 路径与引用规则
- §5 index.html 模板
- §6 打包前自检
1. 目录结构与打包
唯一硬性要求:index.html 位于 zip 根目录作为入口。 其余文件 / 文件夹随你组织 —— 可平铺,也可按需分目录(assets/、images/、audios/ 等),用相对路径引用即可。
平铺示例:
tool.zip
├── index.html # 必需 — 入口,必须在根目录
├── main.js
├── style.css
└── logo.png
分目录示例:
tool.zip
├── index.html # 必需 — 入口,必须在根目录
└── assets/
├── style.css
├── main.js
└── images/
└── ...
| 路径 | 要求 | 说明 |
|---|---|---|
index.html |
必须在 zip 根目录 | 唯一入口;不可改名、不可放进子目录 |
| 其余文件 / 文件夹 | 自由 | JS / CSS / 图片 / 音频等,平铺或分目录皆可,相对路径引用 |
禁止出现在 zip 内
node_modules、.git、.DS_Store*.map、构建配置文件(vite.config.*、webpack.config.*等)- 把整个项目多包一层目录,导致
index.html不在根(错误:app/index.html;正确:根目录就有index.html)
打包方式(关键)
压缩的是「与 index.html 同级的那批文件」本身,不是它们所在的文件夹。 必须先进入该目录再压缩当前目录内容,否则解压后会多套一层目录、index.html 不在根,容器无法加载。
# ✅ 正确:进入目录,压缩目录“内容”(index.html 直接在 zip 根)
cd dist && zip -r ../tool.zip . -x '*.DS_Store'
# ❌ 错误:压缩目录“本身”,解压后多一层 dist/,index.html 变成 dist/index.html
zip -r tool.zip dist
解压后顶层应直接看到 index.html,而不是先看到一个文件夹再点进去。
2. 支持的文件类型
zip 内仅允许以下类型:
| 类型 | 用途 |
|---|---|
.html |
入口,有且只有一个 index.html |
.css |
样式文件 |
.js |
脚本文件 |
.png / .jpg / .jpeg / .gif / .webp / .svg |
图片资源 |
.woff / .woff2 |
字体文件 |
.json |
小型静态数据 / 配置;不得作为大型内置数据库,体积门禁见 performance-budget.md |
3. 资源加载规则(容器 CSP)
容器对页面如何加载各类资源有强制约束。除包内文件外,按类型另允许 data: / blob: 等内存来源。
| 资源类型 | 允许 | 禁止 |
|---|---|---|
脚本 <script> |
引用包内脚本 <script src="./app.js">(同源外链) |
内联 <script>...</script>;行内事件 onclick="...";javascript: URI;eval() / new Function();WebAssembly;外部域名 / data: / blob: 脚本 |
样式 <style> / <link> |
内联 <style>、行内 style="..."、包内样式表 |
外部域名样式表 |
图片 <img> / CSS 背景图 |
包内图片 <img src="./a.png">;data: URI(base64 内嵌);blob:(createObjectURL 内存对象,如选图预览) |
外部域名图片 |
字体 @font-face |
包内字体文件 | 外部域名字体 |
音视频 <video> / <audio> |
包内媒体文件 | 外部域名媒体、data: / blob: 媒体 |
| iframe / object | — | 全部禁止 |
关键点:
- 脚本必须外置:容器 CSP 的
script-src不含unsafe-inline,内联<script>...</script>、行内事件onclick="..."、javascript:URI 均不可用。JS 写进包内.js用<script src>引入,事件用addEventListener绑定。 - 脚本必须是经典脚本:只用
<script src="./app.js">,不要type="module",JS 里也不要import/export。zip 离线加载、无目录服务,module 的相对import解析不可靠,典型症状是「页面渲染出来但 JS 完全不执行」。要拆多个 JS 文件时按依赖顺序写多个<script src>,靠window命名空间协作,并避免 top-levelawait。 - 脚本须兼容目标 WebView:直接交付的 JS 可使用 ES2017;已有构建链可使用更新语法,但最终须转译为面向 Chrome 61 的 ES2017 产物,见 js-compatibility.md。
- 样式可内联:
<style>与style="..."都能用,无需外置。 - 样式须兼容目标 WebView:使用 Chrome 61 基线层保证核心布局,并通过能力检测启用现代 CSS 增强;只做功能点级回退,不维护两套完整 CSS,见 css-compatibility.md。
- 选图预览:
<img src>配data:(FileReader.readAsDataURL)或blob:(URL.createObjectURL)均可显示;大图优先blob:并及时URL.revokeObjectURL()。静态资源不得转成长 Base64 塞进源码,见 performance-budget.md。 - 外部 CDN 一律加载不到,所有资源全部打包进小工具。
4. 路径与引用规则
| 规则 | 正确 | 错误 |
|---|---|---|
| 资源引用 | ./assets/main.js |
/assets/main.js(绝对路径) |
| 入口 | 根目录 index.html |
src/index.html |
| base | 不使用 | <base href="..."> |
| 外部资源 | 下载后打进 zip 再相对引用 | 任何 https://... 在线引用 |
| 单页 | 一个 index.html,视图 JS 切换 |
多 HTML 页面站点 |
5. index.html 模板
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>应用标题</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
-webkit-font-smoothing: antialiased;
-webkit-tap-highlight-color: transparent;
-webkit-user-select: none;
user-select: none;
}
</style>
<link rel="stylesheet" href="./assets/style.css" />
</head>
<body>
<!-- 内容 -->
<script src="./assets/main.js"></script>
</body>
</html>
| 规则 | 原因 |
|---|---|
viewport 含 width=device-width, initial-scale=1.0, viewport-fit=cover |
真机 + 模拟器布局与安全区 |
资源全为相对路径 ./assets/... |
离线 zip 根为 / |
脚本外置 <script src>,用 addEventListener 绑事件 |
容器禁止内联脚本与行内事件 |
不用 <base href> |
破坏真机路径 |
| 不引用任何外部资源(图片 / CSS / JS / 字体) | 外部资源加载不到,须全部打进 zip |
不自建 CSP <meta> |
安全策略由容器统一管理 |
<title>仅影响文档标题;导航栏标题由容器 UI 配置。
6. 打包前自检
包结构
index.html在 zip 根目录(不在任何子目录里)- 解压后顶层直接是文件,未多套一层目录(压缩的是目录内容而非目录本身)
- 仅含支持的文件类型(见 §2),无开发垃圾文件(
node_modules/*.map/ 构建配置等)
index.html 与资源
<!DOCTYPE html>+lang="zh-CN"+charset=UTF-8- viewport 含
width=device-width, initial-scale=1.0, viewport-fit=cover - 全部资源为相对路径,无
http(s)://外部引用(图片、第三方库、字体等已打进 zip) - 脚本全部外置:无内联
<script>、无onclick=等行内事件、无javascript:/eval/new Function - 脚本为经典脚本:无
type="module",JS 内无import/export - JS 兼容 js-compatibility.md:直接交付代码不超出 ES2017,或已有构建链生成面向 Chrome 61 的 ES2017 产物
- CSS 兼容 css-compatibility.md:Chrome 61 基线可用,现代 CSS 通过合适的能力检测启用,并已检查最终构建产物
- 图片可用包内文件 /
data:/blob:;音视频、字体仅用包内文件 - 无
<base href>、无<iframe>/<object>、无自建 CSP<meta>
端能力(见 device-capabilities.md)
- 未使用不可用能力(网络请求、定位、剪贴板、传感器、Worker、WebRTC 等)
- 相机 / 麦克风 / 选图用法符合「用户手势触发 + 授权」
- 若使用 JSBridge:仅调用 jsbridge-api.md 列出的 API,参数符合 schema
正确性(静态自查)
- 被禁能力无调用 / 残留(按 device-capabilities.md 扫描清单逐项 grep)
- JS 无语法错误;关键逻辑通读无明显运行时报错(如调用未定义函数、引用
nullDOM) - 多文件 JS 依赖关系 / 加载顺序正确
- 页面引用的每个资源(脚本 / 样式 / 图片 / 音频)都已打进 zip,且路径正确
- 改写场景:仅替换被禁能力,未顺手改动其余业务逻辑与 UI
- 核心交互在代码层面自洽:事件有绑定、依赖的 DOM 存在、回调闭环完整
体积
- 总包(zip)不超过 10MB(上限);为获得更好的加载体验,建议控制在 2MB 以内
- 单条 Base64 解码后不超过 1MiB;超过 100KiB 时优先改为独立包内文件
- 单个 HTML / CSS / JS / JSON 超过 2MiB、文本合计超过 5MiB 时已人工检查,确认没有把大型数据库 / 生成内容塞进代码包
- 已按 performance-budget.md 的环境分支审计产物目录与最终 zip;无运行时时已完成人工门禁并明确记录
- 明显超出建议值时已按 performance-budget.md 优化,而不是依赖高压缩率掩盖解压后的大源码 / 大数据