谷歌插件开发:手把手教你从零开发谷歌浏览器Monica插件+ChatGPT

758 阅读7分钟

前言

谷歌插件是一种可在谷歌浏览器中添加的小型程序,它可以为用户提供各种功能和服务。其中一些最受欢迎的插件包括广告拦截器、密码管理器、翻译工具、屏幕截图工具和社交媒体管理器等。

作者在chrome应用市场中发现一款特别好的插件“Monica”,能够提高开发人员的工作效率,在chrome应用市场上拥有600,000+用户,Monica是ChatGPT在网页上的应用,换句话说,Monica就是靠着ChatGPT API的强大功能才厉害。此篇文章就让作者手把手教你从0到1开发谷歌浏览器插件吧!

1、 初始化项目

1.1 使用create-react-app 搭建新项目

npx create-react-app chrome-plugin

chrome-plugin是创建的项目名称。

1.2 精简项目

 ├─ node_modules
 ├─ public
 |  ├─ favicon.ico
 |  ├─ index.html
 |  ├─ mainfest.json
 ├─ src-
 |  ├─ App.css
 |  ├─ App.js
 ├─ .gitignore
 ├─ package.json
 ├─ README.md
 └─ yarn.lock

1.3 引入ant Design

1.3.1 安装

    yarn add antd

1.3.2 引入组件

引入你所需要的组件如Button

import { Button } from 'antd';

1.3.3引入组件样式

在公共的文件引入,如App.js

import 'antd/dist/antd.css'

1.3.4 使用antd的Button组件

<Button type="primary">Primary Button</Button>

1.4 依赖包版本

Node.js 16.13.2
create-react-app  5.0.0
react 17.0.2
react-router-dom 6.2.1
antd 4.18.4
react-markdown 8.0.7
event-source-polyfill 1.0.31
marked 5.0.2 
react-shadow 20.0.0 

2、 Chrome插件开发基础

2.1 基础知识

image.png

2.2 Chrome Extension的组成

主要由以下部分组成:

  1. manifest.json (插件配置文件)

  2. popup (点击插件图标弹出的页面)

  3. content script (插入到目标页面中执行的js)

  4. background script (在chrome后台Service Workers中运行的程序)

manifest.json

manifest.json必须放在插件项目根目录,里面包含了插件的各种配置信息,其中也包括了popup、content script、background script等路径。

popup

作为一个独立的弹出页面,有自己的html、css、js,可以按照常规项目来开发。

content script

content script是注入到目标页面中执行的js脚本,可以获取目标页面的Dom并进行修改。但是,content script的JavaScript与目标页面是互相隔离的。也就是说,content script与目标页面的JavaScript不会出现互相污染的问题,同时,也不能调用对方的方法。

注意,以上只是js作用域的隔离,通过content script向目标页面加入的DOM是可以应用目标页面的css,从而造成css互相污染。

background script

background script 常驻在浏览器后台Service Workers运行,没有实际页面。一般把全局的、需要一直运行的代码放在这里。重要的是,background script的权限非常高,除了可以调用几乎所有Chrome Extension API外,还可以发起跨域请求。

2.3 谷歌插件的目录结构如下:

  • manifest.json:插件的清单文件,包含插件的基本信息、权限、入口等信息。
  • _locales/ :存放插件的多语言翻译文件。
  • icons/ :存放插件的图标文件,包括插件在浏览器工具栏中的图标、在扩展管理页面中的图标等。
  • scripts/ :存放插件的脚本文件,包括后台脚本、内容脚本等。
  • styles/ :存放插件的样式文件。
  • pages/ :存放插件的页面文件,包括选项页面、弹出页面等。
  • lib/ :存放插件的第三方库文件。
  • tests/ :存放插件的测试文件。
  • README.md:插件的说明文档。

