2023新春版:React+Antd开发Chrome插件教程(Manifest V3)

lxf2023-05-10 02:37:02

2023新春版:React+Antd开发Chrome插件教程(Manifest V3)

Chrome浏览器插件(Chrome Extension)大家已经非常熟悉了。Chrome Extension经过了十几年的发展,其技术模式已经非常成熟了。目前的Chrome Extension开发应该按照Manifest V3的规范。按照谷歌官方的通知,2023年6月开始,将不允许发布Manifest V2的Chrome Extension,预计到2024年将全面下架Manifest V2的Chrome Extension。Manifest V2即将退出历史舞台,因此,本系列教程将不再提及Manifest V2,全部为Manifest V3(简称MV3)内容。

在2022年1月23日,我发布了《2022新春版:React+Antd开发chrome插件教程(Manifest V3)》。到再次更新本教程的这一年的时间,Chrome Extension官方开发指南也陆陆续续有一些小更新,对日常的开发需求来说,没有什么影响。但这一年里,本教程所涉及的React、Antd等前端库都发生了较大变化,因此2023新春版将按照当前最新的技术栈再次更新讲解。希望能够帮助各位省去摸索的时间,少走弯路,快速完成项目开发。

先睹为快

先看下目录了解本教程都有哪些内容。强烈建议按照以下章节一步一步边学边做,大约40分钟的时间,就可以快速掌握整个项目的原理和细节。这样在以后遇到新问题的时候,可以知道从哪个环节入手。

1 初始化项目
• 1.1 使用create-react-app新建项目
• 1.2 精简项目
2 Chrome Extension基础
• 2.1 Manifest V3概述
• 2.2 Manifest V3 主要新特性2.3 Chrome Extension的组成
• 2.4 规划build生成的目录结构
• 2.5 配置manifest.json
3 项目目录结构设计
4 Webpack配置
• 4.1 配置国内镜像源
• 4.2 暴露Webpack
• 4.3 支持Sass/Scss
• 4.4 支持Less
• 4.5 支持Stylus
• 4.6 设置路径别名
• 4.7 禁止build项目生成map文件
• 4.8 设置多入口
• 4.9 固定build生成的文件名
• 4.10 设置popup只引入自己的index.js4.11 设置全局公用样式
• 4.12 初始化项目架构文件
5 引入Ant Design 5.x5.1 安装Ant Design
• 5.2 设置Antd为中文语言
6 Popup开发
• 6.1 引入popup页面6.2 构建popup的Login页面
• 6.3 构建popup的Home页面
• 6.4 构建popup的Account页面
• 6.5 配置popup页面路由6.6 构建Nav导航组件6.7 构建Entry二级路由框架页面
• 6.8 调整popup入口页面,打通全部路由
• 6.9 完善Login页面的登录跳转
• 6.10 设置popup页面尺寸
7 build项目并载入插件
8 background script开发
• 8.1 设置允许运行popup的页面规则
• 8.2 为什么插件图标在禁用页面不变成灰色
9 content script开发
• 9.1 向目标页面注入悬浮球
• 9.2content script中使用Antd
• 9.3 加载插件自身的静态图片资源(选读)
10 在开发环境中调试content script
11 API请求
• 11.1 background pages不支持XMLHttpRequest(axios)
• 11.2 使用mock.js和mockjs-fetch模拟请求
• 11.3 封装API及fetch业务逻辑
• 11.4 委托background script完成API请求
• 11.5 实现popup的Login页面API请求
• 11.6 设置开发环境的反向代理请求
• 11.7 实现content script的API请求
• 11.8 关键知识点小结
12 其他说明
• 12.1 permission权限配置
• 12.2 以<script>方式向目标页面插入js
• 12.3 Service Worker调试
• 12.4 popup页面调试
• 12.5 精简最终build文件
13 项目Git源码
结束语

本次分享Demo的主要依赖包版本

