使用 Service Worker 处理事件
概述
实现功能:
- 使用万能搜索框(Chrome 浏览器地址框,官方称为
omnibox
)快速前往 Vue API 参考页面 - 在 Vue API 页面显示一个 Tips 按钮,弹出每天提示
PS:原谷歌示例是针对 Chrome Extension API 文档,本文改为 Vue API 文档(读者可以按自己喜欢改为其他),Tips 实现部分没找到 Vue 的,故用回谷歌文档的 Tips API。
教程
1. 创建项目结构
创建以下文件结构:
quick-api-reference/
├── content.js # 内容脚本,负责页面注入小贴士
├── images/
│ ├── icon-16.png # 16x16 图标
│ └── icon-128.png # 128x128 图标
├── manifest.json # 扩展配置文件
├── service-worker.js # 后台主脚本
├── sw-omnibox.js # omnibox 相关逻辑
├── sw-tips.js # 小贴士相关逻辑
└── README.md # 项目说明文档
2. 配置 manifest.json
创建 manifest.json
文件,这是Chrome扩展的配置文件:
{
"manifest_version": 3,
"name": "Open Vue API reference",
"version": "1.0.0",
"icons": {
"16": "images/icon-16.png",
"128": "images/icon-128.png"
},
"minimum_chrome_version": "102",
"permissions": ["storage", "alarms"],
"host_permissions": ["https://chrome.dev/f/*"],
"background": {
"service_worker": "service-worker.js",
"type": "module"
},
"omnibox": {
"keyword": "api"
},
"content_scripts": [
{
"matches": ["https://cn.vuejs.org/api/*"],
"js": ["content.js"]
}
]
}
关键说明:
permissions
申请了storage
和alarms
,用于存储和定时提醒- 由于定时提醒用了 chrome url,故
host_permissions
用https://chrome.dev/f/*
background
设置了 service worker 脚本,且类型为 ES 引入,并拆分了两个脚本sw-omnibox.js
和sw-tips.js
omnibox
即谷歌输入框的设置了触发关键词为api
content_scripts
是针对当前的页面内容修改,故设置了 url 为 Vue API 链接,且处理脚本为content.js
- 由于没有用户界面,故本次没有 html 文档
3. omnibox 部分实现
sw-omnibox.js:
实现流程图见下方~
// 参考文档的链接
const URL_VUE_DOC = 'https://cn.vuejs.org/api/';
// 限制显示的数量
const NUMBER_OF_PREVIOUS_SEARCHES = 5;
// 通过监听 `runtime.onInstalled()` 事件,在扩展程序首次安装时初始化状态
// (以后可以参考这个写法对应用实现一些初始化配置)
chrome.runtime.onInstalled.addListener(({ reason }) => {
if (reason === 'install') {
// 通过以 key-value 形式存入 chrome.storage.local;初始化默认提供了 3 个 api 关键词
chrome.storage.local.set({
apiSuggestions: ['application', 'general', 'composition-api-setup'],
});
}
});
// 在“万能输入框”监听输入变化事件,并提供默认建议
chrome.omnibox.onInputChanged.addListener(async (input, suggest) => {
// 设置默认建议
await chrome.omnibox.setDefaultSuggestion({
description: 'Enter a Vue API or choose from past searches',
});
// 从本地存储获取API建议列表
const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
// 根据API建议列表生成搜索建议
// 参数解释:
// - content: 用户选择建议后实际使用的值
// - description: 在地址栏下拉列表中显示给用户看的文本
const suggestions = apiSuggestions.map((api) => {
return { content: api, description: `Open Vue ${api} API` };
});
// 使用自定义拼接好的 suggestions 提供搜索建议
// suggest 函数是由 Chrome 浏览器自动注入的,用于向 Chrome 地址栏提供搜索建议
suggest(suggestions);
});
// 监 听输入完成事件(即用户按下 Enter),并处理输入的内容
chrome.omnibox.onInputEntered.addListener((input) => {
// 根据输入的内容创建新标签页,打开对应的 Vue API文档
chrome.tabs.create({ url: URL_VUE_DOC + input });
// 更新搜索历史
updateHistory(input);
});
// 更新搜索历史的函数
async function updateHistory(input) {
// 从本地存储获取API建议列表
const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
// 将当前输入的内容添加到搜索历史的最前面
apiSuggestions.unshift(input);
// 保持搜索历史的数量不超过指定的限制
apiSuggestions.splice(NUMBER_OF_PREVIOUS_SEARCHES);
// 更新本地存储中的搜索历史
return chrome.storage.local.set({ apiSuggestions });
}
小贴士:
提示:控制台打开了,脚本也会运行起来
4. tips 部分实现
content.js:
(async () => {
// 发送方
const { tip } = await chrome.runtime.sendMessage({ greeting: 'tip' });
const nav = document.querySelector('.content > nav');
const tipWidget = createDomElement(`
<button type="button" popovertarget="tip-popover" popovertargetaction="show" style="padding: 0 12px;background:#2eb985;color:#fff;">
<span style="display: block; font: var(--devsite-link-font,500 14px/20px var(--devsite-primary-font-family));">Tip</span>
</button>
`);
const popover = createDomElement(`<div id='tip-popover' popover style="margin: auto;">${tip}</div>`);
document.body.append(popover);
// 插入元素
nav.append(tipWidget);
})();
// 创建 dom 元素
function createDomElement(html) {
const dom = new DOMParser().parseFromString(html, 'text/html');
return dom.body.firstElementChild;
}
sw-tips.js:
实现流程图见下方~
// 获取/更新提示
const updateTip = async () => {
const response = await fetch('https://chrome.dev/f/extension_tips');
const tips = await response.json();
const randomIndex = Math.floor(Math.random() * tips.length);
console.log('updateTip');
return chrome.storage.local.set({ tip: tips[randomIndex] });
};
// 提示的闹钟名称
const ALARM_NAME = 'tip';
// 创建提示的闹钟:
// 每当闹钟(比如每天一次)触发时,就自动执行 updateTip,去获取新的小贴士
async function createAlarm() {
const alarm = await chrome.alarms.get(ALARM_NAME);
// 如果不存在则创建新闹钟
if (typeof alarm === 'undefined') {
// 用 chrome.alarms.create 创建了一个定时任务(比如每隔一段时间执行某个操作)
// 当这个定时任务到点时,chrome.alarms.onAlarm 事件就会被触发
chrome.alarms.create(ALARM_NAME, {
delayInMinutes: 1, // 延迟一分钟
periodInMinutes: 1440, // 间隔一天
});
updateTip();
}
console.log('createAlarm');
}
// 初始化提示的闹钟
createAlarm();
// 监听提示的闹钟
chrome.alarms.onAlarm.addListener(updateTip);
// 监听消息:
// chrome.runtime.onMessage 这个 API 是 Chrome 扩展中的消息监听器,
// 用于监听来自其他扩展脚本(如 content script、background、popup、options 等)发送的消息,
// 实现不同脚本之间的通信
// message:收到的消息内容
// sender:消息的发送者信息
// sendResponse:用于回复消息的函数
// 那么,谁是发送方(sender)?
// 发送方是通过 chrome.runtime.sendMessage 主动发消息的脚本
// 这是发送方是来自 content.js 的 `const { tip } = await chrome.runtime.sendMessage({ greeting: 'tip' });`
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// 当收到消息 { greeting: 'tip' } 时,从本地存储读取 tip,并通过 sendResponse 返回给发送方(比如 content script)
if (message.greeting === 'tip') {
chrome.storage.local.get('tip').then(sendResponse);
console.log('greeting', sender);
return true; // 这里告诉 Chrome:我会异步调用 sendResponse,请等我
}
});
5. 加载扩展
- 打开Chrome浏览器
- 访问
chrome://extensions/
- 开启"开发者模式"
- 点击"加载已解压的扩展程序"
- 选择项目目录
完整示例代码👇
实现流程图: