Skip to content

升级 WXT

概述

要将 WXT 升级到最新的主版本:

  1. 安装时跳过脚本,这样 wxt prepare 就不会运行——在主版本变更后它可能会抛出错误(我们稍后再运行)。

    sh
    pnpm i wxt@latest --ignore-scripts
  2. 按照下面的升级步骤修复所有破坏性变更。

  3. 运行 wxt prepare。它应该能成功执行,之后类型错误将会消失。

    sh
    pnpm wxt prepare
  4. 手动测试确保开发模式和生产构建都能正常工作。

对于次版本或补丁版本更新,没有特殊步骤。只需使用包管理器更新即可:

sh
pnpm i wxt@latest

以下列出了升级到新版本 WXT 时需要处理的所有破坏性变更。

目前,WXT 处于预发布阶段。这意味着第二位数字的变更(v0.X)被视为主版本变更,包含破坏性变更。一旦 v1 发布,只有主版本号的变更才会包含破坏性变更。

v0.19.0 → v0.20.0

v0.20 是一个重大版本!有许多破坏性变更,因为这个版本旨在作为 v1.0 的候选发布版本。如果一切顺利,v1.0 将不会有额外的破坏性变更。

TIP

在更新代码之前,请先通读所有变更。

webextension-polyfill 已移除

WXT 的 browser 不再使用 webextension-polyfill

为什么?

参见 https://github.com/wxt-dev/wxt/issues/784

