浏览器插件开发实战

时间:2024-12-16 18:17:32



浏览器插件开发实战

  • [1] 入门DEMO
  • 一、创建项目
  • 二、创建manifest.json
  • 三、加载插件
  • 四、配置 service-worker.js
  • 五、以书签管理器插件为例
  • manifest.json
  • popup.html
  • popup.js
  • 查看效果
  • [2] Vue项目改造成插件
  • 一、复习Vue项目的结构
  • 二、删除、添加个别文件
  • 三、重写build
  • [3] 高级开发
  • 一、脚本通信


Google官方浏览器插件中文文档:https://developer.chrome.com/docs/extensions/reference?hl=zh-cn

Google官方浏览器插件示例代码仓:https://github.com/GoogleChrome/chrome-extensions-samples/tree/main

[1] 入门DEMO

一个最简单的Hello World级别的插件项目,最好要有三个文件:

1. manifest.json
2. service-worker.js

其中,manifest.json文件是插件程序说明文档,也是描述入口设置文档;service-worker.js是全局的js文件。接下来我们说明一下开发过程

一、创建项目

创建目录后,进入该目录路径

mkdir plugin_demo
cd plugin_demo

创建一个资源目录

mkdir icons

拖入你的插件图标,官方通常推荐是16x16, 32x32, 48x48, 128x128四个格式,我放了一个48*48的就够用了。

二、创建manifest.json

根目录下创建一个manifest.json主入口文件

touch manifest.json

然后编辑插件基本信息信息

{
    "manifest_version": 3,  # 主程序api版本,这个只设置为3 2已经废弃
    "name": "名字", # 插件名
    "description": "description", # 插件描述
    "version": "1.0", # 插件版本
    "icons": { # 声明图标
        "16": "icons/16.png",
        "48": "icons/48.png",
        "128": "icons/128.png"
    }
}

基本信息配好后,即使是这一个文件,其实也已经可以加载插件到chrome里了。

三、加载插件

1. **chrome浏览器输入: **chrome://extensions/2. 选中加载已解压的扩展程序3. 选择你的项目目录
4. 查看你的插件如下:

浏览器插件开发实战_json

5. 修改刷新 如果你文件有改动,通过4底部的刷新按钮,刷新加载文件观看变化。

至此,已经完成插件的初步加载过程。

四、配置 service-worker.js

如果说manifest.json是主入口文件,那么service-worker.js就像是前端项目里app.js级别的全局文件。

先在 manifest.json 配置:

{
    ...
    "background": {
        "service_worker": "service-worker.js"
    }
}

然后创建与manifest.json同级的文件service-worker.js,内容为:

chrome.runtime.onInstalled.addListener(function () {
  console.log("插件已被安装");
})

以上就是一个demo级别的浏览器插件所需的文件。manifest.json文件里面具有丰富的配置,常用的如点击、内嵌面板、权限配置需要自行去查看官方文档。

五、以书签管理器插件为例

以一个简单的书签管理器插件为例,展示一个demo级别的插件。插件项目共有三个文件:

浏览器插件开发实战_插件_02

manifest.json

首先创建一个manifest.json文件。

{
  "manifest_version": 3,
  "name": "Bookmark Viewer",
  "version": "1.0",
  "description": "Uses the chrome.bookmarks API to search through, add, and delete bookmarks from the user's bookmark tree.",
  "action": {
    "default_popup": "popup.html"
  },
  "permissions": ["bookmarks"]
}

action表示点击事件,即在浏览器里面点击了插件图标会如何。这里设置的default_popup表示点击之后弹出popup.html页面。

permissions是向浏览器声明要使用到的权限,这里会用到书签相关的操作,所以声明bookmarks。

popup.html

然后创建弹出给用户看的前端页面popup.html。

<!doctype html>
<html>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
  <head>
    <title>Bookmark Viewer</title>
    <script src="popup.js" type="module"></script>
  </head>
  <body>
    <button id="addButton">添加书签</button>
    <button id="removeButton">移除书签</button>
    <ul id="bookmarkList"></ul>
  </body>
</html>

popup.js

最后编写前端页面绑定的js,注意popup.js与全局service-worker.js是不一样的。

chrome.bookmarks.getTree((tree) => {
  const bookmarkList = document.getElementById('bookmarkList');
  displayBookmarks(tree[0].children, bookmarkList);
});

// Recursively display the bookmarks
function displayBookmarks(nodes, parentNode) {
  for (const node of nodes) {
    // If the node is a bookmark, create a list item and append it to the parent node
    if (node.url) {
      const listItem = document.createElement('li');
      listItem.textContent = node.title;
      parentNode.appendChild(listItem);
    }

    // If the node has children, recursively display them
    if (node.children) {
      const sublist = document.createElement('ul');
      parentNode.appendChild(sublist);
      displayBookmarks(node.children, sublist);
    }
  }
}

// Add a bookmark for www.google.com
function addBookmark() {
  chrome.bookmarks.create(
    {
      parentId: '1',
      title: 'Google',
      url: 'https://www.google.com'
    },
    () => {
      console.log('Bookmark added');
      location.reload(); // Refresh the popup
    }
  );
}

// Remove the bookmark for www.google.com
function removeBookmark() {
  chrome.bookmarks.search({ url: 'https://www.google.com/' }, (results) => {
    for (const result of results) {
      if (result.url === 'https://www.google.com/') {
        chrome.bookmarks.remove(result.id, () => {});
      }
    }
    location.reload();
  });
}

