- 前端
- 示例工程
【免费下载链接】chrome-extension-boilerplate-react-vite
Chrome Extension Boilerplate with React + Vite + Typescript
Content Runtime Script 是 chrome-extension-boilerplate-react-vite 模板中用于在运行时(runtime)向任意页面按需注入脚本与 UI的能力模块。它通过 pages/content-runtime/README.md 定义使用流程,配合chrome.scriptingAPI 在运行时动态执行,区别于 manifest 中静态声明的 content scripts。读完本文,你将掌握如何新增一个 runtime 注入脚本、理解其目录约定与构建产物命名规则、看懂底层 Shadow DOM 隔离机制,并能立即在自己的项目里落地一个带 React UI 的按需注入方案。
Content Runtime Script 是什么:与静态 Content Script 的对比
在 Chrome 扩展(Manifest V3)开发中,向页面注入代码有两种主流路径:
- 静态声明(manifest content_scripts):在 manifest 中预先声明
matches与 JS/CSS 文件,浏览器在页面加载时自动注入。当前仓库的 chrome-extension/manifest.ts 中就声明了content/all.iife.js、content-ui/all.iife.js等静态注入项。 - 运行时注入(chrome.scripting.executeScript):在任意时刻通过
chrome.scripting.executeScriptAPI 动态注入脚本,无需提前在 manifest 中声明。这正是 Content Runtime Script 模块的用途。
根据 pages/content-runtime/README.md 的定义,该模块“allows users to inject scripts (Console and UI) during runtime into all pages specified by you”,即:既可以注入纯逻辑脚本(Console),也可以注入带界面的 UI,并且注入目标页面的集合完全由你指定。
从源码结构看,pages/content-runtime/src/matches 下预置了all与example两个注入脚本入口,每个入口都包含 React 组件(App.tsx)、样式(index.css)与挂载逻辑(index.tsx),说明该模块默认面向React UI 的运行时注入场景。
快速上手:新增一个运行时注入脚本
第一步:复制matches/example并改造
原文档给出的第一步是:复制matches/example文件夹,以其他名称命名并编辑其内容。对应的源码目录为 pages/content-runtime/src/matches/example,包含三个文件:
App.tsx:React 根组件。默认实现仅打印日志并渲染一行文本:
import { useEffect } from 'react'; export default function App() { useEffect(() => { console.log('[CEB] Example runtime content view loaded'); }, []); return <div className="ceb-example-runtime-content-view-text">Example runtime content view</div>; }index.css:组件样式,通过@import '@extension/ui/global.css'引入全局样式,并定义局部类:
@import '@extension/ui/global.css'; .ceb-example-runtime-content-view-text { font-size: 20px; }index.tsx:注入入口,负责将组件渲染进页面的 Shadow DOM(详见下文“底层原理”小节):
import inlineCss from '../../../dist/example/index.css?inline'; import { initAppWithShadow } from '@extension/shared'; import App from '@src/matches/example/App'; initAppWithShadow({ id: 'CEB-extension-runtime-example', app: <App />, inlineCss });例如,若要新增一个在 GitHub 页面上注入的“快速导航”脚本,只需将example目录复制为github-nav,然后:
- 修改
App.tsx中的组件内容与useEffect日志; - 修改
index.css中的样式类名(建议保持ceb-前缀以避免与宿主页面冲突); - 修改
index.tsx中initAppWithShadow的id(如CEB-extension-runtime-github-nav)与样式导入路径../../../dist/github-nav/index.css?inline。
第二步:通过chrome.scripting.executeScript触发注入
新目录就绪后,在你希望触发注入的任意位置(README 默认建议放在popup弹窗中)调用chrome.scripting.executeScript,注意注入文件的命名规则:
await chrome.scripting.executeScript({ ..., files: ['/content-runtime/{matches_folder_name}.iife.js'], });其中{matches_folder_name}就是你新建的文件夹名。沿用上面的例子,应为:
await chrome.scripting.executeScript({ target: { tabId }, files: ['/content-runtime/github-nav.iife.js'], });第三步:确保scripting权限
运行时注入依赖chrome.scriptingAPI。当前仓库的 chrome-extension/manifest.ts 已声明permissions: ['storage', 'scripting', 'tabs', 'notifications', 'sidePanel'],其中scripting正是运行时脚本注入所需权限;同时host_permissions: ['<all_urls>']允许脚本注入到所有匹配的 URL。如果你是从零新建扩展,请务必在 manifest 的permissions中加入"scripting",否则executeScript会因缺少权限而失败。
目录约定与构建产物命名:{folder}.iife.js从何而来
README 中files: ['/content-runtime/{matches_folder_name}.iife.js']的产物命名并非凭空约定,而是由仓库中的构建配置决定的。
从源码结构看,Content Runtime 模块的每个matches子目录被当作一个独立的 Rollup/Vite 入口。其入口收集逻辑位于 packages/vite-config/lib/get-content-script-entires.ts:
- 该函数扫描
matchesDir(即matches目录)下的每个子文件夹; - 若某文件夹是目录,则检查其中是否存在
index.ts或index.tsx; - 若两者都不存在,则抛出异常:
{folder} in \matches` doesn't have index.ts or index.tsx file`; - 否则以文件夹名作为入口 key,指向其中的
index.ts或index.tsx文件。
因此,新建的注入脚本文件夹必须包含index.ts或index.tsx作为入口文件(example与all使用的都是index.tsx)。构建时,Vite 会将每个入口打包成<folder>.iife.js格式的 IIFE(立即执行函数)产物,再经由content-runtime/前缀部署到扩展根目录,最终与 README 中的'/content-runtime/{matches_folder_name}.iife.js'路径一一对应。.iife.js后缀意味着产物是自包含的、以立即执行形式运行的脚本,这正是executeScript直接加载文件所需的形式。
底层原理:initAppWithShadow与 Shadow DOM 隔离
example/index.tsx与all/index.tsx都调用了来自@extension/shared的initAppWithShadow。其实现位于 packages/shared/lib/utils/init-app-with-shadow.ts,核心流程如下:
- 创建一个
div元素(root.id = id)并追加到document.body; - 在该
div上通过attachShadow({ mode: 'open' })建立 Shadow DOM; - 将内联 CSS 注入 Shadow DOM:
- Firefox 环境:由于 Mozilla 已知 Bug #1770592 不支持
adoptedStyleSheets,改用<style>元素注入样式(实现中注明这可能导致与宿主页面样式冲突); - 其他浏览器:使用
CSSStyleSheet+adoptedStyleSheets注入,实现彻底的样式隔离;
- Firefox 环境:由于 Mozilla 已知 Bug #1770592 不支持
- 最后通过
createRoot(rootIntoShadow).render(app)将 React 组件渲染进 Shadow DOM。
样式文件通过import inlineCss from '../../../dist/example/index.css?inline'以内联字符串形式引入(Vite 的?inline查询参数),从而避免运行时再次发起网络请求、也无需在web_accessible_resources中额外注册 CSS 资源。该机制使注入的 UI不会受宿主页面样式污染,也不会反向污染宿主页面,这是运行时注入 UI 相比直接document.body.appendChild的关键优势。
两个预置示例的对照参考
仓库提供了两个开箱即用的参考实现:
- pages/content-runtime/src/matches/example:面向特定站点(如
https://example.com/*,对应 manifest 中静态 content scripts 的匹配范围)的示例,Shadow DOM 根节点 id 为CEB-extension-runtime-example; - pages/content-runtime/src/matches/all:面向所有页面的示例,根节点 id 为
CEB-extension-runtime-all,App.tsx中日志为[CEB] All runtime content view loaded。
对照两者可以发现,除目录名、id、样式类名与组件文案不同外,其index.tsx的挂载模式完全一致,这正是“复制文件夹即可新增脚本”这一设计的体现。由于matches目录中的每个文件夹都会由 get-content-script-entires.ts 自动收集为独立入口,新增脚本不需要修改任何配置文件——复制、改名、编辑三步即可。
与静态 Content Scripts 的协同与选型建议
回到 chrome-extension/manifest.ts,可以看到静态声明的 content scripts 会根据matches自动注入。运行时注入与静态注入的协同场景大致如下:
- 页面加载时就必须存在的注入,优先使用 manifest 静态声明(如
content/all.iife.js),浏览器保证注入时机与顺序; - 需要按需触发(点击弹窗按钮、收到消息、用户操作后才注入)、或希望精确控制注入目标的 UI,使用 Content Runtime Script +
chrome.scripting.executeScript,它不受 manifest 匹配规则的束缚,完全由业务代码决定注入时机与目标标签页。
从 README 的默认用法看,popup(弹窗)是最典型的触发入口:用户点击扩展图标后,根据当前标签页 URL 决定是否注入、注入哪个脚本,这与模板中 pages/popup 模块的定位相符。在实际项目中,你完全可以将触发逻辑迁移到 background service worker(chrome-extension/src/background/index.ts)中,结合消息传递实现更复杂的注入决策。
总结
Content Runtime Script 模块以极低的接入成本提供了“运行时按需注入 React UI”的能力:一个文件夹即一个注入入口,构建自动产出content-runtime/{folder}.iife.js,配合chrome.scripting.executeScript即可在任意目标页面动态挂载 UI;底层initAppWithShadow的 Shadow DOM 方案则保证了注入内容与宿主页面之间的样式隔离。结合本仓库的 pages/content-runtime/README.md、pages/content-runtime/src/matches/example/index.tsx 与 packages/shared/lib/utils/init-app-with-shadow.ts,你可以快速搭建属于自己的运行时注入方案。
关键参考路径:
- Content Runtime 使用文档
- 预置示例入口(example)
- 预置示例入口(all)
- 入口自动收集逻辑
- Shadow DOM 注入实现
- 扩展 manifest 权限与静态注入声明
- 前端
- 示例工程
【免费下载链接】chrome-extension-boilerplate-react-vite
Chrome Extension Boilerplate with React + Vite + Typescript
相关推荐
oneTBB Flow Graph 保留式 join_node(Reservation)协议解析与实战
oneTBB Flow Graph 保留式 join_node(Reservation)协议解析与实战 导读 本文以 mold 项目所携带的 oneTBB(Th
前端示例工程Chrome 扩展 Content UI 指南:基于 React + Vite 向指定页面注入组件(chrome-extension-boilerplate-react-vite)
Chrome 扩展 Content UI 指南:基于 React + Vite 向指定页面注入组件(chrome extension boilerplate r
前端示例工程深入解析 chrome-extension-boilerplate-react-vite 的 @extension/env 环境变量包:从 .env 到构建流程的完整指南
深入解析 chrome extension boilerplate react vite 的 @extension/env 环境变量包:从 .env 到构建流程
前端示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考