Skip to content

插件编译入口

插件的入口很多,比如加载到页面中的 content、点击插件弹出的 popup、侧边栏 sidepanel

我看录音和截屏这里的文档,想着确实有的功能模块比如录音录像,用一个全新的标签页或者 window 去打开会比较好。

结果如果你新建一个 HTML 发现 manifest 里面没有引用,vite 里面也没引用,根本就不会编译了,又不可能直接在 html 里面写原生,感觉不是很优雅。还是写 react 然后挂载到这个 html 里面吧,虽然可能直接原生 js 更方便做小 demo。

就需要在 vite 里面配置一下入口,相关的文件就会编译了。

js
    rollupOptions: {
      input: {
        popup: "src/popup/index.html",
        sidepanel: "src/sidepanel/index.html",
        record: "src/content/record/index.html",
      },
    },
    ```

插件 shadowdom

插件由于需要在不同的页面上加载,难免会遇到样式冲突问题,shadowdom 看上去很美好,样式隔离、不用额外处理比如添加前缀之类的。

但是实际还是有问题,比如原页面:root 的样式比如变量,还是会继承下来。

比如 react 实际上不会把 tsx 引入的 css 文件加载到 shadowdom 下面,而是直接加载到全局,然后就被隔离了。

最后只能是折中中的折中,引入 css 文件然后在创建 shadowdom 的时候加一个 style 标签进去,除了以后的其他组件样式都要手动添加一遍,倒也不算特别大的问题。使用 styled-component 好像也会有这个问题。

毕竟不隔离样式的话感觉更容易出问题而且不好排查。到时候如果还要反过来修改原网页的样式就会比较麻烦。人家更新一下这边就全乱完了。

js
// 把 content 下的 css 以原始文本方式导入并注入到 shadowRoot 中,
// 否则 Vite 会把样式注入到 document.head,shadow DOM 内的元素看不到这些样式。
// 使用 ?raw 从 Vite 获取原始文本内容。
// @ts-ignore
import appCss from "./App.css?raw";
// @ts-ignore
import globalCss from "../../styles/global.css?raw";
// * react会把css放入document中,而不是shadowRoot中,需要在顶层script中手动注入css样式
// 将样式注入到 shadowRoot 中(把 global.css 放到最前面)
const style = document.createElement("style");

style.textContent = [globalCss, appCss].join("\n\n");
shadowRoot.appendChild(style);

background 消息返回 sendResponse 延迟问题

之前写其他项目的时候就遇到过,明明文档里写的是 return true 就可以了,结果还是不行,返回的内容总是 undefined。后来想办法用插件的 storage 来存储返回值,然后在需要的地方监听取出来。

结果终于找到问题了,onMessage 监听函数不能当作一个异步函数来处理。不能先 await sendResponse()return true。但是不异步的话,return true 之后函数就运行完了。

这里就需要 settimeout 了,setTimeout(async () => {}),settimeout 一个异步函数,这里面写 sendResponse() 就可以了。

js
case BackgroundMessageAction.RefreshUserState:
  // 异步处理不能放在omessage里面,需要单独用setTimeout包裹,同时return true
  setTimeout(async () => {
    const res = await getUserState()
    sendResponse(res)
  })
  return true

如何在插件中状态管理

状态存在哪里都会有点问题,如果在 background 通信就会比较麻烦,在 content 或者 popup 里面就更不合适了,而且 popup 打开关闭的频率很高,导致 popup 里面拿到的数据也是经常出问题。

考虑之后还是放在 background 呗,数据通过 zustand/vanilla 存储,port 断开时取消订阅

ts
export const TaskControlStorePortName = "taskControlStorePortName";

chrome.runtime.onConnect.addListener((port) => {
  if (port.name === TaskControlStorePortName) {
    const unsub = taskControlStore.subscribe((state) => {
      postMessage(state.taskQueue);
    });

    port.onDisconnect.addListener(() => {
      unsub();
    });
  }
});