chrome插件从0到1

记录一下自己从零开始构建一个插件的过程~
我这个插件的逻辑主要是向目标页面注入一个icon,

1. 创建一个react项目

首先官网更新,不再提供全局安装的create-react-app,使用

npx create-react-app@latest yourProjectName --template typescript --use-npm

安装成功后,将不需要的文件给删除掉,更改manifest.json这个配置文件,当时使用的是V2版本,后续将其更新为V3版本,主要是chrome浏览器在2023年会将V2版本的给废弃掉,:(
我的配置文件如下,其中详细的介绍可以查阅这篇文章:chrome插件开发攻略

{
    // 清单文件的版本,这个必须写,而且必须是2
    "manifest_version": 2,
    // 插件的名称
    "name": "demo",
    // 插件的版本
    "version": "1.0.0",
    // 插件描述
    "description": "简单的Chrome扩展demo",
    // 图标,一般偷懒全部用一个尺寸的也没问题
    "icons":
    {
        "16": "img/icon.png",
        "48": "img/icon.png",
        "128": "img/icon.png"
    },
    // 会一直常驻的后台JS或后台页面
    "background":
    {
        // 2种指定方式,如果指定JS,那么会自动生成一个背景页
        //"page": "background.html"
        "scripts": ["background.js"]
    },
    // 需要直接注入页面的JS
    "content_scripts": 
    [
        {
            //"matches": ["http://*/*", "https://*/*"],
            // "" 表示匹配所有地址
            "matches": [""],
            // 多个JS按顺序注入
            "js": ["content-script.js",icon.js],
            // JS的注入可以随便一点,但是CSS的注意就要千万小心了,因为一不小心就可能影响全局样式
            "css": ["icon.css"],
            // 代码注入的时间,可选值: "document_start", "document_end", or "document_idle",最后一个表示页面空闲时,默认document_idle
            "run_at": "document_end"
        },
    ],
    // 权限申请
    "permissions":
    [
        "contextMenus", // 右键菜单
        "tabs", // 标签
        "activeTab",
        "http://*/*", // 可以通过executeScript或者insertCSS访问的网站
        "https://*/*" // 可以通过executeScript或者insertCSS访问的网站
    ],
    "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
    "externally_connectable":{
      "matches":[
        "*://*.italent-inc.cn/*",
        "*://*.italent.link/*",
        "*://*.italent.cn/*"
      ]
  }
}

先大致知道有这么些配置属性就行,下面一步一步的细化
首先梳理一下backgound与content之间的关系:

  • backgound,它随着浏览器的打开而打开,随着浏览器的关闭而关闭,所以通常把需要一直运行的、启动就运行的、全局的代码放在background里面,而且它可以无限跨域
  • Chrome插件中向页面注入脚本的一种形式,特定无法访问页面中的JS,虽然它可以操作DOM,但是DOM却不能调用它,幸运的是可以通过注入脚本的形式实现向页面添加dom元素
  • 由于content scripts运行在Web页面的上下文中,属于Web页面的组成部分,而不是Google Chrome扩展程序。但是content scripts又往往需要与Google Chrome扩展程序的其他部分通信以共享数据,这通过消息传递实现

    2. 创建backgound.js文件

    我的逻辑就主要是监听页面刷新,就在content去掉用我的方法

//监听页面刷新
chrome.tabs.onUpdated.addListener(async function (tabId, message, option) {
  //防止加载中和加载完成执行2次
  if(message.status !== 'complete' || option.status !== 'complete'){
    return
  }
  chrome.tabs.sendMessage(tabId,{
    type:"initIcon",
    taburl:option.url // 配置信息需要增加activeTab的权限
  })
})

3. 创建content.js

//content接受bg消息
chrome.runtime.onMessage.addListener(async function (msg) {
  if (msg.type === "initIcon") {
    const urlStorage = window.localStorage.getItem('iframeUrl')
    // 以下是我的业务逻辑,就不多描述了
    if(urlStorage){
      //直接使用域名判断
      if(isShow()){
        render()
      }else{
        console.log('remove');
        window.localStorage.removeItem('iframeUrl')
        destroy()
      }
    }else{
      destroy()
    }
  }
});

值得一提的就是render函数就是上面我提到的通过注入脚本的形式实现向页面添加dom元素

const render = function (){
  const url = window.localStorage.getItem('iframeUrl')
  const script = document.createElement('script');
  script.src = chrome.runtime.getURL('icon.js');
  document.documentElement.appendChild(script);
}

这里也看到了我引入icon.js这个脚本,

const App = () => {

    // 省略掉不重要的

  return (
    
setDrawerVisible(true)} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
); }; // 在Chrome扩展程序中将React Component注入页面 const mountNode = document.createElement("div"); mountNode.id = 'mapNode' document.body.appendChild(mountNode); ReactDom.render(, mountNode);

值得一提的是,通过这个方法注入的icon,会在主页面的底部,可以在chrome控制台查看搜索相关内容查看是否注入成功,注入成功后却没有显示,使用了position: fixed;
z-index: 9999这两行代码来显示。
PS我觉得初学chrome插件最难的就是各种路径....:(
此时我的项目 目录结构是这样的
chrome插件从0到1_第1张图片
值得一提的是 我在package.json加了这条命令

"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    // 这条命令
    "build-chrome-ext": "react-scripts build && cp src/js/background.js build/background.js && cp src/js/content.js build/content.js && cp build/static/js/main.***.js build/icon.js && cp build/static/css/main.***.css build/icon.css "
  },

其中index.js文件就是该项目的入口文件,因此此时的命令就是将编译后的文件打包build/static/js/main.*.js

此时执行npm run build-chrome-ext, 然后chrome浏览器加载解压的build文件,一个插件就完成啦

优化:

  1. V2 --> V3
    此时将配置文件更改为
    V2到V3的扩展迁移,可以参考此篇文章chrome插件从V2到V3的迁移
{
  "short_name": "实施地图",
  "name": "实施地图插件",
  "manifest_version": 3,
  "content_security_policy": {
    "extension_pages": "script-src 'self' ; object-src 'self'"
   },
  "version": "1.0",
  "background":{
    "service_worker": "background.js"
  },
  "icons":
    {
        "16": "img/icon.png",
        "48": "img/icon.png",
        "128": "img/icon.png"
    },
  "content_scripts": [ {
    "js": [ "content.js","icon.js" ],
    "css": ["icon.css"],
    "matches": [ 
      "*://*.italent-inc.cn/*",
      "*://*.italent.link/*",
      "*://*.italent.cn/*" ],
    "run_at": "document_end"
  } ],
  "permissions":[
    "tabs",
    "activeTab",
    "storage"
  ],
  "host_permissions":[
    "http://*/*",
    "https://*/*"
  ],
  "externally_connectable":{
    "matches":[
      "*://*.italent-inc.cn/*",
      "*://*.italent.link/*",
      "*://*.italent.cn/*"
    ]
  }
}
  1. 打包命令优化
    那一长串打包命令的确有些丑陋,可以做成这种: "react-scripts build && node xxx.js",然后把文件操作写到xxx.js里,就是写个shell脚本来专门做文件的移动和拷贝,不一定是shell,喜欢哪个用哪个

当然 我的这个插件功能很简单,还有很多没有考虑周到的地方,后续有机会继续跟进学习~

你可能感兴趣的:(chrome插件从0到1)