使用plasma 开发谷歌插件

4,131 阅读5分钟

因为公司的所有系统都挂在一个门户网站上,通过token带入登陆。但是在开发的时候就没有办法带过来了,所以开发了一个小插件来解决这个问题。

这边我是使用的plasma + react 来进行开发这个插件

插件组成

chrome 插件通常由以下几部分组成:

  1. manifest.json:相当于插件的 meta 信息,包含插件的名称、版本号、图标、脚本文件名称等,这个文件是每个插件都必须提供的,其他几部分都是可选的。
  2. background script:可以调用全部的 chrome 插件 API,实现跨域请求、网页截屏、弹出 chrome 通知消息等功能。相当于在一个隐藏的浏览器页面内默默运行。
  3. 功能页面:包括点击插件图标弹出的页面(简称 popup)、插件的配置页面(简称 options)。
  4. content script:早期也被称为 injected script,是插件注入到页面的脚本,但是不会体现在页面 DOM 结构里。content script 可以操作 DOM,但是它和页面其他的脚本是隔离的,访问不到其他脚本定义的变量、函数等,相当于运行在单独的沙盒里。content script 可以调用有限的 chrome 插件 API,网络请求收到同源策略限制。

Background Script

是一个常驻后台,是所有生命周期插件中最长的一个。随着浏览器的打开而打开,随着浏览器的关闭而关闭,将一些一直需要运行,全局代理代码放在backgroud里面。

Content Script

下文简称 content,它只能使用有限的 chrome API。

由于 content 可以访问 DOM,可以用它来选择、修改、删除、增加网页元素。

但是 content 是运行在隔离的空间(类似沙盒),所以如果需要和页面的其他脚本通信,需要采用 window.postMessage 的方式。

可以获取window下面的方法。

export const config = {
  matches: ["http://127.0.0.1/*","http://localhost/*"],
  world: "MAIN"
}
if (window) {
  
  window.addEventListener('beforeunload', function (e) {
    if (e.currentTarget.performance.navigation.type === 1) {
      // 页面刷新
    } else {
      e.preventDefault();
    }
  });
}

plasma

是一个插件开发的框架

docs.plasmo.com/framework#h…

官方示例:

github.com/PlasmoHQ/ex…

系统版本需要

  • Node.js 16.14.x or later
  • macOS, Windows, or Linux
  • 建议使用 pnpm

如何开始

pnpm create plasmo
# OR
yarn create plasmo
# OR
npm create plasmo

使用pnpm 和 npm 我这边一直请求超时,所以我用的cnpm也是可以运行的

loading

目录结构

loading

文件名描述
popup.tsx该文件导出默认的 React 组件,该组件会渲染到您弹出的页面中。这就是您在扩展弹出窗口上工作所需的全部内容
assetsPlasmo 会自动生成一些小图标并将它们从'icon512.png' 文件配置到manifest
package.json常用的 Node.js 项目描述符
.prettierrc.cjs配置代码格式化
.gitignoregit 忽略文件
readme.mdREADEME 文件
tsconfig.jsonTypeScript 配置文件

开发构建

// 构建开发环境 有热更新 
pnpm dev 

生成构建

// 生成环境构建
pnpm build 

构建的包

loading

如何本地加载调试

loading

loading

Untitled (2).png

Untitled.png

Untitled (1).png

package.json的配置

{
  "name": "login",
  "displayName": "Login",
  "version": "0.0.1",
  "description": "login fjf admin",
  "author": "hemoo",
  "scripts": {
    "dev": "plasmo dev",
    "build": "plasmo build",
    "test": "plasmo test"
  },
  "dependencies": {
    "@radix-ui/themes": "^2.0.0",
    "plasmo": "0.83.0",
    "react": "18.2.0",
    "react-dom": "18.2.0"
  },
  "devDependencies": {
    "@ianvs/prettier-plugin-sort-imports": "4.1.0",
    "@types/chrome": "0.0.245",
    "@types/node": "20.5.9",
    "@types/react": "18.2.21",
    "@types/react-dom": "18.2.7",
    "autoprefixer": "^10.4.16",
    "postcss": "^8.4.31",
    "prettier": "3.0.3",
    "tailwindcss": "^3.3.5",
    "typescript": "5.2.2"
  },
  "manifest": {
    "host_permissions": [
      "https://*/*", // 允许在host的权限
      "http://*/*"  // 本地要用这个,不要问为什么 问就是泪和脑子的水
    ],
    "permissions": [ // 需要的权限
      "cookies", // 需要cookie 
      "tabs"  // 为了获取当前地址需要得到这权限
    ]
  }
}

