🚀工程化实战——搭建一个浏览器插件开发脚手架🚀

654 阅读5分钟

前言

最近在搞一个 chrome 插件,主要开发的是 content script 往站点插入一些 dom 的功能。开发过此类功能的同学可能会清楚,如果你用纯 JS 来做这件事情的话是比较麻烦的,当然你也可以选择 jquery

我作为一个 React+Antd 的重度患者,肯定是希望以自己最为舒适的方式去做开发。所以就想着自己去搭建一个简单的脚手架,检验一下自己的工程化能力。

PS:这里的浏览器插件指的是 Chrome 插件,下文就不再做解释。

插件简述

先来看看一个浏览器插件原生的工程目录下会包含什么东西:

image.png

manifest.json

Chrome 扩展的配置文件,用来描述扩展的基本信息和配置。以下是一个实例:

{
  "manifest_version": 3,
  "name": "My Chrome Extension",
  "version": "1.0",
  "description": "A simple Chrome extension.",
  "permissions": ["storage", "activeTab", "scripting"],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ]
}

  • manifest_version 指定清单文件的版本,当前版本为3。
  • name 扩展的名称。
  • version 扩展的版本号。
  • description 扩展的描述信息。
  • permissions 扩展所需的权限,例如访问存储、当前标签页等。
  • background 配置后台脚本,在manifest_version 3中,使用service_worker指定后台脚本文件。
  • action 配置扩展的操作,包括默认的弹出页面和图标。
    • default_popup:指定用户点击扩展图标时显示的HTML文件。
    • default_icon:指定扩展图标的不同尺寸。
  • icons 配置扩展的图标,提供16x16、48x48和128x128像素的图标。
  • content_scripts 配置内容脚本。
    • matches:指定内容脚本将注入哪些页面,这里使用<all_urls>表示所有页面。
    • js:指定要注入的内容脚本文件。

content.js

在网页的上下文中运行,能够与网页的DOM和JavaScript交互。可以访问和操作页面上的元素、修改样式、响应用户事件等。

alert('这是contentjs')

image.png

background.js

Chrome 扩展的后台持续运行的脚本。它不直接与网页交互,而是负责处理全局的任务和逻辑。处理长时间运行的任务,如监听浏览器事件、管理扩展状态、执行定时任务等。

chrome.runtime.onInstalled.addListener(() => {
  console.log('Extension installed');
});

image.png

popup

在用户点击扩展图标时显示的HTML页面。它通常用作扩展的用户界面。主要用作用户与扩展交互的界面,提供按钮、表单、信息显示等。

<!DOCTYPE html>
<html>
<head>
  <title>My Extension</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <h1>Hello, Chrome Extension!</h1>
  <button id="action-button">Click me</button>
  <script src="popup.js"></script>
</body>
</html>

document.getElementById("action-button").addEventListener("click", () => {
  alert(1);
});

image.png

Content Script

OK,那我们现在第一步就先来实现:用 React+Antd 来写内容脚本。

首先把该安装的依赖先装一遍:

image.png

然后把图标、 manifest.json 这种配置的东西放到 public 目录下,后续打包通过一个 copy 插件拷贝到产物文件夹中:

image.png

然后新建一个 config 文件夹,用来放 webpack 配置:

image.png

主要的配置都在 common 中,其他两个是根据不同的环境做一些不同的策略而已。

这里主要贴一下 webpack.common.js 的配置:

const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");

module.exports = {
  entry: {
    content: path.resolve(__dirname, "../src/content/index.js"),
  },
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "../dist"),
    clean: true,
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: "babel-loader",
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.less$/,
        use: [
          "style-loader",
          "css-loader",
          {
            loader: "less-loader",
            options: {
              lessOptions: {
                javascriptEnabled: true,
              },
            },
          },
        ],
      },
    ],
  },
  resolve: {
    extensions: [".js", ".jsx"],
  },
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, "../public"),
          to: path.resolve(__dirname, "../dist"),
        },
      ],
    }),
  ],
};

先看 entry ,我们现在主要关注内容脚本,所以有一个 content 入口,入口文件对应 src/content/index.js

rules 中就是一些 loader ,这里就不展开, plugins 中主要是一个拷贝插件。

这里再顺便贴一下其他两个配置文件:

image.png

image.png

下面主要来看 src/content/index.js

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const div = document.createElement("div");

document.body.appendChild(div);
ReactDOM.createRoot(div).render(<App />);

首先引入一个 App 组件,这就是一个 React 组件,然后通过 ReactDOM ,把它渲染到 dom 上。注意 createElement 产生的div标签,我们的 React 组件最后会作用到这个 div 上。

所以最终渲染出来的 dom 节点,你想挂在页面的哪个元素下,你就把这个 div 追加到哪个元素下,这里我直接放到了 body 下。

最后看一下 App.jsx ,就是一段平平无奇的 React 代码了

import { Button } from "antd"
import React from "react"
const App = () => {
  return (
    <div>
      <Button onClick={()=>{
        alert('🐔')
      }} type="primary">插件插入的按钮</Button>
    </div>
  )
}

export default App

然后在 package.json 中加入下面两条命令:

image.png

无论是开发环境打包还是生产打包,都会有一个 dist 目录,安装插件时,直接安装这个 dist 目录即可。

最后看看效果:

image.png

Background

有了上面的基础之后,我们再来打包一个 background.js 就已经轻车熟路了。

首先 manifest.json 中加入配置:

image.png

然后 webpack 打包中加一个入口:

image.png

尝试修改一下 background.js

import { isEmpty } from "lodash";
chrome.runtime.onInstalled.addListener(() => {
  console.log("这里是background", isEmpty([1, 2, 3]));
});

发现打包也是没问题的。

image.png

Popup

最后再来打包一个 popup 页面,先来把 manifest.json 的配置加上:

image.png

打包这个 popup 页面你可以理解为跟你平时打包一个 ReactSPA 应用一样。

image.png

首先是一个 html 模版文件,然后是一个 index.js 入口:

image.png

最后是一个 App 组件:

image.png

webpack 的配置需要注意:

先加一个入口:

image.png

然后看到了一个熟悉的插件:

image.png

filename 就是输出的文件,这个需要对应 manifest.json 的配置; chunks 就是这个插件需要帮你自动引入哪个打包后的 js ,由于我们上面入口配置的是 popup ,所以这里写 popup

最后大功告成:

image.png

最后

对我来说,这是一次好玩的工程化实战体验。重温了一次 webpack+react 的实战配置,如果你用的是 Vue ,那其实也大差不差。你要搞清楚的是, webpack 的打包入口在哪里,打出来的产物你希望是什么,就可以了。

希望对你有帮助,如果觉得有意思的话,点点关注点点赞吧~