2.4 manifest.json的参数列表

  • name:扩展程序的名称
  • version:扩展程序的版本号
  • description:扩展程序的描述信息
  • manifest_version:manifest 文件的版本号,当前版本为 2
  • icons:扩展程序的图标
  • browser_action:浏览器图标点击时弹出的浏览器操作界面
  • page_action:浏览器图标点击时弹出的页面操作界面
  • background:扩展程序后台脚本
  • content_scripts:扩展程序注入到网页中的脚本
  • permissions:扩展程序需要的权限
  • web_accessible_resources:扩展程序可以访问的网页资源
  • options_page:扩展程序的选项页面
  • options_ui:扩展程序的选项界面
  • default_locale:扩展程序的默认语言
  • chrome_settings_overrides:覆盖 Chrome 浏览器的设置
  • minimum_chrome_version:扩展程序所需的最低 Chrome 浏览器版本号
{
  "name": "plugin",
  "version": "1.0",
  "description": "谷歌插件,基于chatgpt开发。",
  "manifest_version": 3,
  "background": {
    "service_worker": "static/js/background.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["static/js/content.js"],
      "run_at": "document_end"
    }
  ],
  "permissions": ["storage", "declarativeContent","tabs", "activeTab", "scripting", "commands"],
  "host_permissions": [],
  "web_accessible_resources": [
    {
      "resources": ["images/*.png"],
      "matches": ["<all_urls>"]
    },
    {
      "resources": ["static/css/*.css"],
      "matches": ["<all_urls>"]
    },
    {
      "resources": ["*.html"],
      "matches": ["<all_urls>"]
    }
  ],
  "action": {
    "default_icon": {
      "16": "images/icon16.png",
      "48": "images/icon48.png",
      "128": "images/icon128.png"
    },
    "default_title": "React CRX MV3"
  },
  "icons": {
    "16": "images/icon16.png",
    "48": "images/icon48.png",
    "128": "images/icon128.png"
  },
  "commands": {
    "_execute_action": {
      "suggested_key": { "default": "Ctrl+B", "mac": "Command+B" }
    }
  }
}

2.5 谷歌插件permissions的参数说明

  • activeTab:可以访问当前激活的选项卡的信息,包括URL、标题和图标等。常用于执行与当前选项卡相关的操作。
  • storage:可以访问插件本地存储空间,可以用来存储和读取插件的数据,如设置项、用户偏好等。
  • tabs:可以访问所有选项卡的信息,包括URL、标题、图标、ID等。常用于操作选项卡,如打开、关闭、切换等。
  • webRequest:可以拦截、修改和阻止网络请求,常用于实现广告屏蔽、安全防护等功能。
  • webNavigation:可以监控浏览器的导航事件,如页面跳转、前进后退等,常用于实现特定页面的自动化操作。
  • contextMenus:可以添加右键菜单,常用于提供快捷操作和扩展功能。
  • notifications:可以弹出桌面通知,常用于提醒用户、显示状态等。
  • cookies:可以访问浏览器的Cookie,常用于实现自动登录等功能。
  • identity:可以实现OAuth2认证,常用于与第三方服务进行交互。
  • unlimitedStorage:可以拥有无限制的存储空间,常用于需要存储大量数据的插件。
  • bookmarks:可以访问和操作浏览器的书签,常用于实现书签管理等功能。
  • history:可以访问和操作浏览器的历史记录,常用于实现历史记录管理等功能。

3 项目架构设计

3.1 工程目录结构

项目的基本结构如下,主要是在src目录下创建background、content、popup文件夹,在public目录下面创建manifest.json文件。

 ├─ /config                  <--配置目录(由eject生成) `
 ├─ /public                  <--popup入口页面`
 | ├─ /images                <--图片目录`
 | | ├─ icon.png             <--插件图标`
 | ├─ favicon.ico            <--这个没有也行,用不到`
 | ├─ index.html             <--popup入口页面`
 | ├─ insert.js              <--插入到目标页面执行的js(非必须,视业务需求而定)`
 | ├─ manifest.json          <--插件的配置文件`
 ├─ /scripts                 <--项目构建运行脚本(由eject生成)`
 ├─ /src                     <--开发目录`
 | ├─ /api                   <--API公用目录`
 | | ├─ index.js`
 | ├─ /background            <--background script开发目录`
 | | ├─ index.js`
 | ├─ /common                <--公用资源目录`
 | | ├─ /js                  <--公用js目录`
 | | └─ /stylus              <--公用样式目录(本demo使用stylus)`
 | ├─ /content               <--content script开发目录`
 | | ├─ /components          <--content 组件目录`
 | | ├─ /images              <--content 图片目录`
 | | ├─ content.styl         <--content 样式`
 | | └─ index.js             <--content script主文件`
 | ├─ /popup                 <--popup开发目录`
 | | ├─ /pages               <--popup 页面目录`
 | | ├─ /components          <--popup 组件目录`
 | | ├─ index.js             <--popup 主文件`
 | ├─ index.js               <--项目主文件,也是popup入口文件`
 ├─ pakeage.json`

