- 前端
- UI组件
- 跨平台
【免费下载链接】quasar
Quasar Framework - Build high-performance VueJS user interfaces in record time
导读
本文围绕 Quasar 官方文档中"浏览器扩展(BEX)的入口类型"这一主题展开,系统讲解如何使用一套 Quasar 应用(@quasar/app-vite)同时承载"新标签页(New Tab)"、"弹出窗(Popup)"、"选项页(Options)"、"开发者工具页(DevTools)"以及"注入网页的浮层 UI"五种浏览器扩展入口。你将掌握每种入口的 manifest 配置方式、基于路由(Hash 模式)的页面映射技巧,以及通过 IFrame + 内容脚本把 Quasar 应用"嵌入"任意网页并实现双向通信的完整实战方案。
在 Quasar 中,浏览器扩展模式的入口形态远比普通网页应用灵活:同一个 App 的 UI 既可以独立运行在浏览器自带的标签页、弹窗与开发者工具面板中,也可以被注入到第三方网页里形成浮层。核心思路是——用 Vue Router 定义路由,用src-bex/manifest.json把每个入口指向www/index.html#/<route>。下文将逐一展开。
五种 BEX 入口类型概述
浏览器扩展(BEX)本质上是一类运行在浏览器托管上下文中的应用,可以自定义浏览器本身或它展示的页面。Quasar 的 BEX 模式支持以下五种运行形态,而一个 Quasar 应用即可覆盖全部五种,无需为每种类型单独创建项目:
- New Tab(新标签页):在浏览器自己的标签页中运行,替换浏览器默认的新标签页。
- Developer Tools(开发者工具面板):运行在浏览器开发者工具窗口中。
- Popup(弹出窗):点击扩展图标后弹出的窗口。
- Options(选项页):扩展的设置页面。
- Web Page 注入:以内容脚本 + IFrame 的方式运行在被注入的网页上下文里。
前四种(New Tab、DevTools、Popup、Options)本质上是"浏览器自己管理页面"的入口,只需要在 manifest 中把对应字段指向带路由的www/index.html即可;第五种则需要额外的注入与通信机制,是扩展能力最丰富的场景,文档也专门用一节案例演示。
关于 BEX 的整体结构,可参考 配置 BEX;五种类型的官方原始说明见 types-of-bex.md。
New Tab:替换浏览器的新标签页
新标签页类型最简单:在 manifest 中将chrome_url_overrides指向www/index.html,浏览器就会在用户新建标签页时加载你的 Quasar 应用。
{ "chrome_url_overrides": { "newtab": "www/index.html" } }注意两点:
- 点击扩展图标(toolbar 图标)与新建标签页是两条不同路径。点击图标走的是 manifest 的
action(Manifest v3)或browser_action(Manifest v2)配置,而不是chrome_url_overrides。如果希望点击图标也打开同一页面,需要同时配置action.default_popup或default_title等字段。 - 构建产物中的
www/index.html来自 Quasar 应用在/src下的 UI(Vite 构建输出),构建(或开发)时会被注入到扩展包中,详细机制见 bex-config.js 中vite()对build.outDir的处理。
Developer tools、Options 与 Popup:同一套路由,三种入口
这三种入口遵循完全相同的模式:在 Vue Router 中创建路由,再把 manifest 中对应字段指向带 Hash 的路由地址。
Hash 模式路由(www/index.html#/popup)之所以可行,是因为扩展页面的 URL 是chrome-extension://<extension-id>/www/index.html#/...,不存在服务端,因此不能依赖 history 模式所需的服务器 rewrite 规则——Hash 路由由浏览器本地解析,天然适配扩展环境。
1. 定义路由
在 Quasar 应用的routes.js中为每种入口创建独立页面组件:
const routes = [ { path: '/options', component: () => import('@/pages/OptionsPage.vue') }, { path: '/popup', component: () => import('@/pages/PopupPage.vue') }, { path: '/devtools', component: () => import('@/pages/DevToolsPage.vue') } ]路由采用懒加载,每个入口页面只在被打开时才下载对应组件。
2. 在 manifest 中引用路由
Manifest v3(Chrome / Chromium 系):
{ "manifest_version": 3, "action": { "default_popup": "www/index.html#/popup" }, "options_page": "www/index.html#/options", "devtools_page": "www/index.html#/devtools" }Manifest v2(旧版兼容):
{ "manifest_version": 2, "options_page": "www/index.html#/options", "browser_action": { "default_popup": "www/index.html#/popup" }, "devtools_page": "www/index.html#/devtools" }字段语义对照:
| 入口 | Manifest v3 | Manifest v2 |
|---|---|---|
| Popup(点击图标弹出) | action.default_popup | browser_action.default_popup |
| Options(扩展选项页) | options_page | options_page |
| DevTools(开发者工具面板) | devtools_page | devtools_page |
关于 DevTools 的补充:
devtools_page本身只是开发者工具面板的"入口 HTML",真正的面板 UI 通常需要在该页面中通过chrome.devtools.panels.create()创建,你可以把www/index.html#/devtools路由对应的页面组件写成负责创建面板的引导逻辑,或直接在其中渲染面板内容。
工程细节:Quasar 的 manifest 支持
all/chrome/firefox三个顶层对象,构建时按目标浏览器合并(all与目标对象深合并)。如果你需要为 Chrome 和 Firefox 使用不同的 manifest 版本或不同的 background 机制,可以像模板那样分别配置,例如 Chrome 用background.service_worker,Firefox 用background.scripts(参见 模板 manifest)。合并逻辑位于 bex-utils.js 的createManifest(),且构建时会自动校验manifest_version字段是否存在。
Case study:把 Quasar 应用注入网页(Web Page 注入)
这是五种类型中"真正体现扩展威力"的场景:通过内容脚本创建一个 IFrame,把 Quasar 应用塞进去再注入目标网页,让应用看起来就像网页本身的一部分。
整体思路
- 内容脚本(
src-bex/my-content-script.js)在目标页面加载时执行:创建 IFrame,src指向扩展包内的www/index.html,插入到页面顶部。 - Quasar 应用(
/src)通过 BEX Bridge 向内容脚本发送事件,控制 IFrame 高度等行为。 - 内容脚本监听事件并操作 IFrame 与底层页面(例如展开抽屉时把 IFrame 拉满全屏,关闭时恢复成只露出工具栏的高度)。
下面按文档中的三个文件逐一实现。
第一步:内容脚本 —— 创建并注入 IFrame
src-bex/my-content-script.js:
/** * Importing the file below initializes the content script. * * Warning: * Do not remove the import statement below. It is required for the extension to work. * If you don't need createBridge(), leave it as "import '#q-app/bex/content'". */ import { createBridge } from '#q-app/bex/content' const bridge = createBridge({ debug: false }) /** * When the drawer is toggled set the iFrame height to take the whole page. * Reset when the drawer is closed. */ bridge.on('wb.drawer.toggle', ({ payload }) => { if (payload.open) { setIFrameHeight('100%') } else { resetIFrameHeight() } }) const iFrame = document.createElement('iframe') const defaultFrameHeight = '62px' /** * Set the height of our iFrame housing our BEX * @param height */ function setIFrameHeight(height) { iFrame.height = height } /** * Reset the iFrame to its default height e.g The height of the top bar. */ function resetIFrameHeight() { setIFrameHeight(defaultFrameHeight) } /** * The code below will get everything going. Initialize the iFrame with defaults and add it to the page. * @type {string} */ iFrame.id = 'bex-app-iframe' iFrame.width = '100%' resetIFrameHeight() // Assign some styling so it looks seamless Object.assign(iFrame.style, { position: 'fixed', top: '0', right: '0', bottom: '0', left: '0', border: '0', zIndex: '9999999', // Make sure it's on top overflow: 'visible' }) ;(function () { // When the page loads, insert our browser extension app. iFrame.src = chrome.runtime.getURL('www/index.html') document.body.prepend(iFrame) })()要点解析:
import { createBridge } from '#q-app/bex/content'这行不能删除——它负责初始化内容脚本上下文(#q-app/bex/content是 Quasar CLI 提供的别名模块,即使不用createBridge()也要保留import '#q-app/bex/content')。- 默认 IFrame 高度只有
62px,刚好容纳 Quasar 工具栏高度,因此页面主体仍可与底层网页交互;收到wb.drawer.toggle事件且open为真时,高度切换为100%,让整个抽屉可见。 chrome.runtime.getURL('www/index.html')把扩展包内的 UI 页面解析为chrome-extension://协议地址——这正是"UI 以www文件夹形式注入扩展包"的直接应用。- IFrame 使用
position: fixed覆盖全屏并设置极高zIndex,保证浮层永远在最上层。
第二步:给页面内容留出工具栏空间
src-bex/assets/content.css:
.target-some-header-class { margin-top: 62px; }给目标页面的头部元素增加62px的上外边距,避免 IFrame 中的工具栏遮挡网页原有内容(62px 与 IFrame 的默认高度一致,二者需要保持同步)。
第三步:Quasar 应用中控制抽屉并通知内容脚本
在/src的 Vue 组件中使用q-drawer,监听其开关事件,通过$q.bex(BEX Bridge 的 App 端接口)向内容脚本发送事件:
<q-drawer :model-value="drawerIsOpen" @update:model-value="drawerToggled"> Some Content </q-drawer>import { useQuasar } from 'quasar' import { ref } from 'vue' setup () { const $q = useQuasar() const drawerIsOpen = ref(true) async function drawerToggled () { const contentPort = $q.bex.portList.find(portName => portName.startsWith('content@my-content-script-') ) if (contentPort === void 0) { return } await $q.bex.send({ event: 'wb.drawer.toggle', to: contentPort, payload: { open: drawerIsOpen.value } }) // Only set this once the promise has resolved so we can see the entire slide animation. drawerIsOpen.value = !drawerIsOpen.value } return { drawerToggled } }关键机制:
- 端口查找:内容脚本的 Bridge 端口命名规则为
content@<脚本相对路径>-<实例号>(见 bex-bridge.md),所以用startsWith('content@my-content-script-')匹配;每个标签页都会有一个独立的内容脚本实例,portList中可能同时存在多个匹配项,这里取第一个。 - 先发事件再翻转状态:
drawerIsOpen.value的翻转放在await之后,确保 IFrame 先完成拉高动画,抽屉内容才完整可见。 - 返回值的语义:
send()返回 Promise,可await等待内容脚本处理完(bridge.on支持同步返回或返回 Promise 的异步响应)。
完成这三步后,你的 Quasar 应用就已经"跑在网页里"了。之后可以在 Quasar 应用中触发任意自定义事件(例如highlight.content、save.note等),内容脚本监听后直接操作底层页面的 DOM 或调用chrome.*API。
多内容脚本与资源清单:容易踩的坑
[!WARNING] 务必检查
src-bex/manifest.json,尤其是对my-content-script.js的引用。你可以同时拥有多个内容脚本,每新建一个,都必须同步在 manifest 中注册;同理,/src-bex/assets下新增的每个 CSS 文件也要在 manifest 中引用。
"content_scripts": [ { "matches": [ "<all_urls>" ], "css": [ "assets/content.css" ], "js": [ "my-content-script.js" ] } ]对应到 Quasar 构建流程:createManifest()会从background.service_worker、background.scripts、content_scripts[].js以及quasar.config中的bex.extraScripts四处收集脚本清单,逐个用 Rolldown 编译产出.js并写入构建目录(参见 bex-utils.js 的extractBexScripts());assets、icons、_locales三个目录会被原样复制到构建产物(见copyBexAssets())。漏注册的内容脚本不会出现在产物中,这是新手最常见的"脚本不生效"原因。
补充两点工程细节:
- TS 开发者:background 与 content 脚本在 manifest 中应写
.ts扩展名(如my-content-script.ts),Quasar CLI 会在编译时自动把 manifest 内的.ts/.tsx改写为.js/.jsx(extractBexScripts()中的scriptExtRE正则负责剥离扩展名)。浏览器厂商只认.js,转换由 CLI 自动完成。 - 权限最小化:模板默认使用
<all_urls>匹配,示例内容脚本可在任意页面运行,但这会扩大扩展的权限并增强安装时的权限警告。若扩展只服务特定站点,应将matches、host_permissions与web_accessible_resources收敛到最窄范围(例如https://*.example.com/*),具体可参考 配置 BEX 中的"Least-privilege example"。
与内容脚本通信的基础:BEX Bridge
网页注入场景中的事件收发依赖 BEX Bridge——这是 Quasar 为扩展各部件(background / content scripts / popup / options / devtools)提供的基于 Promise 的通信层。核心规则如下(详见 bex-bridge.md):
- Background 是通信中枢:所有消息都经由 background 脚本中的 Bridge 转发。若想让 app 与 content scripts 之间直接通信,必须在 background 脚本中创建 Bridge(且只在一个 background 脚本中创建,切勿多实例)。
- App 端:在
/src的 Vue 组件中通过$q.bex直接使用;$q.bex.portName固定为"app"。 - 内容脚本端:
createBridge({ debug: false })创建实例后,建议先挂载初始监听器,再调用bridge.connectToBackground()建立连接(连接成功后bridge.isConnected为true;后续也可调用bridge.disconnectFromBackground()主动断开)。 - 端口命名:
content@<路径>-<编号>(如content@my-content-script-2345),编号为 1–10000 的实例号。 - 监听连接变化:订阅内部事件
@quasar:ports可获得{ portList, added?, removed? },用于感知端口增删。 - 广播与定向:遍历
bridge.portList可向全部 app/content 端口广播;也可用portList.find(p => p.startsWith('content@'))定位任意一个内容脚本。
安全提示:来自内容脚本的消息本质上是不可信输入(内容脚本运行在任意网页上下文),处理消息前必须校验事件名、发送方、payload 形状、URL 与标识符,只暴露狭窄操作,并仅申请扩展真正需要的权限。
发送大 payload:浏览器对扩展消息有硬性大小限制。当payload为数组时,Bridge 会自动按数组元素分块发送(此时若想发送真正的数组,需把数组包进对象里,例如payload: { myArray: [...] },否则每个元素会被当作独立消息,效率极低);分块时记得给每块留出几字节余量,因为消息本身还包裹了其他属性。
构建与打包:从源码到可安装的 ZIP
理解入口类型后,顺带掌握构建流程有助于排查问题。BEX 构建由app-vite的 bex-builder.js 驱动,流程如下:
createManifest():读取src-bex/manifest.json,按目标浏览器合并all+chrome/firefox,自动补齐name、short_name、description、version(取自 package.json),执行bex.extendBexManifestJson钩子,最后把改写后的 manifest 写入构建目录。copyBexAssets():复制assets、icons、_locales目录。- 并行构建:UI 部分走 Vite(产物进
www/目录,Firefox 与生产构建均输出到dist/www,见 bex-config.js);各 BEX 脚本(background、content scripts、extraScripts)走 Rolldown。 - 若无
--skip-pkg参数,最后用fflate把dist目录打包为Packaged.<app-name>.zip,可直接加载或上传应用商店。
开发模式(Chrome)下,quasar dev -m bex会额外注入 WebSocket token 与开发服务器端口,使扩展页面能连上 Vite HMR——这也是为什么在 Chrome 下开发时扩展 UI 可以热更新。
小结
- 五种入口,一套 App:New Tab / Popup / Options / DevTools 通过
www/index.html#/<route>与 manifest 字段映射;网页注入通过内容脚本 + IFrame 实现。 - Hash 路由是扩展环境的默认选择:扩展 URL 无服务端,Hash 模式无需 rewrite 规则。
- 内容脚本是网页注入的钥匙:创建 Bridge、注入 IFrame、用
$q.bex.send()触发事件、用bridge.on()响应事件。 - 多内容脚本记得注册:每新增一个脚本或
assets下的 CSS,都要同步更新src-bex/manifest.json,否则不会进入构建产物。 - 权限保持最小化:把
matches、host_permissions、web_accessible_resources收敛到实际所需范围,减少权限警告、降低安全风险。
相关阅读:
- BEX 类型官方文档
- 配置 BEX(manifest 结构与 quasar.config 选项)
- BEX Bridge 通信机制
- 内容脚本详解
- BEX 模式构建源码 与 manifest 处理源码
- 前端
- UI组件
- 跨平台
【免费下载链接】quasar
Quasar Framework - Build high-performance VueJS user interfaces in record time
相关推荐
扩展页面开发实战:Popup、Options与New Tab页面
扩展页面开发实战:Popup、Options与New Tab页面 本文详细介绍了Chrome扩展中四种核心页面的开发技巧:Popup弹窗页面、Options设置
前端开发工具Quasar BEX 与 TypeScript:从脚手架到类型安全的浏览器扩展消息桥
Quasar BEX 与 TypeScript:从脚手架到类型安全的浏览器扩展消息桥 本指南讲解在 @quasar/app vite 项目中,如何使用 Type
前端UI组件跨平台Quasar BEX Content Scripts 完整指南:注册、桥接通信与网页 DOM 交互实战
Quasar BEX Content Scripts 完整指南:注册、桥接通信与网页 DOM 交互实战 内容脚本(Content Scripts)是 Quasa
前端UI组件跨平台
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考