Vue3 + Typescript 开发浏览器插件

3,156 阅读4分钟

前言

建议先去看源码,再来看文章, 仅有前端代码, 后端接口没写,别问问就是懒……

源码

源码

源码

因为群主乐于分享,有时候把网页添加到书签之后,系统重装啊 浏览器卸载呐……各种杂七杂八的问题,这样导致书签全没了(主要是懒)

索性就写个浏览器插件,当看到好文章的时候也可以保存至服务器,也方便分享给团队成员。

目的明确 浏览器插件, 在查相关资料得时候想起来以前看过一篇政采云的浏览器插件文章。

手把手教你打造属于自己团队的前端小报系统

仿“政采云前端小报”系统新鲜出炉了

好家伙天助我也……政采云给大致方案,这个大兄弟直接把代码写完了……

不过这个大兄弟的代码是vue2的,所以咱们就用vue3 + typescript 实现一个自己……

image.png

项目配置

项目创建这里就略过了……

配置项目

项目创建完成了,咱们就要去装点npm包来开发了。

copy-webpack-plugin 拷贝文件

@types/chrome 谷歌浏览器类型

  1. 修改 tsconfig.js

    ……
    "types": [
        "webpack-env"
        // 添加 chrome 后面要用到。
        "chrome"  
    ],
    ……
    
  2. 创建了vue.config.js (可以先把代码注释一下,因为现在还用不到)

    const CopyWebpackPlugin = require("copy-webpack-plugin");
    const path = require("path");
    
    module.exports = {
      configureWebpack: {
        plugins: [
          new CopyWebpackPlugin({
            patterns: [
              {
                from: path.resolve("src/plugins/plugin-chrome.js"),
                to: path.resolve("dist/js")
              }
            ]
          })
        ]
      }
    };
    
  3. 在public下创建 manifest.json

    {
      "manifest_version": 2,
      "name": "galaxy-browser-extends",
      "homepage_url": "http://localhost:8080/",
      "description": "用于分享的浏览器插件",
      "permissions": ["<all_urls>", "*://*/*", "notifications", "webRequest"],
      "icons": {
        "16": "icons/16.png",
        "48": "icons/48.png",
        "128": "icons/128.png"
      },
      "browser_action": {
        "default_popup": "popup.html",
        "default_title": "galaxy-browser-extends",
        "default_icon": {
          "19": "icons/19.png",
          "38": "icons/38.png"
        }
      },
      "version": "0.1.0",
      "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
    }
    
    

    public 文件夹下创建icon, 再放入几张png。

  4. 修改下package.json 中的server 启动命令,方便于我们开发。

    "serve": "vue-cli-service build --mode development --watch"
    

那么我们执行下 yarn serve, 安装插件。

使用插件

访问 chrome://extensions/, 打开开发者模式,点击 加载已解压的扩展程序, 选择项目目录下dist。不出问题的话可以看到插件加载成功了。

image.png

image.png

没啥问题能用……

manifest.json

在开始写功能之前我们先了解下一些前置知识。

manifest.json详情见

每一个扩展、可安装的WebApp、皮肤,都有一个JSON格式的manifest文件,叫 manifest.json,里面提供了重要的信息。

{
  "manifest_version": 2,
  "name": "galaxy-browser-extends",
  "homepage_url": "http://localhost:8080/",
  "description": "用于分享的浏览器插件",
  "permissions": ["<all_urls>", "*://*/*", "notifications", "webRequest"],
  "icons": {
    "16": "icons/16.png",
    "48": "icons/48.png",
    "128": "icons/128.png"
  },
  "browser_action": {
    "default_popup": "popup.html",
    "default_title": "galaxy-browser-extends",
    "default_icon": {
      "19": "icons/19.png",
      "38": "icons/38.png"
    }
  },
  "version": "0.1.0",
  "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
}

permissions:插件用到的浏览器的权限. MDN 插件API概览

icons: 图标 16px 48px 128px

browser_action: browser actions 可以在浏览器工具条的地址栏右边增加一个图标。 详细介绍

version: 插件版本

content_security_policy: 因为我们是用webpack打包的,所以要运行eval MDN

交互

我们主要的功能的功能就是获取到web页面的 title, url, description

那么我们要怎么才能拿到这些信息呢?

首先我们要获取到当前web页面的tabId, 并向这个页面注入js。注入了js,我们的插件还是没有办法直接跟当前的web 页面交互,还需要一步就是通过Message 传递消息。 消息传递Message

/**
 * 获取当前页得tabid
 * @returns
 */
export const ChromTabsQuery = (): Promise<number | any> => {
  return new Promise((resolve, reject) => {
    chrome.tabs.query(
      {
        active: true,
        currentWindow: true
      },
      (tabs: Array<chrome.tabs.Tab>): void => {
        tabs.length ? resolve(tabs[0].id) : reject(null);
      }
    );
  });
};

/**
 * 向浏览器注入脚本
 * @param tabId
 * @param path
 * @returns
 */
export const ChromeTabsExecuteScript = (tabId: number, path: string) => {
  return new Promise(resolve => {
    chrome.tabs.executeScript(tabId, { file: path }, response => {
      resolve(response);
    });
  });
};

/**
 * 向浏览器发送Message
 * @param tabId
 * @param message
 * @returns
 */
export const ChromeTabsSendMessage = (
  tabId: number,
  message: {}
): Promise<{
  title: string;
  link: string;
  description: string;
}> => {
  return new Promise(resolve => {
    chrome.tabs.sendMessage(tabId, message, response => resolve(response));
  });
};

要被注入的js

/* eslint-disable no-undef */
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  if (request.message === "GET_TOPIC_INFO") {
    // 获取 title
    const title = document.getElementsByTagName("title")[0].textContent;
    const descriptionEl = document.querySelectorAll("meta[name=description]")[0];
    // 获取 描述
    const description = descriptionEl ? descriptionEl.getAttribute("content") : title;
    // 发送数据
    sendResponse({
      title: title.trim(),
      link: location.href,
      description: description.trim()
    });
  }
});

当我们想要获取Web页面信息的时候, 先获取到 tabId, 通过 tabId给相应的Web注入脚本。我们发送一条Message告诉脚本,脚本也会返回一条Message 给我们。

const tabId = await ChromTabsQuery();
const status = await ChromeTabsExecuteScript(tabId, "./js/plugin-chrome.js");
if (status) {
    const result = await ChromeTabsSendMessage(tabId, {
      message: "GET_TOPIC_INFO"
    });
    const { title, description, link } = result;
    formData.title = title;
    formData.description = description;
    formData.link = link;
}

完结

可能讲的比较快,一些细节没有讲到。浏览器插件这块东西真的是非常多,而已很多资料都是英文的。中文的资料真的不是太多,主要是写浏览器插件的人相对较少。