Node.js 18.14.0

create-react-app 5.0.1

react 18.2.0

react-router-dom 6.8.0

antd 5.1.7

node-sass 8.0.0

sass-loader 12.3.0

less 4.1.3

less-loader 11.1.0

stylus 0.59.0

stylus-loader 7.1.0

mockjs 1.1.0

mockjs-fetch 2.0.0

http-proxy-middleware 2.0.6

※注:代码区域每行开头的:

"+" 表示新增

"-" 表示删除

"M" 表示修改

1 初始化项目

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

找个合适的目录,执行:

npx create-react-app react-crx-2023

命令最后的react-crx-2023是项目的名称,可以自行更改。

编写教程时,create-react-app已经发布了5.0.1,如果一直报错

you are running create-react-app 4.0.3 which is behind the latest release (5.0.1)

说明你还在使用旧版本的create-react-app,需要先清除npx缓存,执行:

npx clear-npx-cache

然后再执行之前的命令创建项目:

npx create-react-app react-crx-2023

稍等片刻即可完成安装。安装完成后,可以使用npm或者yarn启动项目。

进入项目目录,并启动项目:

cd react-crx-2023
yarn start  (或者使用npm start

如果没有安装yarn,可执行以下命令全局安装:

npm install --global yarn

yarn中文网站: yarn.bootcss.com/

启动后,可以通过以下地址访问项目:

http://localhost:3000/

2023新春版:React+Antd开发Chrome插件教程(Manifest V3)

1.2 精简项目

接下来,删除用不到的文件,最简化项目。

    ├─ /node_modules
    ├─ /public
    |  ├─ favicon.ico
    |  ├─ index.html
-   |  ├─ logo192.png
-   |  ├─ logo512.png
    |  ├─ mainfest.json
-   |  └─ robots.txt
    ├─ /src
-   |  ├─ App.css
    |  ├─ App.js
-   |  ├─ App.test.js      
-   |  ├─ index.css
    |  ├─ index.js
-   |  ├─ logo.svg
-   |  ├─ reportWebVitals.js
-   |  └─ setupTests.js
    ├─ .gitignore
    ├─ package-lock.json
    ├─ package.json
    └─ README.md

现在目录结构如下,清爽许多:

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

以上文件删除后,页面会报错。这是因为相应的文件引用已不存在。需要继续修改代码,先让项目正常运行起来。

逐个修改以下文件,最终精简代码依次如下:

src/App.js:

function App() {
    return <div className="App">React-CRX-2023</div>
}

export default App

src/index.js:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)

public/index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>React-CRX-2023</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

运行效果如下:

2023新春版:React+Antd开发Chrome插件教程(Manifest V3)

2 Chrome Extension基础

本次教程基于目前最新的Chrome Extension Manifest V3进行。

2.1 Manifest V3概述

Manifest V3(简称MV3) 是2020年11月9日发布的,时隔MV2已经有很多年了。使用MV3的Chrome插件将有更好的隐私、安全和性能,还能使用很多新的Web技术。具体如下:

(1)隐私

新版插件可以在不需要特殊权限的情况下正常运行,当运行到需要某个权限时再请求用户使用许可。

(2)安全

对插件访问外部资源做了限制,禁止引入外部js,但图片、视频等静态外部资源不受影响。

(3)性能

确保插件可以在各种设备良好运行,即使在安装了很多插件的情况下,也能流畅运行。

(4)开发

降低开发门槛,减少开发障碍,更快更好地开发插件。

(5)能力

持续提升插件的能力、丰富功能,充分发挥更大的价值作用。

2.2 Manifest V3 主要新特性

(1)Service Workers取代background pages

使用Service Workers,可以对资源进行缓存,从而实现离线访问。

(2)网络请求调整 新增了一个declarativeNetRequestAPI,允许插件修改及阻断网络请求。

