Chrome 插件中更简单的通信方式
在 chrome 73
版本,增加了 chrome.storage.onChanged
可以实现全局监听 storage
改变,我们可以借助这个方法实现一种非常简单的通信方式。
下面代码是封装成 npm
包后的使用方式
1 | // popup.js |
1 | // content.js |
是不是非常熟悉的调用方式!是不是非常简单!在任何脚本中都可以调用。
那么如何实现请见下文。
初学者的感到困难的通信方式
在写 chrome
插件过程中,经常会遇到一个问题,就是各个脚本需要通信,我们共有如下脚本
- popup.js
- options.js
- background.js
- content.js
- injected.js
injected.js 是指 content.js 通过 script 脚本插入的一段脚本,所以它和页面自己加载的 js 脚本拥有相同功能,最主要的是能读取挂载到 window 上的变量,而 content.js 不行。
大部分介绍怎么通信的博客文章都是介绍脚本两两之间怎么通信,如 runtime.sendMessage
、tabs.sendMessage
。但它们的通信方式不是完全相同的,这就导致了需要考虑谁向谁通信该调用什么方法。
最重要的是,如果想跨脚本通信也很麻烦,如 injected.js
向 popup.js
发送消息,或者反过来 popup.js
向 injected.js
发送消息都需要通过 content.js
中转。
options
向content
发消息也非常麻烦,需要在页面加载时保存当前页面的引用,然后在 options 页面中找到这个引用并发送消息。
还有一些问题奇奇怪怪的问题,如经常遇到一些通信相关的报错
- Unchecked runtime.lastError: The message port closed before a response was received.
- Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
刚刚接触 chrome
插件开发的同学(比如我)肯定是一脸懵逼。
那就没有一种简单的通信方式吗?
使用 storage.onChanged 全局监听
有,chrome
有提供一个存储的 api
,叫 storage
,我们可以用它来存储、读取一些持久化数据,在除 injected.js
外的脚本中都能直接调用,它在 73 版本支持 onChanged
监听方法,这就让 storage
和 redux store
有类似的用法
1 | // redux |
每次 dispatch
改变数据后,都会调用监听函数。与之类似
1 | chrome.storage.local.set({ key: 1, type: 'reload' }); |
在每次 set
后,也会调用监听函数。
所有脚本中只有 injected.js
不能调用 chrome.storage
,所以只能通过 CustomEvent
和 content.js
通信,再由 content.js
和其他脚本通信。
我们需要做的,就是提供统一 API
,让调用方无感知地完成通信。
简单的调用方式
如开头所示,我们只有两个方法,emit
和 on
。
假设现在 popup
上有一个按钮,点击后刷新页面,这么一个需求,使用我们的 emitter
来写是这样的
1 | // injected.js |
1 | // popup.js |
就是这么简单!
而如果用官方推荐的通信方法 sendMessage
1 | // popup.js |
1 | // content.js |
1 | // injected.js |
就问你懵不懵,两者的区别显而易见。
下载使用
那么这么容易使用的库在哪里能找到呢?现在只需要简单两步即可拥有
1 | yarn add chrome-emitter |
1 | import emitter from 'chrome-emitter'; |
原理实现
最核心的原理就是 chrome.storage.onChanged.addListener
,当然还需要对其进行一些封装。如 set
数据相同时,不会触发事件处理器;响应事件后移除 storage
中无用数据;
除此之外就是需要额外处理 injected
和其他脚本的通信,在 injected
内监听的事件,content
也需要监听,这样 popup
或者其他脚本,发送消息时先到 content.js
,再从 content.js
使用 CustomEvent
到 injected.js
,对用户来说,感觉就像从 popup
直接到了 injected
。
具体可以查看 代码。
示例
clone
代码后,在插件管理页面,加载 build
文件夹,即可安装该插件,体验 chrome-emitter
的效果。
emitter 存在的问题
讲完优点,缺点也不能忽略,emitter
和原生通信方式相比
- 在实际代码量上,肯定前者更多因为多加载了一个
chrome-emitter.js
。并且如果不使用打包工具则需要手动把emitter
代码拷贝到各个脚本中。 emitter.emit
第二个参数用来给监听方法提供参数,只能使用JSON
支持的类型(可能是会序列化保存到storage
导致的)。- 如果其他插件也调用了
onchanged.addListener
方法,会导致其也收到事件。 - 传递的数据量不能太大,由于需要存储到
storage
所以如果几百 kb 的数据量可能还是会有问题。
所以如果是简单的项目可以尝试使用下。
脚本间有区别的原因
前面提到脚本间有区别,其实本质上是 window.chrome
的值不同。
第一个是 background.js
。可以看到 background.js
、popup.js
和 options.js
都有完整的 api
,而 content.js
只有 runtime
和 storage
;injected.js
更是什么都没有。