转换 Chrome Extension 为 Safari 版本
本文最后更新于:2025年4月1日 凌晨
背景
这两天吾辈开始尝试将一个 Chrome 扩展发布到 Safari,这是一件一直想做的事情,但由于 Xcode 极其糟糕的开发体验,一直没有提起兴趣完成。这两天又重新燃起了一丝想法,来来回回,真正想做的事情总是会完成。所以于此记录一篇,如何做到以及踩过的坑。下面转换的扩展 Redirector 实际上已经发布到 Chrome/Firefox/Edge,将作为吾辈第一个发布到 App Store 的 Safari 扩展。
转换扩展
首先,在 WXT 的官方文档中提到了如何发布 Safari 版本 [1],提到了一个命令行工具 xcrun [2],它允许将一个 Chrome 扩展转换为 Safari 扩展。
WXT 提供的命令
1 |
|
由于吾辈使用了 Manifest V3,第二条命令必须修改为
1 |
|
不幸的是,立刻就可以发现一个错误,默认的 App Bundle Identifier 不正确,需要手动指定 --bundle-identifier
,由于需要运行多次这个命令,所以还应该指定 –force 允许覆盖现有 Output。
1 |
|
现在可以在 Redirector 目录下看到一个 Xcode 项目,并且会自动使用 Xcode 打开该项目。
构建并测试
接下来切换到 Xcode 开始 build 并运行这个扩展。
然而,打开 Safari 之后默认不会看到刚刚 build 的扩展,因为 Safari 默认不允许运行未签名的扩展 [3]。
需要设置 Safari
- 选中 Safari > Settings > Advanced > Show features for web developers
- 选中 Safari > Settings > Developer > Allow unsigned extensions
此时,如果你像吾辈一样之前安装然后卸载过这个扩展的话,需要手动使用 --project-location
来指定另一个路径重新转换,然后在 Xcode 中构建,这是一个已知的 issue [4]。
好的,完全退出 Xcode/Safari,然后重新运行新的转换命令,指定一个其他目录(这里是用了日期后缀)作为转换 Xcode 项目目录。
1 |
|
在 Safari 扩展故障排除中可以有这样一条命令,可以检查已经识别安装的扩展 [5]。当然,实际上即使识别出来了,也有可能在 Safari 中看不到,必要不充分条件,转换之前最好检查 /Users/username/Library/Developer/Xcode/DerivedData 目录并清理构建的临时扩展。
1 |
|
无论如何,如果一切正常,就可以在 Extensions 中查看并启用临时扩展了。
启用它,然后就可以在 Safari Toolbar 中看到扩展图标并进行测试了。
如果你发现 Mac 生态下的开发很痛苦,经常没有任何错误但也没有正常工作,那可能是正常的。
更换不兼容的 API
好吧,如果幸运的话,扩展就可以正常在 Safari 中工作了对吧,Safari 支持 Chrome 扩展对吧?oh sweet summer child,当然不可能这么简单,Safari 有一些不兼容的 API,它不会出错,但也确实不工作。你可以在官方兼容性文档 [6] 中检查一些不兼容的 API,并采用其他方法绕过。
例如 webRequest 相关的 API,Manifest v3 中 webRequest blocking API 被删除是已知的,但根据这个 App Developer Forums 上的 issue 可知,对于 Safari 而言,Manifest v3 中 webRequest API 整个功能都不生效,仅在 Manifest v2 persistent background pages 中生效,有趣的是,iOS 不支持 persistent background pages。所以之前使用的 webRequest API 需要转换,幸运的是,对于 Redirector 而言,只需要转换为一个。
browser.webRequest.onBeforeRequest.addListener
=> browser.webNavigation.onCommitted.addListener
可以参考官方文档 [7] 调试 background script,它不方便,但它是唯一的方法。
现在,扩展可以正常工作了。
发布到 App Store
现在,让我们开始构建并发布到 App Store 或在其他地方发布扩展,无论哪种方式,都需要正确配置签名证书。
- 首先修改 Project > Build Settings > Checkout ‘All’ & ‘Combined’ > Search ‘team’ > Development Team
- 在 Mac/iOS Targets > General > Identity > App Category 选择产品类型
在发布之前,还需要手动指定版本,因为它并不跟随 manifest 中指定的版本,而是单独的,建议在转换之后就指定。
该配置也在 <project name>/<project name>.xcodeproj/project.pbxproj
文件中,可以搜索并替换 MARKETING_VERSION = 1.0;
然后从 Xcode 工具栏选择 Product > Archive,就可以构建一个 bundle 并等待分发了。
首先点击 Validate App 确保 bundle 没有什么配置错误。这里遇到了一个错误,提示吾辈的扩展名(不是扩展 id)已存在,需要使用一个其他的名字。
好的,让我们修改 manifest 中的名字并重复以上转换和构建流程,重新验证,没有发现错误。
接下来就可以分发 App 了,点击 Distribute App,然后选择 App Store Connect 在 App Store 上架或 Direct Distribute 公证插件。
吾辈发现这个视频很有帮助 https://youtu.be/s0HtHvgf1EQ?si=rbzc88E1Y_6nZY6k
完善发布信息
最后,还需要前往 https://appstoreconnect.apple.com/apps 完善发布信息,包括截图、隐私政策和定价等等。吾辈没有意识到 Apple 使用网页来管理 App 发布,这与 Apple 万物皆可 App(App Developer/TestFlight) 的风格似乎不太相像,因此白白苦等了 2 周。
吾辈还发现 App 描述中禁止使用 emoji 字符,否则会提示
The message contains invalid characters.
总结
Mac/iOS 开发是非常封闭的平台,开发工具与体验与 Web 大有不同,但考虑到 Safari 是 Mac 上默认的浏览器,而在 iOS 上更是无法修改的事实上的标准,可能还是值得投入一些精力去支持它,尽管它甚至比 Firefox 更加糟糕。
- https://wxt.dev/guide/essentials/publishing.html#safari ↩
- https://developer.apple.com/documentation/safariservices/converting-a-web-extension-for-safari ↩
- https://developer.apple.com/documentation/safariservices/running-your-safari-web-extension#Configure-Safari-in-macOS-to-run-unsigned-extensions ↩
- https://developer.apple.com/forums/thread/765761 ↩
- https://developer.apple.com/documentation/safariservices/troubleshooting-your-safari-web-extension ↩
- https://developer.apple.com/documentation/safariservices/assessing-your-safari-web-extension-s-browser-compatibility ↩
- https://developer.apple.com/documentation/safariservices/troubleshooting-your-safari-web-extension ↩