首页 / WXT 浏览器扩展框架教程 / 不出现在 manifest 里的入口:unlisted

WXT 浏览器扩展框架教程

不出现在 manifest 里的入口:unlisted

本教程共 45 篇 · 第 13 篇 · 更新于 2026-08-13 · 约 3 分钟阅读

WXTunlisted未列出入口浏览器扩展scripting入口点

本节目标:理解 listed 与 unlisted 的区别,掌握未列出页面、脚本、CSS 三种入口的用法,以及如何配合 scripting API 按需执行。

两种入口:listed 与 unlisted

第 07 章说过,入口分两类。**已列出(listed)**入口会写进 manifest.json,浏览器自动注册;**未列出(unlisted)**入口只打包成文件,不出现在 manifest 里,由代码自己决定何时加载。

未列出入口共三种:未列出页面、未列出脚本、未列出 CSS。

未列出页面:{name}.html

任何不是保留名字的 HTML 文件都是未列出页面:

📂 entrypoints/
   📄 welcome.html           # → 构建产物 welcome.html
   📂 help/
      📄 index.html          # → 构建产物 help.html

典型场景:安装后的欢迎页、登录页、帮助页。运行时用 browser.runtime.getURL 拿到真实地址,再打开:

const url = browser.runtime.getURL('/welcome.html');
await browser.tabs.create({ url });
// 结果类似 "chrome-extension://{id}/welcome.html"

mkext 的登录页就是未列出页面:browser.tabs.create({ url: browser.runtime.getURL('/tabs.html#login') })

未列出脚本:{name}.ts

任何不是保留名字的 TS/JS 文件都是未列出脚本:

📂 entrypoints/
   📄 injected.ts            # → 构建产物 injected.js

defineUnlistedScript 定义:

// entrypoints/injected.ts
export default defineUnlistedScript(() => {
  // 脚本被加载时执行
  document.querySelectorAll('a').forEach((a) => {
    // ...
  });
});

注意两点:

  • 脚本不会自动执行,加载和运行由你负责;
  • 要让网页能访问它(比如注入网页主世界),需要把产物加进 web_accessible_resources(权限相关内容见 §21)。

未列出脚本的典型场景:内容脚本往网页主世界(MAIN world)注入代码时,需要一份网页能直接引用的文件(详见 §15)。

未列出 CSS:{name}.css

CSS 入口永远是未列出的:

📂 entrypoints/
   📄 theme.css              # → 构建产物 theme.css

Vite 支持的预处理器都可以用,比如 theme.scsstheme.less,构建后照常输出 CSS。运行时想拿到样式文件的地址,同样用 browser.runtime.getURL('/theme.css')

有个特殊命名:content.css{name}.content.css 会被输出到 content-scripts/ 目录,语义上属于内容脚本。配合 cssInjectionMode 可以在运行时按需注入样式(如 §15 所述)。

与 scripting API 配合:按需执行

未列出脚本最大的用武之地,是配合 browser.scripting.executeScript 按需执行。后台可以这样调用:

// entrypoints/background.ts
const res = await browser.scripting.executeScript({
  target: { tabId },
  files: ['content-scripts/example.js'],
});
console.log(res); // 拿到脚本返回值

对应地,内容脚本入口把 registration 设为 'runtime',就不会在构建时注册进 manifest,而是等 executeScript 调用:

// entrypoints/example.content.ts
export default defineContentScript({
  registration: 'runtime',
  main(ctx) {
    console.log('Script was executed!');
    return 'Hello John!'; // executeScript 能拿到这个返回值
  },
});

声明式注册(manifest)适合「常驻功能」,运行时注入(runtime)适合「用户点了才执行」。后者的好处是不占网页访问权限,权限更小。更细的取舍见 §16。

未列出脚本最常见的翻车点:文件加进了

,但路径写错或尺寸没匹配上,网页里引用直接 404。检查方法:构建后在 .output/ 里找到产物文件,把 browser.runtime.getURL 返回的地址和产物目录对照一遍(产物检查法见 §38)。

别忘了 main 规则

未列出脚本同样会被构建期 Node 导入,运行时代码必须放进 defineUnlistedScript 的回调里:

// ❌ 顶层操作 DOM
document.querySelectorAll('a').forEach((a) => {});

// ✅ 放进回调
export default defineUnlistedScript(() => {
  document.querySelectorAll('a').forEach((a) => {});
});

另一个容易忽略的点:未列出页面虽然不进 manifest,但照样被打包进产物,商店审核看得到文件——别把密钥、内网地址写进去,它们和产物代码一样公开(§27 的环境变量警告同理)。

以及:未列出入口的名字不能和保留入口冲突,比如别建一个 background.html 想当普通页面——它会被当成后台入口处理(§07)。

小结

未列出入口 = 打包但不注册。页面用 browser.runtime.getURL 打开,脚本用 executeScript 执行或直接引用,CSS 配合内容脚本按需注入。它们让「不需要常驻、不占用权限」的功能成为可能。