如何使用 sidePanel 侧边栏
sidePanel(侧边栏)是 Chrome MV3 提供的一种常驻 UI 入口,适合不打断浏览的持续性交互。在 webextkits 中,它和 options、popup 一样属于普通的扩展页——直接拥有 chrome.* 权限,可以直接使用 useStorageLocal,无需经过 messages-center 桥接。
浏览器版本要求
sidePanel API 需要 Chrome 114+,chrome.sidePanel.open 需要 Chrome 116+。模板默认采用方式 A(用 chrome.sidePanel.open),因此 manifest.ts 中的 minimum_chrome_version 设为 "116"。若你只用方式 B(点图标打开侧边栏),可下调到 "114"。
目录约定
侧边栏的代码放在 src/scopes/sidepanel/ 下,结构与 popup 完全一致:
┣ 📂src
┃ ┗ 📂scopes
┃ ┃ ┗ 📂sidepanel
┃ ┃ ┃ ┣ 📜index.html # 侧边栏页面入口
┃ ┃ ┃ ┗ 📂src
┃ ┃ ┃ ┃ ┣ 📜index.tsx # React 挂载入口
┃ ┃ ┃ ┃ ┗ 📜App.tsx # 页面逻辑,演示 useStorageLocal 读写 ┣ 📂src
┃ ┗ 📂scopes
┃ ┃ ┗ 📂sidepanel
┃ ┃ ┃ ┣ 📜index.html # 侧边栏页面入口
┃ ┃ ┃ ┗ 📂src
┃ ┃ ┃ ┃ ┣ 📜index.tsx # React 挂载入口
┃ ┃ ┃ ┃ ┗ 📜App.tsx # 页面逻辑,演示 useStorageLocal 读写App.tsx 与 popup 一样直接使用 useStorageLocal 读写 bucket:
import { schema, SchemaType } from "@/schema";
import { useStorageLocal } from "@webextkits/storage-local";
const { updateBucket, getBucket } = useStorageLocal<SchemaType>(schema);import { schema, SchemaType } from "@/schema";
import { useStorageLocal } from "@webextkits/storage-local";
const { updateBucket, getBucket } = useStorageLocal<SchemaType>(schema);manifest 注册
在 src/manifest.ts 中需要两处改动:
permissions加入"sidePanel"- 新增顶层
side_panel.default_path指向侧边栏入口
export default defineManifest(async () => ({
// ...
permissions: ["tabs", "scripting", "storage", "sidePanel"],
side_panel: {
default_path: "src/scopes/sidepanel/index.html",
},
// ...
}));export default defineManifest(async () => ({
// ...
permissions: ["tabs", "scripting", "storage", "sidePanel"],
side_panel: {
default_path: "src/scopes/sidepanel/index.html",
},
// ...
}));
side_panel.default_path是普通的 HTML 入口,由@crxjs/vite-plugin自动处理,无需改动任何 vite 插件配置。
两种打开方式
Chrome 不允许「点击扩展图标」同时绑定 popup 和侧边栏,因此需要二选一。模板默认采用方式 A,并在注释中提供方式 B 的切换说明。
方式 A(默认):保留 popup,按钮打开侧边栏
action.default_popup 保持不变,点击图标仍然弹出 popup。在 popup 内放一个按钮调用 chrome.sidePanel.open 打开侧边栏:
<Button
onClick={() => {
// 必须在用户手势调用栈内同步调用 open;WINDOW_ID_CURRENT 是常量,无需异步查询
chrome.sidePanel
.open({ windowId: chrome.windows.WINDOW_ID_CURRENT })
.catch((e) => console.error(e));
}}
>
打开侧边栏
</Button><Button
onClick={() => {
// 必须在用户手势调用栈内同步调用 open;WINDOW_ID_CURRENT 是常量,无需异步查询
chrome.sidePanel
.open({ windowId: chrome.windows.WINDOW_ID_CURRENT })
.catch((e) => console.error(e));
}}
>
打开侧边栏
</Button>用户手势限制(易踩坑)
chrome.sidePanel.open() 必须在用户手势(如按钮点击)的同步调用栈内触发。若在调用前 await(例如先 await chrome.tabs.query 取 tabId),手势会丢失,导致 sidePanel.open() may only be called in response to a user gesture 报错。
因此这里用 windowId: chrome.windows.WINDOW_ID_CURRENT(常量,无需异步)直接打开,避免任何 await 前置。
方式 B:点击图标直接打开侧边栏
如果你的扩展以侧边栏为主、不需要 popup,可切换为方式 B:
- 移除
manifest.ts中action.default_popup - 在
background/index.ts中启用:
chrome.sidePanel
.setPanelBehavior({ openPanelOnActionClick: true })
.catch((e) => console.error(e));chrome.sidePanel
.setPanelBehavior({ openPanelOnActionClick: true })
.catch((e) => console.error(e));此时点击扩展图标会直接打开侧边栏。模板的 background/index.ts 中已以注释形式预置了这段代码,取消注释并删除 default_popup 即可。
与构建链路的关系
sidePanel 是普通扩展页,落地它不需要修改 @webextkits/vite-plugins 与 @webextkits/messages-center:
vite-plugins的injects/externals机制只服务src/scopes/injects/(页面 MAIN world),不涉及扩展页。- 侧边栏的 HTML 入口由
@crxjs/vite-plugin读取 manifest 自动处理,路径与popup/options完全一致。
Webextkits Docs