要升级,你有两个选择:

  1. 停止使用 polyfill

    • 如果你已经在使用 extensionApi: "chrome",那么你没有在使用 polyfill,无需任何更改!

    • 否则只有一个变更:browser.runtime.onMessage 不再支持使用 Promise 来返回响应:

      ts
      browser.runtime.onMessage.addListener(async () => { 
        const res = await someAsyncWork(); 
        return res; 
      browser.runtime.onMessage.addListener(async (_message, _sender, sendResponse) => { 
        someAsyncWork().then((res) => { 
          sendResponse(res); 
        }); 
        return true; 
      });
  2. 继续使用 polyfill - 如果你想继续使用 polyfill,完全可以!在本次升级中可以少操心一件事。

    • 安装 webextension-polyfill 和 WXT 的新 polyfill 模块

      sh
      pnpm i webextension-polyfill @wxt-dev/webextension-polyfill
    • 将 WXT 模块添加到你的配置中:

      ts
      export default defineConfig({
        modules: ['@wxt-dev/webextension-polyfill'],
      });

新的 browser 对象(及类型)由 WXT 的新包 @wxt-dev/browser 提供支持。这个包延续了 WXT 为整个社区提供实用包的使命。就像 @wxt-dev/storage@wxt-dev/i18n@wxt-dev/analytics 一样,它被设计为可以在任何 Web 扩展项目中轻松使用,而不仅仅是使用 WXT 的项目,并在所有浏览器和 Manifest 版本之间提供一致的 API。

extensionApi 配置已移除

extensionApi 配置已被移除。之前,此配置提供了一种在 v0.20.0 之前选择使用新 browser 对象的方式。

如果存在的话,请从你的 wxt.config.ts 文件中移除它:

ts
export default defineConfig({
  extensionApi: 'chrome', 
});

Extension API 类型变更

随着 v0.20 引入的新 browser,访问类型的方式发生了变化。WXT 现在基于 @types/chrome 而非 @types/webextension-polyfill 提供类型。

这些类型对 MV3 API 更加新,包含更少的 bug,组织更好,并且没有任何自动生成的名称。

要访问类型,请使用来自 wxt/browser 的新 Browser 命名空间:

ts
import type { Runtime } from 'wxt/browser'; 
import type { Browser } from 'wxt/browser'; 

function getMessageSenderUrl(sender: Runtime.MessageSender): string { 
function getMessageSenderUrl(sender: Browser.runtime.MessageSender): string { 
  // ...
}

如果你使用自动导入,Browser 将无需手动导入即可使用。

并非所有类型名称都与 @types/webextension-polyfill 提供的相同。你需要通过查看所使用的 browser.* API 的类型来找到新的类型名称。

public/modules/ 目录已移动

public/modules/ 目录的默认位置已更改,以更好地与其他框架(Nuxt、Next、Astro 等)设定的标准保持一致。现在,每个路径都相对于项目的根目录,而非 src 目录。

  • 如果你遵循默认的文件夹结构,则无需做任何更改。
  • 如果你设置了自定义的 srcDir,你有两个选择:
    1. 将你的 public/modules/ 目录移到项目根目录:

      html
       📂 {rootDir}/
          📁 modules/ 
          📁 public/ 
          📂 src/
             📁 components/
             📁 entrypoints/
             📁 modules/ 
             📁 public/ 
             📁 utils/
             📄 app.config.ts
          📄 wxt.config.ts
    2. 保持文件夹在原位并更新项目配置:

      ts
      export default defineConfig({
        srcDir: 'src',
        publicDir: 'src/public', 
        modulesDir: 'src/modules', 
      });

导入路径变更和 #imports

wxt/sandboxwxt/clientwxt/storage 导出的 API 已移至 wxt/utils/* 路径下的各个独立导出。

为什么?

随着 WXT 的增长和更多工具的添加,任何带有副作用的辅助工具都不会从最终包中被 tree-shaking 掉。

这可能会导致问题,因为并非每种类型的入口点都可以使用这些副作用所用的所有 API。某些 API 只能在后台使用,沙盒页面不能使用任何扩展 API,等等。这导致 JS 在顶层作用域中抛出错误,阻止你的代码运行。

将每个工具拆分为独立的模块可以解决这个问题,确保你只将 API 和副作用导入到它们可以运行的入口点中。

请参阅更新后的 API 参考 查看新导入路径的列表。

不过,你不需要记忆或学习新的导入路径!v0.20 引入了一个新的虚拟模块 #imports,它将所有这些从开发者那里抽象出来。有关此模块如何工作的更多详细信息,请参阅博客文章

因此,要升级,只需将从 wxt/storagewxt/clientwxt/sandbox 的任何导入替换为新的 #imports 模块:

ts
import { storage } from 'wxt/storage'; 
import { defineContentScript } from 'wxt/sandbox'; 
import { ContentScriptContext, useAppConfig } from 'wxt/client'; 
import { storage } from '#imports'; 
import { defineContentScript } from '#imports'; 
import { ContentScriptContext, getAppConfig } from '#imports'; 

你可以将导入合并为单个导入语句,但直接查找/替换每个语句更简单。

ts
import { storage } from 'wxt/storage'; 
import { defineContentScript } from 'wxt/sandbox'; 
import { ContentScriptContext, useAppConfig } from 'wxt/client'; 
import {
  storage, 
  defineContentScript, 
  ContentScriptContext, 
  getAppConfig, 
} from '#imports'; 

TIP

在类型生效之前,你需要在安装 v0.20 后运行 wxt prepare 来生成新的 TypeScript 声明。

createShadowRootUi CSS 变更

WXT 现在通过在 Shadow Root 内设置 all: initial 来重置从网页继承的样式(visibilitycolorfont-size 等)。

WARNING

这不影响 rem 单位。如果网页设置了 HTML 元素的 font-size,你应该继续使用 postcss-rem-to-px 或等效的库。

如果你使用 createShadowRootUi

  1. 移除任何手动重置特定网站样式的 CSS 覆盖。例如:

    css
    body { 
      /* 覆盖 Reddit 对元素默认的 "hidden" visibility */
      visibility: visible !important; 
    } 
  2. 仔细检查你的 UI 是否与之前看起来一样。

如果你遇到新行为的问题,可以禁用它并继续使用当前的 CSS:

ts
const ui = await createShadowRootUi({
  inheritStyles: true, 
  // ...
});

默认输出目录变更

outDirTemplate 配置的默认值已更改。现在,不同的构建模式会输出到不同的目录:

  • --mode production.output/chrome-mv3:生产构建不变
  • --mode development.output/chrome-mv3-dev:开发模式现在有 -dev 后缀,这样就不会覆盖生产构建
  • --mode custom.output/chrome-mv3-custom:其他自定义模式以 -[mode] 后缀结尾

要使用旧行为(将所有输出写入同一目录),请设置 outDirTemplate 选项:

ts
export default defineConfig({
  outDirTemplate: '{{browser}}-mv{{manifestVersion}}', 
});

WARNING

如果你之前在开发时手动将扩展加载到浏览器中,你需要从新的开发输出目录卸载并重新安装。

已弃用的 API 已移除

  • entrypointLoader 选项:WXT 现在使用 vite-node 在构建过程中导入入口点。

    这在 v0.19.0 中已弃用,请参阅 v0.19 章节 了解迁移步骤。

  • transformManifest 选项:请改用 build:manifestGenerated 钩子来转换 manifest:
    ts
    export default defineConfig({
      transformManifest(manifest) { 
      hooks: { 
        'build:manifestGenerated': (_, manifest) => { 
         // ...
        }, 
      },
    });

新的弃用项

runner API 已重命名

为了与 web-ext.config.ts 文件名保持一致性,"runner" API 和配置选项已被重命名。你可以继续使用旧名称,但它们已被弃用,将在未来版本中移除:

  1. runner 选项已重命名为 webExt

    ts
    export default defineConfig({
      runner: { 
      webExt: { 
        startUrls: ["https://wxt.dev"],
      },
    });
  2. defineRunnerConfig 已重命名为 defineWebExtConfig

    ts
    import { defineRunnerConfig } from 'wxt'; 
    import { defineWebExtConfig } from 'wxt'; 
  3. ExtensionRunnerConfig 类型已重命名为 WebExtConfig

    ts
    import type { ExtensionRunnerConfig } from 'wxt'; 
    import type { WebExtConfig } from 'wxt'; 

v0.18.5 → v0.19.0

vite-node 入口点加载器

默认的入口点加载器已更改为 vite-node。如果你使用了依赖 webextension-polyfill 的 NPM 包,你需要将它们添加到 Vite 的 ssr.noExternal 选项中:

ts
export default defineConfig({
  vite: () => ({ 
    ssr: { 
      noExternal: ['@webext-core/messaging', '@webext-core/proxy-service'], 
    }, 
  }), 
});

阅读完整文档 了解更多信息。

此变更启用了以下功能:

导入变量并在入口点选项中使用它们:

ts
import { GOOGLE_MATCHES } from '~/utils/constants'

export default defineContentScript({
  matches: [GOOGLE_MATCHES],
  main: () => ...,
})

使用 Vite 特定的 API(如 import.meta.glob)来定义入口点选项:

ts
const providers: Record<string, any> = import.meta.glob('../providers/*', {
  eager: true,
});

export default defineContentScript({
  matches: Object.values(providers).flatMap(
    (provider) => provider.default.paths,
  ),
  async main() {
    console.log('Hello content.');
  },
});

基本上,你现在可以在入口点的 main 函数之外导入和执行操作了——以前不能这样做。不过要小心,仍然建议避免在 main 函数之外运行代码,以保持构建速度。

要继续使用旧方式,请在 wxt.config.ts 文件中添加以下内容:

ts
export default defineConfig({
  entrypointLoader: 'jiti', 
});

WARNING

entrypointLoader: "jiti" 已弃用,将在下一个主版本中移除。

取消 CJS 支持

WXT 不再附带 Common JS 支持。如果你在使用 CJS,以下是迁移步骤:

  1. 在你的 package.json 中添加 "type": "module"
  2. 将使用 CJS 语法的 .js 文件扩展名更改为 .cjs,或更新它们以使用 ESM 语法。

Vite 也提供了迁移到 ESM 的步骤。查看更多详情:https://vitejs.dev/guide/migration#deprecate-cjs-node-api

v0.18.0 → v0.18.5

当此版本发布时,它并未被视为破坏性变更……但实际上应该是。

新的 modules/ 目录

WXT 现在将 modules/ 目录识别为包含 WXT 模块 的文件夹。

如果你已经有 <srcDir>/modules<srcDir>/Modules 目录,wxt prepare 和其他命令将会失败。

你有两个选择:

  1. [推荐] 保持文件在原位,让 WXT 在不同的文件夹中查找:

    ts
    export default defineConfig({
      modulesDir: 'wxt-modules', // 默认为 "modules"
    });
  2. 将你的 modules 目录重命名为其他名称。

v0.17.0 → v0.18.0

自动将 MV3 host_permissions 转换为 MV2 permissions

出于谨慎考虑,此变更被标记为破坏性变更,因为权限生成方式不同。

如果你在 wxt.config.ts 的 manifest 中列出了 host_permissions 并且已发布你的扩展,请仔细检查你在 .output/*/manifest.json 文件中针对所有目标浏览器的 permissionshost_permissions 是否有变化。权限变更可能导致扩展在更新时被禁用,并可能导致用户流失,因此请务必与之前的 manifest 版本进行对比检查。

v0.16.0 → v0.17.0

Storage - defineItem 需要 defaultValue 选项

如果你之前使用了带版本控制但没有默认值的 defineItem,你需要在选项中添加 defaultValue: null 并更新第一个类型参数:

ts
const item = storage.defineItem<number>("local:count", { 
const item = storage.defineItem<number | null>("local:count", { 
defaultValue: null, 
  version: ...,
  migrations: ...,
})

如果传入第二个选项参数,defaultValue 属性现在是必需的。

如果你省略第二个选项参数,它将默认为可空的,与之前一样。

ts
const item: WxtStorageItem<number | null> =
  storage.defineItem<number>('local:count');
const value: number | null = await item.getValue();

Storage - 修复 watch 回调中的类型

如果你不使用 TypeScript,这不是破坏性变更,这只是类型变更。

ts
const item = storage.defineItem<number>('local:count', { defaultValue: 0 });
item.watch((newValue: number | null, oldValue: number | null) => { 
item.watch((newValue: number, oldValue: number) => { 
  // ...
});

v0.15.0 → v0.16.0

输出目录结构变更

输出目录中的 JS 入口点已被移动。除非你在做某种引用文件的构建后工作,否则不需要做任何更改。

plaintext
.output/
  <target>/
    chunks/
      some-shared-chunk-<hash>.js
      popup-<hash>.js // [!code --]
    popup.html
    popup.html
    popup.js // [!code ++]

v0.14.0 → v0.15.0

zip.ignoredSources 重命名为 zip.excludeSources

ts
export default defineConfig({
  zip: {
    ignoredSources: [
      /*...*/
    ], 
    excludeSources: [
      /*...*/
    ], 
  },
});