(3)远程资源访问限制 禁止访问外部的JavaScript及Wasm文件,但图片、音视频文件不受影响。

(4)Promises使用 可以愉快地使用promise了,包括async/await。

除此之外,还有一些其他的变化。未来,MV3还会引入更多的新特性。

了解更多可参阅官网说明:

developer.chrome.com/docs/extens…

2.3 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.4 规划build生成的目录结构

在了解Chrome Extension的基本组成后,需要按照Chrome Extension官方开发文档以及manifest.json的要求,按以下结构build最终的目录。

├─ favicon.ico         <--这个没有也行,用不到
├─ index.html          <--popup入口页面
├─ insert.js           <--插入到目标页面执行的js(非必须,视业务需求而定)
├─ manifest.json       <--插件的配置文件
└─ /static
   ├─ /css
   |  ├─ content.css   <--content页面样式(会与目标页面互相污染)
   |  └─ main.css      <--popup页面样式(不会与目标页面互相污染)
   ├─ /js
   |  ├─ background.js <--background script
   |  ├─ content.js    <--content script
   |  └─ main.js       <--popup script
   └─ /media           <--项目的图片资源存放目录

接下来就是如何实现build出这样的目录结构。

2.5 配置manifest.json

在开发Chrome Extension之前,要先配置好manifest.json。

修改public/manifest.json(请删除其中的注释代码):

{
  "name": "Chrome插件V3",
  "version": "1.0",
  "description": "React开发chrome插件V3 Demo。",
  // Chrome Extension 版本号,3表示MV3
  "manifest_version": 3,
  // background script配置(根目录为最终build生成的插件包目录)
  "background": {
    "service_worker": "static/js/background.js"
  },
  // content script配置
  "content_scripts": [
    {
      // 应用于哪些页面地址(可以使用正则,<all_urls>表示匹配所有地址)
      "matches": ["<all_urls>"],
      // 注入到目标页面的css,注意不要污染目标页面的样式
      "css": ["static/css/content.css"],
      // 注入到目标页面js,这个js是在沙盒里运行,与目标页面是隔离的,没有污染问题。
      "js": ["static/js/content.js"],
      // 代码注入的时机,可选document_start、document_end、document_idle(默认)
      "run_at": "document_end"
    }
  ],
  // 申请chrome extension API权限
  "permissions": ["storage","declarativeContent"],
  // 插件涉及的外部请求地址,暂未发现影响跨域请求,猜测是用于上架商店时方便审核人员查阅
  "host_permissions":[],
  // 如果向目标页面插入图片或者js,需要在这里授权插件本地资源(以下仅为示例)。
  "web_accessible_resources": [
    {
      "resources": [ "/images/app.png" ],
      "matches": ["<all_urls>"]
    },
    {
      "resources": [ "insert.js" ],
      "matches": ["<all_urls>"]
    }
  ],
  // popup页面配置
  "action": {
    // popup页面的路径(根目录为最终build生成的插件包目录)
    "default_popup": "index.html",
    // 浏览器插件按钮的图标
    "default_icon": {
      "16": "/images/app.png",
      "32": "/images/app.png",
      "48": "/images/app.png",
      "128": "/images/app.png"
    },
    // 浏览器插件按钮hover显示的文字
    "default_title": "React CRX MV3"
  },
  // 插件图标,图省事的话,所有尺寸都用一个图也行
  "icons": {
    "16": "/images/app.png",
    "32": "/images/app.png",
    "48": "/images/app.png",
    "128": "/images/app.png"
  }
}

manifest的配置项还有很多,可前往官网查阅:

manifest:developer.chrome.com/docs/extens…

manifest_version:developer.chrome.com/docs/extens…

content script:developer.chrome.com/docs/extens…

permissions:developer.chrome.com/docs/extens…

3 项目目录结构设计

本文将按照以下目录结构进行开发,后续章节的webpack配置也是基于此目录结构。

