Content Script:往网页里注入代码
本教程共 45 篇 · 第 11 篇 · 更新于 2026-08-13 · 约 3 分钟阅读
本节目标:认识内容脚本入口与 defineContentScript,掌握 matches 等核心配置,理解最小站点范围原则和 main 里的清理方式。
内容脚本是什么
内容脚本(content script)是注入到网页里运行的 JS。它像派到别人家的代表:能读能改网页 DOM,但和网页脚本隔离,有自己的小世界。隔离机制、UI 注入等进阶内容见 §15。
创建内容脚本
命名规则比前面几种灵活:
📂 entrypoints/
📄 content.ts # 通用内容脚本
📄 google-search.content.ts # 带名字的内容脚本
📂 youtube.content/
📄 index.ts # 目录写法
带名字的写法适合多个内容脚本并存。构建后输出到 content-scripts/ 目录,比如 google-search.content.ts 变成 content-scripts/google-search.js。
最小内容脚本:
// entrypoints/example.content.ts
export default defineContentScript({
matches: ['*://*.wxt.dev/*'],
main() {
console.log('脚本已注入', location.href);
},
});
核心配置项
内容脚本的配置几乎都是 manifest 选项,WXT 自动写入:
| 配置 | 作用 |
|---|---|
| matches(必填) | 哪些网址注入,用 match pattern 写法 |
| excludeMatches | 排除哪些网址 |
| runAt | 注入时机:document_start / document_end / document_idle |
| allFrames | 是否也注入所有 iframe |
| world | ISOLATED(默认)或 MAIN(主世界) |
| registration | manifest(默认,构建时注册)或 runtime(运行时注册,§13 / §16) |
| cssInjectionMode | CSS 注入方式:manifest / manual / ui |
match pattern 的通用写法是 scheme://host/path:
matches: [
'https://example.com/*',
'https://*.example.com/articles/*',
],
把 match pattern 想成门牌号:https://example.com/* 是「这一户」,https://*.example.com/* 是「这条街上所有分店」。写错门牌号,脚本要么进错门(注入到不该去的站点),要么根本进不去(该注入的站点没匹配上)。写完先在心里过一遍每个 pattern 会命中哪些网址,再交给浏览器。
最小站点范围原则
内容脚本的 matches 就是扩展的网页访问范围。范围越大,权限越大,审核越严,风险越高。原则只有一条:只匹配功能需要的站点。
mkext 的教程专门提醒:演示项目为了「开箱见效果」用了 http://*/* 全站匹配,真实产品绝不该照抄。功能只在一个站点用,就只匹配那个站点:
export default defineContentScript({
matches: ['https://docs.example.com/*'],
main(ctx) {
// 业务逻辑
},
});
别为了省事写
。先收窄站点和路径,再决定功能形态。只在你需要时才扩大范围。
运行时代码必须进 main
和后台一样,内容脚本文件也会被构建期 Node 导入,顶层运行时代码(比如 document.createElement)会出错。全部放进 main:
export default defineContentScript({
matches: ['https://example.com/*'],
main(ctx) {
const heading = document.querySelector('h1')?.textContent;
console.log(heading);
},
});
main 可以是 async——这是与后台 main 的重要区别(后台 main 不能 async,见 §10)。
用 ctx 清理,别依赖返回值
扩展更新、禁用时,内容脚本会被浏览器终止,但页面里可能残留事件监听、定时器、MutationObserver。清理工作交给 ctx 对象:
main(ctx) {
const observer = new MutationObserver(() => {});
observer.observe(document.body, { childList: true });
// 脚本失效时自动断开
ctx.onInvalidated(() => observer.disconnect());
}
不要写
指望 WXT 调用返回值——WXT 不会调用它,清理是空承诺。要用 ctx 的
onInvalidated、setTimeout等助手。ctx 的完整生命周期与失效机制在第 14 章。
真实示例
mkext 的 Google 搜索增强脚本:只在 Google 搜索页注入,document_idle 时机执行,监听结果变化并防抖重扫,失效时断开监听:
// entrypoints/google-search.content.ts(简化)
export default defineContentScript({
matches: ['https://www.google.com/search*'],
runAt: 'document_idle',
main(ctx) {
const observer = new MutationObserver(() => {
// 结果变化后重扫,200ms 防抖
});
observer.observe(document.body, { childList: true, subtree: true });
ctx.onInvalidated(() => observer.disconnect());
},
});
注意 runAt: 'document_idle' 的写法:等页面主体加载完再注入,避免和页面初始化抢时间。
allFrames 默认只注入顶层页面;要覆盖 iframe 里的场景(比如视频网站的播放器)才需要打开,开了之后每个 frame 都会执行一次脚本。
小结
- 内容脚本按
matches注入网页,最小站点范围既是原则也是审核要求。 runAt控制注入时机,registration决定「manifest 注册」还是「运行时注册」。- 入口注册细节与上下文生命周期分别在 §07 与 §14。