如何从类型声明文件导入并重命名Firefox WebExt的browser类型?
我尝试用以下代码导入Firefox扩展的类型并重命名:
import { browser as webextBrowser } from '@types/firefox-webext-browser'
但触发了两个TypeScript错误:
File '.../node_modules/@types/firefox-webext-browser/index.d.ts' is not a module. ts(2306)
Cannot import type declaration files. Consider importing 'firefox-webext-browser' instead of '@types/firefox-webext-browser'. ts(6137)
背景
我在用WebdriverIO测试Firefox浏览器扩展,扩展需要访问书签等功能,但WebdriverIO没有提供修改书签的API。于是我利用扩展的书签权限,通过WebdriverIO的browser.execute在扩展上下文执行代码来设置测试用书签(知道这种方法比较取巧,欢迎提供更好的方案)。
当前代码如下:
browser.execute(async (url) => { // @ts-expect-error: does not know about web-extension types await browser.bookmarks.create({ url: url }) }, url)
这段代码运行正常,但编译阶段有问题:外层的browser是WebdriverIO类型,内层的browser是WebExt类型。我只能用@ts-expect-error抑制错误,但typescript-eslint会告警,而且误用WebExt类型也得不到编译提示。
我希望能把WebExt类型以别名(比如webextBrowser)导入,避免类型冲突,同时给browser.execute传入的函数提供类型提示。
补充说明
@types/firefox-webext-browser只有类型声明文件,没有对应的firefox-webext-browser模块,因为实现是浏览器提供的。- 在
tsconfig.json的types: [ ... ]里加@types/firefox-webext-browser没用,反而会导致不同browser类型冲突。把WebdriverIO的browser别名导入虽然可行,但WebdriverIO的browser在测试代码里大量使用,WebExt的browser只在browser.execute里用,这么做太麻烦。 - 试过用三斜线指令在单独文件里导出:
VS Code里用/// <reference types="firefox-webext-browser" /> export const webextBrowser = browserwebextBrowser时能识别类型,但typescript-eslint会报no-unsafe-*错误,WebdriverIO配置文件wdio.conf.ts里的browser会错误指向WebExt类型,导致TypeScript错误,而且运行时会出现“ReferenceError: webextBrowser is not defined”的报错。
方法1:类型断言+自定义类型模块
创建一个类型声明文件(比如webext-types.d.ts),封装WebExt的类型别名:
declare module 'webext-browser-types' { import type { browser } from '@types/firefox-webext-browser'; export { browser as webextBrowser }; }
在需要的地方导入类型并做断言:
import type { webextBrowser } from './webext-browser-types'; browser.execute(async (url) => { // 断言当前上下文的browser为WebExt类型 const webextBrowser = browser as typeof import('./webext-browser-types').webextBrowser; await webextBrowser.bookmarks.create({ url }); }, url);
这种方式既隔离了类型,又不会引入运行时依赖。
方法2:直接内联类型断言(最简方案)
不需要额外文件,直接在回调里给browser做类型断言:
browser.execute(async (url) => { const webextBrowser = browser as import('@types/firefox-webext-browser').browser; await webextBrowser.bookmarks.create({ url }); }, url);
适合只在少数场景使用的情况,简洁高效。
方法3:局部类型隔离(不推荐)
如果不想重复写断言,可以在单个测试文件顶部用三斜线指令,仅让当前文件识别WebExt类型,同时保存WebdriverIO的类型:
/// <reference types="@types/firefox-webext-browser" /> // 保存WebdriverIO的browser类型 type WdioBrowser = typeof browser; // execute回调内直接用WebExt的browser类型 browser.execute(async (url: string) => { await browser.bookmarks.create({ url }); }, url); // 其他场景用断言转回WebdriverIO类型 const wdioBrowser = browser as WdioBrowser; wdioBrowser.url('https://example.com');
但这种方式容易混淆类型,不推荐在大量代码中使用。
替代方案:使用官方WebExtension测试API
Firefox提供了专门的WebExtension测试API,可以直接在测试中调用扩展的书签等功能,不需要通过browser.execute中转。这种方式类型支持更完善,流程也更规范,建议优先尝试。
内容的提问来源于stack exchange,提问作者Marcono1234