├─ /config              <--配置目录(由eject生成)
├─ /public              <--popup入口页面
|  ├─ /images           <--图片目录
|  |  └─ app.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目录
|  |  └─ /styles        <--公用样式目录
|  ├─ /content          <--content script开发目录
|  |  ├─ /components    <--content 组件目录
|  |  ├─ /images        <--content 图片目录
|  |  ├─ content.styl   <--content 样式
|  |  └─ index.js       <--content script主文件
|  ├─ /popup            <--popup开发目录
|  |  ├─ /pages         <--popup 页面目录
|  |  ├─ /components    <--popup 组件目录
|  |  ├─ /router        <--popup 路由配置目录
|  |  |  └─ index.js    <--popup 路由配置文件
|  |  ├─ index.js       <--popup 主文件
|  |  └─ popup.styl     <--popup 样式文件
|  └─ index.js          <--项目主文件,也是popup入口文件
├─ .gitignore
└─ package.json

这种目录结构设计,将background script、content script、popup分别建立独立的目录,并且设置了api、common等公用目录,方便多人协作开发及后续维护。

【说明】

  1. 由于content script是在目标页面上执行,并不是独立的页面,因此不能使用router,也无需pages目录。
  2. popup是独立的页面,可以按照常规React项目设定相应的目录。

4 Webpack配置

现在执行yarn build生成的工程是无法被Chrome加载的。这是因为在manifest.json里设置了popup、background script、content script的路径,而现在build出来的工程并不符合Chrome Extension的要求,所以需要对Webpack进行配置。

4.1 配置国内镜像源

npm和yarn默认是从国外源站拉取依赖包的,为提高下载速度和稳定性,建议配置为国内镜像源。

设置yarn registry国内镜像:

yarn config set registry https://registry.npmmirror.com

设置npm registry国内镜像:

npm config set registry https://registry.npmmirror.com

设置yarn node-sass国内镜像:

yarn config set SASS_BINARY_SITE https://npmmirror.com/mirrors/node-sass/

设置npm node-sass国内镜像:

npm config set SASS_BINARY_SITE https://npmmirror.com/mirrors/node-sass/

据淘宝官方声明,原先的 npm.taobao.org 和 registry.npm.taobao.org 域名于2022年5月31日零时起停止服务。新域名如下:

【Web 站点】npmmirror.com

【Registry Endpoint】registry.npmmirror.com

官方公告原文:《【望周知】淘宝 NPM 镜像站喊你切换新域名啦》(zhuanlan.zhihu.com/p/430580607)

如果不清楚本地当前yarn或者npm的配置,可以执行以下命令查看:

yarn查看方法:

yarn config list

npm查看方法:

npm config list

4.2 暴露Webpack

create-react-app默认情况下未暴露配置文件。如果要更灵活地配置项目,需要将配置文件暴露出来。

执行以下命令,暴露配置文件:

yarn eject

eject之前必须确保当前工程所有文件已提交git,否则会报以下错误:

Remove untracked files, stash or commit any changes, and try again.

需要先在项目根目录下执行提交git:

git add .
git commit -m "初始化项目(备注)"

然后再执行:

yarn eject

即可完成Webpack的暴露,这时项目里会多出来两个目录和若干个文件。具体变化如下:

+   ├─ /config
    ├─ /node_modules
    ├─ /public
+   ├─ /scripts
    ├─ /src
    ├─ .gitignore
M   ├─ package-lock.json
M   ├─ package.json
    └─ README.md

4.3 支持Sass/Scss

eject后,虽然package.json以及webpack.config.js里有了sass相关代码,但是要正确使用Sass/Scss,还要再安装node-sass。

执行以下命令:

yarn add node-sass --dev

安装完成后,项目已支持Sass/Scss。

4.4 支持Less

支持Less稍微多一点步骤,首先安装less和less-loader:

yarn add less less-loader --dev

