manifest.json 要位于压缩包根目录。当上传内容经过压缩或打包时,Firefox Add-ons 还会要求一份源码 zip,而生产构建正是这种情况。Safari 是例外:扩展随 Xcode 构建的 macOS 或 iOS 应用一起发布。本页解释生成压缩包的三种方式,列出 extension build --zip 实际写出的文件名,并说明每个商店接受什么。本页还会引用 CLI 打包时打印的控制台输出。
生成压缩包的三种方式
手动压缩dist/<browser>。 运行 extension build --browser=chrome,打开 dist/chrome,选中其中的内容并压缩。陷阱在于文件夹本身:如果压缩的是 chrome 文件夹,manifest.json 就会下沉一层,商店会拒绝。
extension build --zip。 构建写出 dist/<browser>,然后在该文件夹旁边打包,绝不放在它里面。source map 不会进入压缩包。加上 --zip-source 即可得到 AMO 要求的源码压缩包。
CI 任务。 同一条 extension build --zip 命令在 runner 上运行,任务把 dist/*.zip 作为构建产物上传,或交给商店 API。商店凭据留在 CI secret 里。参见 CI 模板。
手动路线用一次就好,用来了解商店期望什么。之后的每次发布都用
--zip,当不止一个人负责发布时,把同一条命令搬进 CI。
命令片段
这些标志属于build:
build 都不加标志就打包,同样的选项也可以放进 extension.config.js:
extension.config.js
extension build --browser=chrome 会写出 dist/store-chrome.zip 和 dist/store-source.zip。
构建写出了什么
每个压缩包都落在dist/ 里,与 dist/<browser> 文件夹并列。不传 --zip-filename 时,文件名是 manifest 的 name 转小写并去掉 a-z0-9 和空格之外的所有字符,再接 manifest 的 version,再接浏览器。一个名为 zip-probe、版本 1.0.0 的 manifest 会打包成 zipprobe-1.0.0-chrome.zip:连字符没了,所以请从构建输出里读路径,而不是自己拼。以下运行使用的是 Extension.js 4.1.31:
从表里可以得出两条规则。默认浏览器是
chromium,所以目标是 Chrome Web Store 时要传 --browser=chrome。另外,单独的 --zip-filename 永远不会写源码压缩包:写它的是 --zip-source。此时显式名称同时管两个文件,发行压缩包后面追加浏览器,另一个追加 -source。
发行压缩包装的是 dist/<browser> 的内容去掉 .map 文件,manifest.json 位于根目录。源码压缩包装的是项目文件夹去掉 node_modules、.git、dist、extensions/ 伴随扩展文件夹、除 *.example 之外的所有 .env* 文件,以及你的 .gitignore 排除的内容。符号链接会被跳过并给出警告,因为压缩包存储的是文件。
在 --output json 下,每个压缩包都会列在 zip_artifacts 里,带有 kind(dist 或 source)、path 和以字节计的 size,所以 CI 步骤不必解析控制台就能拿到文件。
各浏览器差异
Firefox 自行分发是商店唯一会把文件交还给你的情况。AMO 会对上传内容签名,签名副本可从你的提交页面下载时会通过邮件通知你。Chrome 和 Edge 永远不会返回
.crx,Extension.js 也不会写出 .crx 或 .xpi 文件:商店接受的是 zip。
Opera 的商店拒绝压缩过的代码,所以 extension build --browser=opera 默认关闭 --minify。
你会看到的控制台输出
把你看到的那一行复制到搜索里。每一行对应一个原因。Packaged dist/zipprobe-1.0.0-chrome.zip (34.2 KB).
发行压缩包已写出。该路径就是要上传的文件,由于上面的清洗规则,它的名字可能与你的 manifest name 不一致。
Packaged dist/zipprobe-1.0.0-source.zip (37.2 KB).
源码压缩包已写出。同一次运行里它打印在发行压缩包之前。构建多个浏览器时每个浏览器打印一次,因为源码对每个浏览器都是一样的。
Skipped the addons.mozilla.org lint: addons-linter is not installed. Install it with: npm install -D addons-linter or pass --no-addon-lint to silence this.
Firefox 构建完成但没有做 AMO 检查。zip 是完整的。安装 addons-linter 可以在上传前看到 AMO 会标记什么,或者传 --no-addon-lint 让这一行不再出现。
Edge Add-ons refuses a package whose manifest carries key, so the edge production build dropped it.
Edge 构建移除了顶层的 key 字段。Partner Center 会分配扩展 ID,所以这个字段在该包里没有用处。如果它只用于 Chrome Web Store 的构建,写成 chrome:key。
default_locale is set, but the _locales folder is missing.
manifest 检查在打包前停止了构建。商店会拒绝缺少默认语言环境的包,所以请恢复 _locales/<default>/messages.json 再重新构建。
The source zip skipped a symlink, because an archive stores files.
项目里的一个符号链接被排除在源码压缩包之外。如果压缩包需要它,把链接指向的内容复制进项目。
The source zip was requested and not created.
构建完成了,但源码压缩包没有生成。这一行会给出路径和原因。发行压缩包不受影响,因为两个压缩包各自独立失败。
Extension.js 的做法
用命令打包,而不是用文件管理器:- 为每个商店运行
extension build --browser=<browser> --zip,商店是 Firefox Add-ons 时再加--zip-source。 - 在 CI 里传
--zip-filename=release,让产物名称稳定,并让 CLI 追加浏览器。 - 从
Packaged那一行或--output json下的zip_artifacts读压缩包路径,永远不要从 manifest 的name推算。 - 让
dist/远离 git。构建每次运行都会清理dist/<browser>。之前运行留下的压缩包绝不会进入下一个源码压缩包。 - 在第一次上传前写好 STORE.md,让审核者备注和 AMO 构建说明随代码一起走。
pack 命令,extension publish 是 extension.dev 上的分享链接,不是商店提交。参见 Publish 命令。

