写在前面:本文并不是浏览器插件或web component的使用教程,而是他们两者的结合使用,所以需要读者对浏览器插件及web component有基本的认识。
零、引言
最近接到了一个需求,是开发一个谷歌浏览器插件(MV3版本)。
此插件需要在用户打开指定页面时,请求后端接口,将后端返回的一些数据展示在页面上。(说白了就是使用插件编写页面UI)。
其实要实现这个需求并不困难,众所周知,插件的contentScripts是可以访问页面的部分属性,如document元素。所以通过document.createElement、Node.appendChild等方法创建元素并插入至页面的目标位置,是完全可以实现需求的。
但如果我们需要编写的UI有多个可复用的部分,使用上述原始方法就显得不那么优雅了。
此时我们自然会想到“组件”这一概念,正如vue组件和react组件一样的可复用组件。那么我们就可联想到web component这一技术,来封装、复用组件。
一、在html中使用
不考虑浏览器插件,只考虑在正常网页HTML(即执行环境包含DOM和BOM)中时,使用web component定义自定义元素是非常方便的,只需一个组件类
和customElements.define
api。
下面代码展示了如何使用web components创建自定义元素:
// 封装Card组件类
class Card extends HTMLElement {
constructor() {
super();
// 获取传入的prop
let text = this.getAttribute("text");
const shadow = this.attachShadow({ mode: "open" });
// 创建组件外层元素
const wrapper = document.createElement("div");
wrapper.setAttribute("class", "card-wrapper");
wrapper.innerText = text;
// 将元素添加到shadow DOM
shadow.appendChild(wrapper);
// 添加一些样式
const style = document.createElement("style");
style.textContent = `
.card-wrapper {
border: 1px solid #ff522f;
border-radius: 8px;
padding: 4px;
font-size: 14px;
color: #ff522f;
}
`;
shadow.appendChild(style);
}
}
// 定义自定义元素
customElements.define("message-card", Card);
二、在插件中使用
但非常遗憾的是,在浏览器插件的contentScripts中,是不能访问customElements,那自然就无法调用define方法了。
2-1、思路
既然浏览器插件的contentScripts中无法访问customElements,那么我们希望借助contentScripts可使用的一些api,去使得网页HTML帮我们执行定义自定义元素的相关代码。
所以我们的思路就是,利用插件的contentScripts生成一个script标签,并将其放置在页面的html中,而这个script标签引用的就是上面定义自定义元素的代码。
那么这段代码的运行环境就是网页的html,也就可以访问customElements对象了。
2-2、实践
首先列出本次例子的目录结构:
contentScripts文件夹中的index.js就是普通的contentScripts文件。而card.js就是我们定义自定义元素的代码,它的内容与第一节代码一样。
plugs/
├── src/
│ ├── background/
│ │ └── index.js
│ ├── contentScripts/
│ ├── index.js
│ └── card.js
├── manifest.json
└── index.html
创建好文件之后就要在manifest.json中标明文件该如何使用了。
由下面的代码片段可知,contentScripts/index.js
是正常作为contentScripts的一部分被引用的。
但contentScripts/card.js
就需要配置在web_accessible_resources字段中,这代表了这部分的资源可以被网页访问。 (需要注意的是:resources中的路径是基于插件项目根目录的路径)
{
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": [
"./src/contentScripts/index.js"
],
"run_at": "document_end",
"all_frames": true
}
],
"web_accessible_resources": [
{
"resources": ["src/contentScripts/card.js"],
"matches": ["<all_urls>"]
}
]
}
在manifest.json中配置了contentScripts/card.js可被网页访问后,就可以借助chrome.runtime.getURL
这个api,为card.js生成一个url,并作为script元素的src属性值。(需要注意的是chrome.runtime.getURL的参数也是基于插件项目根目录的路径,而非当前文件的相对路径)
然后将此script元素插入至网页html中,那么此script就能加载card.js的内容,并且运行环境就是网页HTML,而非插件的contentScripts了。
// 生成script
const script = document.createElement("script");
// 使用chrome.runtime.getURL生成url
script.src = chrome.runtime.getURL(
"src/contentScripts/card.js" // 注意:这里的路径也是基于插件项目根目录的路径
);
// 将script插入至页面的html文档中,使其运行环境是网页html
document.documentElement.appendChild(script);
如下截图就是被插进网页的script元素加载的js资源,也就是在插件中定义的自定义元素代码。
三、总结
总的来说,在浏览器插件中定义自定义元素,就是分三步走:
- 正常编写web component代码(但是不要在manifest.json的content_scripts中引入)
- 配置manifest.json的web_accessible_resources字段,这代表了这部分的资源可以被网页访问。
- 在常规contentScripts文件中,借助
chrome.runtime.getURL
生成组件文件url,生成script元素,引用此url,并将其插入页面html中。