然后修改config/webpack.config.js:

    // style files regexes
    const cssRegex = /\.css$/;
    const cssModuleRegex = /\.module\.css$/;
    const sassRegex = /\.(scss|sass)$/;
    const sassModuleRegex = /\.module\.(scss|sass)$/;
+   const lessRegex = /\.less$/;
+   const lessModuleRegex = /\.module\.less$/;

    ...(略)
    
    // Opt-in support for SASS (using .scss or .sass extensions).
    // By default we support SASS Modules with the
    // extensions .module.scss or .module.sass
    {
      test: sassRegex,
      exclude: sassModuleRegex,
      use: getStyleLoaders(
        {
          importLoaders: 3,
          sourceMap: isEnvProduction
            ? shouldUseSourceMap
            : isEnvDevelopment,
          modules: {
            mode: 'icss',
          },
        },
        'sass-loader'
      ),
      // Don't consider CSS imports dead code even if the
      // containing package claims to have no side effects.
      // Remove this when webpack adds a warning or an error for this.
      // See https://github.com/webpack/webpack/issues/6571
      sideEffects: true,
    },
    // Adds support for CSS Modules, but using SASS
    // using the extension .module.scss or .module.sass
    {
      test: sassModuleRegex,
      use: getStyleLoaders(
        {
          importLoaders: 3,
          sourceMap: isEnvProduction
            ? shouldUseSourceMap
            : isEnvDevelopment,
          modules: {
            mode: 'local',
            getLocalIdent: getCSSModuleLocalIdent,
          },
        },
        'sass-loader'
      ),
    
+   // 支持Less 
+   {
+     test: lessRegex,
+     exclude: lessModuleRegex,
+     use: getStyleLoaders(
+       {
+         importLoaders: 3,
+         sourceMap: isEnvProduction
+           ? shouldUseSourceMap
+           : isEnvDevelopment,
+         modules: {
+           mode: 'icss',
+         },
+       },
+       'less-loader'
+     ),
+     sideEffects: true,
+   },
+   {
+     test: lessModuleRegex,
+     use: getStyleLoaders(
+       {
+         importLoaders: 3,
+         sourceMap: isEnvProduction
+           ? shouldUseSourceMap
+           : isEnvDevelopment,
+         modules: {
+           mode: 'local',
+           getLocalIdent: getCSSModuleLocalIdent,
+         },
+       },
+       'less-loader'
+     ),
+   },

其实就把上面sass配置代码复制一遍,改成less。按照以上操作后,项目已支持Less。

4.5 支持Stylus

支持Stylus跟Less完全一样,首先安装stylus和stylus-loader:

执行以下命令:

yarn add stylus stylus-loader --dev

安装完成后,按照上一小节介绍的支持Less的方法,修改config/webpack.config.js:

    // style files regexes
    const cssRegex = /\.css$/;
    const cssModuleRegex = /\.module\.css$/;
    const sassRegex = /\.(scss|sass)$/;
    const sassModuleRegex = /\.module\.(scss|sass)$/;
    const lessRegex = /\.less$/;
    const lessModuleRegex = /\.module\.less$/;
+   const stylusRegex = /\.styl$/;
+	const stylusModuleRegex = /\.module\.styl$/;

    ...(略)
 
+   // 支持stylus
+   {
+     test: stylusRegex,
+     exclude: stylusModuleRegex,
+     use: getStyleLoaders(
+       {
+         importLoaders: 3,
+         sourceMap: isEnvProduction
+           ? shouldUseSourceMap
+           : isEnvDevelopment,
+         modules: {
+           mode: 'icss',
+         },
+       },
+       'stylus-loader'
+     ),
+     sideEffects: true,
+   },
+   {
+     test:stylusModuleRegex,
+     use: getStyleLoaders(
+       {
+         importLoaders: 3,
+         sourceMap: isEnvProduction
+           ? shouldUseSourceMap
+           : isEnvDevelopment,
+         modules: {
+           mode: 'local',
+           getLocalIdent: getCSSModuleLocalIdent,
+         },
+       },
+       'stylus-loader'
+     ),
+   },

按照以上操作后,项目已支持Stylus。

4.6 设置路径别名

为了避免使用相对路径的麻烦,可以设置路径别名。

修改config/webpack.config.js:

    alias: {
        // Support React Native Web
        // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
        'react-native': 'react-native-web',
        // Allows for better profiling with ReactDevTools
        ...(isEnvProductionProfile && {
            'react-dom$': 'react-dom/profiling',
            'scheduler/tracing': 'scheduler/tracing-profiling',
        }),
        ...(modules.webpackAliases || {}),
+       '@': path.join(__dirname, '..', 'src'),
    },

这样在js代码开头的import路径中,直接使用@表示“src根目录”,不用去自己去数有多少个"../"了。

例如,src/app.js:

// 表示该文件当前路径下的app.styl(相对路径)
import './app.styl'
// 表示src/app.styl,等价于上面的文件地址(绝对路径)
import '@/app.styl'

4.7 禁止build项目生成map文件

map文件,即Javascript的source map文件,是为了解决被混淆压缩的js在调试的时候,能够快速定位到压缩前的源代码的辅助性文件。这个文件发布出去,会暴露源代码。因此,建议直接禁止build时生成map文件。

修改config/webpack.config.js,把shouldUseSourceMap的值改成false:

    // Source maps are resource heavy and can cause out of memory issue for large source files.
-   const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
+   const shouldUseSourceMap =false;

4.8 设置多入口

这一步是实现React架构Chrome插件的关键。

为了让:

src/background/index.js
src/content/index.js
src/popup/index.js

按照manifest.json中设置的文件目录,分别编译出对应的文件,需要设置多入口。

对config/webpack.config.js进行以下修改:

❤️❤️❤️------试读结束------❤️❤️❤️

后续精彩章节

5 引入Ant Design 5.x5.1 安装Ant Design
• 5.2 设置Antd为中文语言
6 Popup开发
• 6.1 引入popup页面
• 6.2 构建popup的Login页面
• 6.3 构建popup的Home页面
• 6.4 构建popup的Account页面
• 6.5 配置popup页面路由
• 6.6 构建Nav导航组件
• 6.7 构建Entry二级路由框架页面
• 6.8 调整popup入口页面,打通全部路由
• 6.9 完善Login页面的登录跳转
• 6.10 设置popup页面尺寸
7 build项目并载入插件
8 background script开发
• 8.1 设置允许运行popup的页面规则
• 8.2 为什么插件图标在禁用页面不变成灰色
9 content script开发
• 9.1 向目标页面注入悬浮球
• 9.2content script中使用Antd
• 9.3 加载插件自身的静态图片资源(选读)
10 在开发环境中调试content script
11 API请求
• 11.1 background pages不支持XMLHttpRequest(axios)
• 11.2 使用mock.js和mockjs-fetch模拟请求
• 11.3 封装API及fetch业务逻辑
• 11.4 委托background script完成API请求
• 11.5 实现popup的Login页面API请求
• 11.6 设置开发环境的反向代理请求
• 11.7 实现content script的API请求
• 11.8 关键知识点小结
12 其他说明
• 12.1 permission权限配置
• 12.2 以<script>方式向目标页面插入js
• 12.3 Service Worker调试
• 12.4 popup页面调试
• 12.5 精简最终build文件
13 项目Git源码
结束语

完整教程可订阅我的公众号**【卧梅又闻花】**

2023新春版:React+Antd开发Chrome插件教程(Manifest V3)

项目Git源码

项目全部代码请到Git站查阅。推荐Gitee,国内网络访问顺畅。

Gitee:

gitee.com/betaq/react…

GitHub:

github.com/Yuezi32/rea…