Files
2026-09-17 21:54:33 +08:00

10 KiB
Raw Permalink Blame History

小工具 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-level await。
  • 脚本须兼容目标 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 无语法错误;关键逻辑通读无明显运行时报错(如调用未定义函数、引用 null DOM)
  • 多文件 JS 依赖关系 / 加载顺序正确
  • 页面引用的每个资源(脚本 / 样式 / 图片 / 音频)都已打进 zip,且路径正确
  • 改写场景:仅替换被禁能力,未顺手改动其余业务逻辑与 UI
  • 核心交互在代码层面自洽:事件有绑定、依赖的 DOM 存在、回调闭环完整

体积

  • 总包(zip)不超过 10MB(上限);为获得更好的加载体验,建议控制在 2MB 以内
  • 单条 Base64 解码后不超过 1MiB;超过 100KiB 时优先改为独立包内文件
  • 单个 HTML / CSS / JS / JSON 超过 2MiB、文本合计超过 5MiB 时已人工检查,确认没有把大型数据库 / 生成内容塞进代码包
  • 已按 performance-budget.md 的环境分支审计产物目录与最终 zip;无运行时时已完成人工门禁并明确记录
  • 明显超出建议值时已按 performance-budget.md 优化,而不是依赖高压缩率掩盖解压后的大源码 / 大数据