// Add click event listeners to the buttons
document.getElementById('addButton').addEventListener('click', addBookmark);
document
  .getElementById('removeButton')
  .addEventListener('click', removeBookmark);

查看效果

1. **chrome浏览器输入: **chrome://extensions/2. 选中加载已解压的扩展程序3. 选择本项目目录
4. 查看插件如下:

浏览器插件开发实战_json_03

5. 点击浏览器右上方的插件按钮,即可看到本机的书签

浏览器插件开发实战_html_04

[2] Vue项目改造成插件

为什么要用Vue开发Chrome插件?个人主要是回到自己熟悉的模式,提高效率。我们总不能用原生JS手撸插件吧。

Vue项目改造成插件的主要原理: 改造配置文件,这样npm run build的时候,生成的内容是chrome插件结构。同时根据dist结构,配置maifest.json。

一、复习Vue项目的结构

对于一个常规的Vue2、Vue3的项目,它的结构一般是:

./project
├── public
│   ├── favicon.ico
│   └── index.html
├── src
├── package.json
├── package-lock.json
├── jsconfig.json
├── babel.config.js
├── vue.config.js
└── README.md

打包(npm run build)之后的dist文件夹结构为:

./dist
├── css
├── js
├── public
├── favicon.ico
└── index.html

可以看到public目录下的内容,被原封不动的搬到dist目录下。

而src下的vue组件和main.js, 则被编译成js和css文件,分别存储到js和css下。

由于我们开发的是chrome插件,可以把不渲染的文件放到public目录下,把渲染源文件放到src下。

另外在public下,创建一个manifest.json文件即可。

把不编译的js,放到public的好处是: 对于chrome api,我们可以直接跳过编译。直接使用chrome.xxx的形式。

二、删除、添加个别文件

把Vue项目改造成插件只需要改动一点点。

第一步,把Vue项目的public文件夹里面的内容全部删除。

第二步,在Vue项目的public文件夹里添加插件必备的文件,结构如下:

./public
├── service-worker    
│   └── service-worker.js   # 插件后台运行js
├── manifest.json       		# 插件主描述文件
├── css
│   └── your.css       			# 自定义css 后面会和Vue项目打包的css合并到css文件夹下
├── js
│   └── your.js       			# 自定义js 后面会和Vue项目打包的js合并到js文件夹下
└── icons              			# 插件icon图片
    ├── 48.png
    ├── 16.png
    └── 128.png

第三步,在src文件夹下添加一个popup.html。这个popup就是最终打包时会使用的html,你的manifest.json得有行为(比如action)来绑定这个文件。

./src
├── ...					# 业务代码
└── popup.html  # 额外添加的插件关联html

html这样写

<!DOCTYPE html>
<html lang="">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
    html,
    body,
    #app {
        height: 100%;
        margin: 0px;
        padding: 0px;
    }
</style>
<body>
<div id="app"></div>
<script src="js/xxx.js"></script> # 你的自定义js
</body>
</html>

三、重写build

在vue.config.js中,配置build过程,主要说明要加载的页面是popup/popup.html。

配置vue.config.js

const { defineConfig } = require('@vue/cli-service')
const pages = {}; #声明一个page集合,如果有
const chromepages=["popup"];        # 这里主要有一个popup页面
chromepages.forEach(name=>{
pages[name]={
entry:`src/main.js`,        				# 页面入口是popup下的main.js
template:`src/popup.html`,  				# popup.html
filename:`${name}.html`             # 最终渲染的页面是popup.html      
};
});
module.exports = {
  pages,
  productionSourceMap: false,
}

然后执行build

npm run build

生成的目录结构如下

./dist
├── service-worker
│   └── service-worker.js
├── popup.html     #渲染的弹出页面
├── manifest.json  主程序入口
├── js
│   ├── popup.ff36d80a.js
│   └── chunk-vendors.1c3d3fe0.js
├── css
│   ├── popup.0b9776ec.css
│   └── your.css
└── icons
    ├── 48.png
    ├── 16.png
    └── .png

最后,直接在Googls插件页面选择加载dist文件夹即可。

[3] 高级开发

一、脚本通信

浏览器插件所涉及到的三类JS文件有:

1. service-worker.js。属于插件的全局js,背景脚本在扩展安装后就会持续运行,不会因为浏览器的标签页切换或关闭而停止。会在浏览器下一次打开后自动恢复,拥有更广的权限。
2. content.js。指的是向用户浏览的页面注入的JS文件,我们一版统称content脚本。注入content文件有很多写法,比如直接在manifest.json文件里面声明,或者在service-worker.js里面显式地写。content脚本的作用域在用户访问的页面那里,如果想和插件传递数据需要用google提供的message方法。
3. popup.js。指的是插件的html页面使用script标签引入的JS文件,它的地位是属于插件所有,作用域在插件这边,但是权限没有service-worker.js大。

因此,说脚本通信,指的是插件脚本(service-worker.js、popup.js)和content.js脚本之间的通信。

它们之间需要用message通信:

// popup.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    console.log("收到消息:", request.data);
});

// content.js
chrome.runtime.sendMessage({data: 'value'}, function(response) {
    console.log("收到来自popup.js的回复:", response);
});