3.2 暴露webpack配置

项目要根据Chrome Extension格式打包出对应的文件。因此打出来的包要包含popup、background script、content script,所以需要对webpack进行配置。

在create-react-app创建项目时,webpack配置原本是隐藏的需要执行以下命令

npm run eject

执行完上述命令后,会在项目根目录下生成一个 config 文件夹,其中的 webpack.config.js文件就是webpack的配置文件。需要注意的是,一旦执行了 eject命令,就无法回退到原来的状态,所以在修改之前需要慎重考虑。

3.3 webpack.config.js配置整改

需要修改entry和output,如下:

    entry: {
      main: isEnvDevelopment && !shouldUseReactRefresh
          ? [webpackDevClientEntry,paths.appIndexJs] : paths.appIndexJs,
      content: './src/content/index.js',  // 谷歌插件需要用到的centent.js
      background: './src/background/index.js' // 谷歌插件需要用到的background.js
    },
    output: {
      path: paths.appBuild, // 打包后的文件夹名称
      pathinfo: isEnvDevelopment,
      filename: isEnvProduction
        ? 'static/js/[name].js'
        : isEnvDevelopment && 'static/js/[name].bundle.js',
      chunkFilename: isEnvProduction
        ? 'static/js/[name].chunk.js'
        : isEnvDevelopment && 'static/js/[name].chunk.js',
      assetModuleFilename: 'static/media/[name].[hash][ext]',
      publicPath: paths.publicUrlOrPath,
      devtoolModuleFilenameTemplate: isEnvProduction
        ? info =>
            path
              .relative(paths.appSrc, info.absoluteResourcePath)
              .replace(/\\/g, '/')
        : isEnvDevelopment &&
          (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
    },

打包完之后,会在static/js目录下面创建centent.js和background.js文件。如图:

image.png

4 content script开发

Content script是一种可以注入到网页中的JavaScript脚本。它可以与页面上的元素进行交互。我们可以通过Content script注入chatgpt的聊天框。如图:

image.png

4 向浏览器页面注入react应用。

4.1 创建react挂载点

在content文件夹下面创建index.js文件,并且在文件中创建挂载点。如图:

import ReactDOM from "react-dom";
// 创建id为sino-container的div
const app = document.createElement("div");
app.id = "my-container";

function Content() {
    return <div>hello world</div>;
}

// 将刚创建的div插入body最后
document.body.appendChild(app);
// 将ReactDOM插入刚创建的div
ReactDOM.render(<Content />, app);

Content组件就是我们需要开发的模块

image.png

4.2 开发chatgpt应用

在content文件夹下面创建components组件库

image.png

4.3 使用Shadow DOM开发

Content script是注入到网页中的JavaScript脚本,样式必须要全局注入到页面中,这样会导致页面的样式污染。为了解决这个问题,我们采用了shadow DOM。

Shadow DOM 是一种 Web API,用于将 DOM 树的一部分隐藏起来,使其不受外部 CSS 样式的影响,并且可以防止外部 JavaScript 访问其内部。可以创建独立的、封闭的组件,使其可以更好地维护和重用。在开发过程中,可以使用 Shadow DOM 创建自定义元素,然后将其插入到页面中,从而实现更高效、可靠的页面构建。

在react中用到react-shadow,安装下:

  npm install react-shadow@20.0.0

在代码中使用

import ReactDOM from "react-dom";
import root from "react-shadow";
// 创建id为sino-container的div
const app = document.createElement("div");
app.id = "my-container";

function Content() {
return <root.div>hello world</root.div>;
}

// 将刚创建的div插入body最后
document.body.appendChild(app);
// 将ReactDOM插入刚创建的div
ReactDOM.render(<Content />, app);

效果如图:

image.png

4.3.1 向shadow DOM中注入Style

使用chrome.runtime.getURL(xxx)获取文件资源地址,再注入到标签中。

let style;
try {
fetch(chrome.runtime.getURL("static/css/content.css")) // 打包后的样式文件地址
.then((response) => response.text())
.then((fileContent) => {
style = fileContent;
});
} catch (error) {}

function Content() {
return (
<root.div> <style type="text/css">{styleData}</style>
hello world
</root.div>
);
}