这两天看到了一个项目:WXT,简而言之,它就好比是扩展程序开发里的 Next.js。
我被它的几个特点吸引,于是将一个已经开发完成的扩展程序改为了使用 WXT,以下是体验报告。
先说结论
是否要使用取决于一点:你是否能忍受安装包体积变大。
不同项目可能体积变大的程度不一样,我自己的项目,体积变大了 30%。
在使用 WXT 之前,我使用 Webpack 打包我的源码,其中 background script 单独打包(target: 'webworker'),其余所有代码统一打包(target: 'web')。最后,打包出来的 zip 上传到 webstore 后,下载下来的 .crx 文件为 430KB。
使用 WXT 之后,.crx 文件为 607KB。变大的原因会在后面解释。
优点
我比较喜欢的几个点:
- 对内容脚本失效的情况(即
Error: Extension context invalidated.)做了检测,见文档- 但使用这个功能会要求内容脚本改为异步的,后面有说
- 基于文件系统的 entrypoint 定义,见文档
- 开发环境下会自动加载扩展程序并打开一个全新的浏览器 profile 用来测试,见文档
- 这可以把我自己使用的 Chrome 和用来测试扩展程序的 Chrome 分隔开来
- 开发环境下,修改代码后会自动刷新扩展 / 网页 / 侧边栏等
- 但是体验不太稳定,经常出现(大概是 2 天内出现了 3 次的频率)虽然自动刷新了,但实际上代码还是旧的,需要重启开发环境的情况
- 如果修改了环境变量,不会自动刷新,甚至重启开发服务也不会生效,需要修改用到了环境变量的文件代码(例如加一行
console.log('x'))才会应用到改动后的环境变量
- 开箱即用的多浏览器打包,见文档
- 但是我还没真的用过,所以体验如何未知
- 通过 CLI 发布扩展程序到各大 store,见文档
- 目前仅体验了自动发布到 Chrome Webstore,除了初次配置比较麻烦外(因为申请 Google 的 API Key 特别麻烦),之后体验都很好
缺点
内容脚本的 entrypoint 需要写成异步的
WXT 需要在 entrypoint 里使用 defineContentScript 函数来定义内容脚本,例如:
// entrypoints/example.content.ts
// 其余 import 可以像普通的场景一样使用
import other from './other'
export default defineContentScript({
matches: ['<all_urls>'],
main(ctx) {
// 但这个文件内的代码必须写在 main 里面
if(other.top) {
console.log('In top.')
}
},
});
这是我很不习惯的一点,当然也有 workaround:
// entrypoints/example.content.ts
// 所以逻辑都写在 bootstrap 里
import './bootstrap'
// entrypoint 仅用来定义内容脚本
export default defineContentScript({
matches: ['<all_urls>'],
});
WXT 应该是会剥离掉所有 import,然后在 Node.js 环境读取内容脚本的配置,因为经过测试,如果我在别的 js 文件顶层使用 self.top === self 这样的代码不会报错,但如果在 example.content.ts 里使用就会报错,需要放到 main 函数里。
另外,如果要用到优点里的“内容脚本失效检测”,那这也要求所有代码都改为在 main 里启动,这相当于强制内容脚本要用异步的方式来写,也就是所有代码都要包装进一个函数里。
安装包体积变大
上面的缺点都还算有 workaround,但这一个目前没有解决方案。
出现这个问题的原因是,WXT 的打包策略似乎是:
- 每个 content script 单独打包
- 其余所有部分:background、options、sidepanel 等统一打包
这意味着:内容脚本与内容脚本以及其它部分之间共同依赖的代码(例如 lodash、react)没有抽离出来公共 chunk。
我的情况是,我在两个内容脚本和 sidepanel 里都用了 react,最后我查看打包情况时,发现 react 没有被抽离出来,而是分别打包进了这 3 个 entroypoint。
这真的让我很难取舍
我向 WXT 提交了一个 issue,希望能有 workaround。