不出现在 manifest 里的入口:unlisted
本教程共 45 篇 · 第 13 篇 · 更新于 2026-08-13 · 约 3 分钟阅读
本节目标:理解 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.scss、theme.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 配合内容脚本按需注入。它们让「不需要常驻、不占用权限」的功能成为可能。