news 2026/9/20 12:15:40

Quasar BEX 扩展类型全解析:New Tab、Popup、Options、DevTools 与网页注入实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Quasar BEX 扩展类型全解析:New Tab、Popup、Options、DevTools 与网页注入实战
  • 前端
  • UI组件
  • 跨平台

【免费下载链接】quasar

Quasar Framework - Build high-performance VueJS user interfaces in record time

项目地址:https://gitcode.com/gh_mirrors/qu/quasar
点击查看免费下载

导读

本文围绕 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 应用即可覆盖全部五种,无需为每种类型单独创建项目:

  1. New Tab(新标签页):在浏览器自己的标签页中运行,替换浏览器默认的新标签页。
  2. Developer Tools(开发者工具面板):运行在浏览器开发者工具窗口中。
  3. Popup(弹出窗):点击扩展图标后弹出的窗口。
  4. Options(选项页):扩展的设置页面。
  5. 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_popupdefault_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 v3Manifest v2
Popup(点击图标弹出)action.default_popupbrowser_action.default_popup
Options(扩展选项页)options_pageoptions_page
DevTools(开发者工具面板)devtools_pagedevtools_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 应用塞进去再注入目标网页,让应用看起来就像网页本身的一部分。

整体思路

  1. 内容脚本src-bex/my-content-script.js)在目标页面加载时执行:创建 IFrame,src指向扩展包内的www/index.html,插入到页面顶部。
  2. Quasar 应用/src)通过 BEX Bridge 向内容脚本发送事件,控制 IFrame 高度等行为。
  3. 内容脚本监听事件并操作 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.contentsave.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_workerbackground.scriptscontent_scripts[].js以及quasar.config中的bex.extraScripts四处收集脚本清单,逐个用 Rolldown 编译产出.js并写入构建目录(参见 bex-utils.js 的extractBexScripts());assetsicons_locales三个目录会被原样复制到构建产物(见copyBexAssets())。漏注册的内容脚本不会出现在产物中,这是新手最常见的"脚本不生效"原因。

补充两点工程细节:

  • TS 开发者:background 与 content 脚本在 manifest 中应写.ts扩展名(如my-content-script.ts),Quasar CLI 会在编译时自动把 manifest 内的.ts/.tsx改写为.js/.jsxextractBexScripts()中的scriptExtRE正则负责剥离扩展名)。浏览器厂商只认.js,转换由 CLI 自动完成。
  • 权限最小化:模板默认使用<all_urls>匹配,示例内容脚本可在任意页面运行,但这会扩大扩展的权限并增强安装时的权限警告。若扩展只服务特定站点,应将matcheshost_permissionsweb_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.isConnectedtrue;后续也可调用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 驱动,流程如下:

  1. createManifest():读取src-bex/manifest.json,按目标浏览器合并all+chrome/firefox,自动补齐nameshort_namedescriptionversion(取自 package.json),执行bex.extendBexManifestJson钩子,最后把改写后的 manifest 写入构建目录。
  2. copyBexAssets():复制assetsicons_locales目录。
  3. 并行构建:UI 部分走 Vite(产物进www/目录,Firefox 与生产构建均输出到dist/www,见 bex-config.js);各 BEX 脚本(background、content scripts、extraScripts)走 Rolldown。
  4. 若无--skip-pkg参数,最后用fflatedist目录打包为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,否则不会进入构建产物。
  • 权限保持最小化:把matcheshost_permissionsweb_accessible_resources收敛到实际所需范围,减少权限警告、降低安全风险。

相关阅读:

  • BEX 类型官方文档
  • 配置 BEX(manifest 结构与 quasar.config 选项)
  • BEX Bridge 通信机制
  • 内容脚本详解
  • BEX 模式构建源码 与 manifest 处理源码
  • 前端
  • UI组件
  • 跨平台

【免费下载链接】quasar

Quasar Framework - Build high-performance VueJS user interfaces in record time

项目地址:https://gitcode.com/gh_mirrors/qu/quasar
点击查看免费下载
上一篇:Typo.js 项目推荐
下一篇:TypeResolver类型告警:异常解析的及时通知机制

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 12:14:04

我用GetQzonehistory把QQ空间说说、评论和配图搬进本地表格

我用GetQzonehistory把QQ空间说说、评论和配图搬进本地表格 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 跑完 GetQzonehistory 一次&#xff0c;本地 resource/result/你的QQ号/ 目录…

作者头像 李华
网站建设 2026/9/20 12:13:34

用PHP解析B站视频下载地址:从BV号到高清播放地址的完整实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 12:12:52

示波器实验报告数据处理:从V/div读数到李萨如图形与误差分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 12:11:41

10 分钟用 TaoToken 跑通 Dify 工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华