未文档化的常量已重命名

#380 中重命名了用于在运行时检测构建配置的未文档化常量。现在已记录在此:https://wxt.dev/guide/multiple-browsers.html#runtime

  • __BROWSER__import.meta.env.BROWSER
  • __COMMAND__import.meta.env.COMMAND
  • __MANIFEST_VERSION__import.meta.env.MANIFEST_VERSION
  • __IS_CHROME__import.meta.env.CHROME
  • __IS_FIREFOX__import.meta.env.FIREFOX
  • __IS_SAFARI__import.meta.env.SAFARI
  • __IS_EDGE__import.meta.env.EDGE
  • __IS_OPERA__import.meta.env.OPERA

v0.13.0 → v0.14.0

Content Script UI API 变更

createContentScriptUicreateContentScriptIframe 及其部分选项已被重命名:

  • createContentScriptUi({ ... })createShadowRootUi({ ... })
  • createContentScriptIframe({ ... })createIframeUi({ ... })
  • type: "inline" | "overlay" | "modal" 已更改为 position: "inline" | "overlay" | "modal"
  • onRemove 现在在 UI 从 DOM 中移除之前调用,之前是在 UI 移除之后调用
  • mount 选项已重命名为 onMount,以更好地与相关选项 onRemove 匹配。

v0.12.0 → v0.13.0

