Skip to content

如何使用 sidePanel 侧边栏

sidePanel(侧边栏)是 Chrome MV3 提供的一种常驻 UI 入口,适合不打断浏览的持续性交互。在 webextkits 中,它和 optionspopup 一样属于普通的扩展页——直接拥有 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 完全一致:

text
 ┣ 📂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.tsxpopup 一样直接使用 useStorageLocal 读写 bucket:

tsx
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 中需要两处改动:

  1. permissions 加入 "sidePanel"
  2. 新增顶层 side_panel.default_path 指向侧边栏入口
ts
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 打开侧边栏:

tsx
<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:

  1. 移除 manifest.tsaction.default_popup
  2. background/index.ts 中启用:
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-pluginsinjects/externals 机制只服务 src/scopes/injects/(页面 MAIN world),不涉及扩展页。
  • 侧边栏的 HTML 入口由 @crxjs/vite-plugin 读取 manifest 自动处理,路径与 popup/options 完全一致。

Powered by Vitepress