前言
chrome插件大家应该都不陌生了,或多或少都会装几个。那么作为前端开发者,当然要考虑用插件来做一些提升开发效率的工具了。
插件能做什么?
-
抓取埋点请求,格式化埋点数据。
使用gif图上报埋点是很常见的场景,这种数据一般是一坨放在url参数后面,测试和产品在验收时都很麻烦(直接看数据后台可能会受到其他数据的影响),使用插件拦截的http请求后,可以将所有埋点的请求记录下来,格式化后呈现。 -
请求mock
拦截http请求,修改入参和出参,比charles、Fiddler等第三方工具方便太多了。 -
在线UI图的图片素材直接上传CDN
现在很多公司都在用蓝湖、慕客这种在线UI稿,图片素材需要先下载到本地,再上传到CDN,使用插件可以做到点击页面的图片直接上传到自己的CDN,复制好CDN链接。 -
自动登录,切换账号
开发过程中经常要切换环境、切换账号,使用插件记录不同的环境和不同类型的账号,快捷切换。 -
...
以上都是我在开发过程中真正使用了的插件,提升了开发效率,对测试和产品也有很大帮助。
这个系列文章最终会实现一个自动登录/切换环境的插件,并可简单识别验证码。
本章节代码放在这里。
插件效果如下:
文章目标
- 插件的开发流程
- 基础功能介绍
- 搭建vue开发环境
- 开发一个切换环境与账户的插件
参考资料
【干货】Chrome插件(扩展)开发全攻略,这个教程写的很不错,只是时间有点久了,文章采用的v2版本,目前chrome已经停止了接收v2版本的插件(本地开发可以使用)。v3版本的修改也不是特别多,所以这篇文章也还是可以继续参考的。
实现一个最基本的插件
采用官方文档的示例,实现一个改变当前页面背景色的插件,以此来对插件的文件结构和开发流程有一个简单的认识。
新建manifest文件
新建插件目录chrome-ext-change-bg, 目录下新建manifest.json文件,来对项目进行声明及配置,完整的配置项可以看这里
{
"name": "change bg", // 插件名
"description": "change current page background color", // 描述
"version": "1.0", // 插件版本
"manifest_version": 3, // manifest版本,固定写3即可
}
在chrome中加载插件
没错,建好manifest文件, 插件就已经创建好了(当然了,没有任何功能),在chrome中把我们的插件加载进去:
- 地址栏输入
chrome://extensions打开扩展程序设置(也可以在设置中进入)。 - 打开右上角开发者模式,点击加载已解压的扩展程序,选择插件文件夹即可。
此时没有指定icon图标,会以插件首字母作为默认的图标。
点击图中按钮,将我们的插件固定在任务栏。
注册background script
要让插件执行某些功能,就需要一个运行环境,background script就是这个运行环境之一(其他环境我们后面再说)。
manifest.json添加background配置:
{
// ...
"manifest_version": 3,
"background": {
"service_worker": "background.js" // 声明一个service_worker,代码内容为当前目录下的`background.js`
}
}
现在chrome知道了你的插件包含一个service worker,在你重新加载插件时,chrome会运行声明的文件(这里即background.js)。这个worker的生命周期贯穿整个chrome的生命周期,从插件加载(打开chrome)开始运行,到关闭chrome后停止。所以如果有什么需要在插件开始运行时就执行的操作,如插件事件的监听,可以放到这里。
创建background script的执行文件
我们在插件加载完毕(installed)后,使用 storage API存储一个默认的颜色值。
在插件目录下新建background.js,写入如下代码:
// background.js
let color = '#3aa757';
chrome.runtime.onInstalled.addListener(() => {
chrome.storage.sync.set({ color });
console.log('Default background color set to %cgreen', `color: ${color}`);
});
插件API chrome.*
chrome 插件 API 在插件的运行环境中,chrome提供了一些API,这些API都挂载在全局变量chrome上,使用这些API,可以获取当打开的所有页面(tabs),在插件中存储一些持久化数据(如上面的storage)...
大部分API的功能在使用时,都要在manifest中进行权限声明。
添加storage权限声明
要使用storage,需要在manifest中声明权限。插件的权限要求比较严格,http请求、加载外部资源等,都需要声明。
{
"permissions": ["storage"]
}
检查background script
回到插件的设置页,点击Reload图标重新加载插件(没有热更新,每次修改都需要reload)
点击service worker查看background script的打印, "Default background color set to green"
用户界面
用户界面有好几种类型,每个界面的形式都是一个html页面。这里我们先使用popup。就是左键点击chrome右上角的插件图标,弹出的界面。
vue调试工具的popup
在根目录创建popup.html。放置一个按钮来修改背景色,与普通的html页面一样,可以引入css文件。
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="button.css">
</head>
<body>
<button id="changeColor"></button>
</body>
</html>
创建button.css
button {
height: 30px;
width: 30px;
outline: none;
margin: 10px;
border: none;
border-radius: 2px;
}
button.current {
box-shadow: 0 0 0 2px white,
0 0 0 4px black;
}
与background script类似,这个文件也必须在manifest里声明。添加action配置来声明popup.html
{
"name": "change bg",
"description": "change current page background color",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["storage"],
"action": {
"default_popup": "popup.html"
}
}
插件在工具栏的图标也是在action里设置的,图片文件可以在这里下载,解压到插件根目录。
{
// ...
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
}
}
}
在chrome插件管理页面和警告、其他用户界面的favicon,也会显示图标,这些图标的配置在manifest配置项的根节点下的icons.
{
"name": "change bg",
"description": "change current page background color",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["storage"],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
}
},
"icons": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
}
}
刷新插件,可以看到chrome工具栏的插件图标已经变化了
创建popup.js,给popup界面的按钮加上之前在content script 里设置的颜色
// Initialize button with user's preferred color
let changeColor = document.getElementById("changeColor");
chrome.storage.sync.get("color", ({ color }) => {
changeColor.style.backgroundColor = color;
});
在popup.html中插入
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="button.css">
</head>
<body>
<button id="changeColor"></button>
<script src="popup.js"></script>
</body>
</html>
重新加载插件,可以看到按钮变成了绿色
content script
目前为止我们使用了两种代码的运行环境:
background scriptpopup
这两个环境都是独立于用户浏览的实际页面的(将这些页面称为web page),我们想修改web page的颜色,需要让代码在web page里执行,这种代码就叫做content script。
content script是运行在web page中的代码,可以操作页面DOM,修改它们,或者读取页面信息传递给background script等其他环境(使用messages通信)。
与普通的js不同,在content script中还可以使用一部分chrome插件的API,如获取插件的storage中内容,使用messages与插件其他环境通信...但是相比于其他环境,做了很大的限制。
在content script中支持的API:
content script的执行方式
content script的执行方式有两种:
- 动态插入
在background script、popup等环境中,通过chrome提供的API:chrome.scripting.executeScript,动态插入到web page中执行,支持插入代码块和代码文件。
chrome.scripting.executeScript({
target: { tabId: tab.id }, // 要插入的web page的id
func: setPageBackgroundColor, // 插入代码块
files: ['content-script.js'] // 插入文件
});
需要注意的是,如果是插入代码块(func),chrome会将函数体复制后执行,而不是直接执行原函数,所以在函数体内不能使用外部变量,否则会报变量未声明错误。
- 声明式
在manifest中声明需要插入的content script文件,可配置需要插入的页面url规则,以及执行时机(如页面加载完毕后执行)。
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"css": ["my-styles.css"],
"js": ["content-script.js"]
}
],
...
}
改变页面背景色
现在我们的插件有了图标和popup交互界面,并且把popup中按钮的颜色设置为存在插件的storage中的颜色值。接下来在popup.js后添加逻辑,点击popup的绿色按钮,让当前页面的背景色改变。而让当前页面(web page)背景色改变,就需要使用content script了。
因为我们要在popup中点击按钮时才改变页面背景色(即触发content script执行),所以采用方式1来动态插入content script。
在popup.js文件末尾添加如下代码:
// 点击按钮时,将 setPageBackgroundColor 函数插入到当前页面执行
changeColor.addEventListener("click", async () => {
// chrome.* 是chrome给插件提供的api,后面会再详细说明
// 获取当前页面
let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
// 使用chrome.scripting.executeScript, 在web page中执行代码块(content script)
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: setPageBackgroundColor,
});
});
// 这个函数的「函数体」将作为当前页面content script被执行
function setPageBackgroundColor() {
// content script可以
chrome.storage.sync.get("color", ({ color }) => {
document.body.style.backgroundColor = color;
});
}
这段代码给button添加了点击事件,点击后将改变背景色的代码块(setPageBackgroundColor)插入到当前正在浏览的web page中,改变其背景色。
对于上面使用到的API,要更新权限声明,修改manifest.json
{
"name": "change bg",
...
"permissions": ["storage", "activeTab", "scripting"],
...
}
保存后重新加载插件,点击工具栏的插件图标,点击绿色按钮,当前页面的背景色就会被改为绿色了。
插件配置页options
现在我们的插件已经可以修改页面的背景色了,但是只能修改为默认的绿色。现在来提供一个选项页面,让用户可以选择更多的背景色。
options页面是个交互界面,与popup不同的是,popup可以理解为用户的操作界面,而options则是一个用来让用户配置插件选项的页面
在根目录新建options.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="button.css">
</head>
<body>
<div id="buttonDiv">
</div>
<div>
<p>Choose a different background color!</p>
</div>
<script src="options.js"></script>
</body>
</html>
在manifest中注册
{
"name": "change bg",
...
"options_page": "options.html"
}
创建options.js
let page = document.getElementById("buttonDiv");
let selectedClassName = "current";
const presetButtonColors = ["#3aa757", "#e8453c", "#f9bb2d", "#4688f1"];
// 点击不同的颜色块,将颜色值存入storage
function handleButtonClick(event) {
// Remove styling from the previously selected color
let current = event.target.parentElement.querySelector(
`.${selectedClassName}`
);
if (current && current !== event.target) {
current.classList.remove(selectedClassName);
}
// Mark the button as selected
let color = event.target.dataset.color;
event.target.classList.add(selectedClassName);
chrome.storage.sync.set({ color });
}
// 将几种预设的颜色按钮渲染到options页面中,添加点击事件
function constructOptions(buttonColors) {
chrome.storage.sync.get("color", (data) => {
let currentColor = data.color;
// For each color we were provided…
for (let buttonColor of buttonColors) {
// …create a button with that color…
let button = document.createElement("button");
button.dataset.color = buttonColor;
button.style.backgroundColor = buttonColor;
// …mark the currently selected color…
if (buttonColor === currentColor) {
button.classList.add(selectedClassName);
}
// …and register a listener for when that button is clicked
button.addEventListener("click", handleButtonClick);
page.appendChild(button);
}
});
}
// 初始化颜色按钮
constructOptions(presetButtonColors);
重新加载组件,右键点击工具栏的插件图标,进入选项页面。