新的 wxt/storage API

wxt/storage 不再依赖 unstorage。一些 unstorage API(如 prefixStorage)已被移除,而其他的(如 snapshot)是新 storage 对象上的方法。大部分标准用法保持不变。详情请参阅 https://wxt.dev/guide/storagehttps://wxt.dev/api/reference/wxt/storage/#300

v0.11.0 → v0.12.0

API 导出变更

defineContentScriptdefineBackground 现在从 wxt/sandbox 而非 wxt/client 导出。(#284

  • 如果你使用自动导入,则无需更改。

  • 如果你禁用了自动导入,你需要手动更新导入语句:

    ts
    import { defineBackground, defineContentScript } from 'wxt/client'; 
    import { defineBackground, defineContentScript } from 'wxt/sandbox'; 

v0.10.0 → v0.11.0

Vite 5

你需要将其他 Vite 插件更新到支持 Vite 5 的版本。

v0.9.0 → v0.10.0

扩展图标发现

WXT 不再发现 .png 以外的图标文件。如果你之前使用了 .jpg.jpeg.bmp.svg,你需要将图标转换为 .png 文件,或在 wxt.config.ts 文件中手动将它们添加到 manifest 中。

v0.8.0 → v0.9.0

默认移除 WebWorker 类型

.wxt/tsconfig.json 中移除了 "WebWorker" 类型。这些类型对使用 Service Worker 的 MV3 项目很有用。

要将它们添加回你的项目,请在项目的 TSConfig 中添加以下内容:

json
{
  "extends": "./.wxt/tsconfig.json",
  "compilerOptions": {
    "lib": ["ESNext", "DOM", "WebWorker"] 
  } 
}

v0.7.0 → v0.8.0

defineUnlistedScript

未列出的脚本现在必须 export default defineUnlistedScript(...)

BackgroundDefinition 类型

BackgroundScriptDefintition 重命名为 BackgroundDefinition

v0.6.0 → v0.7.0

Content Script CSS 输出位置变更

Content Script CSS 之前输出到 assets/<name>.css,现在输出到 content-scripts/<name>.css 以与文档一致。

v0.5.0 → v0.6.0

vite 配置需要使用函数

vite 配置选项现在必须是一个函数。如果你之前使用的是对象,请将 vite: { ... } 更改为 vite: () => ({ ... })

v0.4.0 → v0.5.0

恢复公共目录移动

将默认的 publicDir<rootDir>/public 改回 <srcDir>/public

v0.3.0 → v0.4.0

更新默认路径别名

.wxt/tsconfig.json 中使用相对路径别名。

v0.2.0 → v0.3.0

移动公共目录

将默认的 publicDir<srcDir>/public 更改为 <rootDir>/public

改进类型安全

browser.runtime.getURL 添加类型安全。

v0.1.0 → v0.2.0

重命名 defineBackground

defineBackgroundScript 重命名为 defineBackground