permissions的参数

允许获取的权限

  1. tabs: tab的权限
  2. active:
  3. TabcontextMenus:网页右键菜单,
  4. browser_action 右键菜单
  5. cookies:操作 cookie,和用户登录态相关的功能可能会用到该权限
  6. storage:插件存储,不是 localStorage
  7. web_accessible_resources:网页能访问的插件内部资源,比如插件提供 SDK 给页面使用,如 ethereum 的 metamask 钱包插件。或者是修改 DOM 结构用到了插件的样式、图片、字体等资源。

安装Tailwindcss

自动安装

// 创建的时候就自带tailwindcss
pnpm create plasmo --with-tailwindcss

手动安装

添加依赖项

// 安装依赖性
pnpm i -D tailwindcss postcss autoprefixer

// 初始化tailwindcss 
pnpm tailwindcss init 

定义postcss

/**
 * @type {import('postcss').ProcessOptions}
 */
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {}
  }
}

tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  mode: "jit",
  darkMode: "class",
  content: ["./**/*.tsx"],
  plugins: []
}

style.css

@tailwind base;
@tailwind components;
@tailwind utilities;

在扩展中的使用

popup.tsx

import { useReducer } from "react"
 
import "./style.css" // 需要将style引入,不然会导致tailwind.css 无效
 
function IndexPopup() {
  const [count, increase] = useReducer((c) => c + 1, 0)
 
  return (
    <button
      onClick={() => increase()}
      type="button"
      className="inline-flex items-center px-5 py-2.5 text-sm font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
      Count:
      <span className="inline-flex items-center justify-center w-8 h-4 ml-2 text-xs font-semibold text-blue-800 bg-blue-200 rounded-full">
        {count}
      </span>
    </button>
  )
}
 
export default IndexPopup

前置知识结束

实现代码,代码很简单就是一行

import { useState } from "react"
import { Button } from "@radix-ui/themes"
import '@radix-ui/themes/styles.css';
import "./style.css"


function IndexPopup() {
  function login() {
    chrome.permissions.getAll(
     (res)=>{
      console.log('res: ', res);
     }
    )
    fetch('host', {
      method: 'POST',
      headers: {
        'content-type': 'application/json'
      },
      body: JSON.stringify({
        "mobile": 13666666666,
        "password": "123456",
        "code": "123456"
      })
    }).then(async (response: any) => {
      response.json().then((res) => {
        if (res.code === 0) {
					// 获取当前tab的网址
          chrome.tabs.query({
            active: true,
            currentWindow: true
          }, function (tabs) {
            if (tabs.length > 0) {
              const currentTab = tabs[0];
							 // 获取当前url的cookies权限
              chrome.permissions.request({
                permissions: ['cookies'],
                origins: [currentTab.url]
              }, function (granted) {
                console.log('granted: ', granted);
                if (granted) {
                  // 权限已被授予,可以执行访问 Cookie 的操作
                  const cookie = {
                    url: currentTab.url,
                    name: "token",
                    value: res.data
                  };
                  chrome.cookies.set(cookie, function (cookie) {
                    alert("添加cookie成功")
                  });
                } else {
                  alert("权限被关闭")
                  // 用户拒绝了权限请求,无法访问 Cookie
                }
              });
            }
          });
        }
      })

    })
  }

  return (
    <div
      className="flex flex-col p-2 w-24 border-r-4">
      <Button  className="cursor-pointer" onClick={login}>登录</Button>
    </div>
  )
}

export default IndexPopup

参考资料

掘金-字节跳动plasmo文章

plasmo官方文档

demo地址