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.js
• 4.11 设置全局公用样式
• 4.12 初始化项目架构文件
5 引入Ant Design 5.x
• 5.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.2 在content 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中文网站:
https://yarn.bootcss.com/
启动后,可以通过以下地址访问项目:
http://localhost:3000/
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 >
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" target="_blank" rel="external nofollow" />
<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>
运行效果如下:
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)网络请求调整
新增了一个declarativeNetRequest
API,允许插件修改及阻断网络请求。
(3)远程资源访问限制
禁止访问外部的JavaScript及Wasm文件,但图片、音视频文件不受影响。
(4)Promises使用
可以愉快地使用promise了,包括async/await。
除此之外,还有一些其他的变化。未来,MV3还会引入更多的新特性。
了解更多可参阅官网说明:
https://developer.chrome.com/docs/extensions/mv3/intro/mv3-overview/
2.3 Chrome Extension的组成
主要由以下部分组成:
- manifest.json (插件配置文件)
- popup (点击插件图标弹出的页面)
- content script (插入到目标页面中执行的js)
- 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:https://developer.chrome.com/docs/extensions/mv3/manifest/
manifest_version:https://developer.chrome.com/docs/extensions/mv3/manifest/manifest_version/
content script:https://developer.chrome.com/docs/extensions/mv3/content_scripts/
permissions:https://developer.chrome.com/docs/extensions/mv3/declare_permissions/
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等公用目录,方便多人协作开发及后续维护。
【说明】
- 由于content script是在目标页面上执行,并不是独立的页面,因此不能使用router,也无需pages目录。
- 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/
据淘宝官方声明,原先的 http://npm.taobao.org 和 http://registry.npm.taobao.org 域名于2022年5月31日零时起停止服务。新域名如下:
【Web 站点】https://npmmirror.com
【Registry Endpoint】https://registry.npmmirror.com
官方公告原文:《【望周知】淘宝 NPM 镜像站喊你切换新域名啦》(https://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进行以下修改:
❤️❤️❤️------试读结束------❤️❤️❤️
如果您对本次分享感兴趣,请关注我的微信公众号「卧梅又闻花」,继续阅读完整版:
《2023新春版:React+Antd开发Chrome插件教程(Manifest V3)》
后续精彩章节
5 引入Ant Design 5.x
• 5.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.2 在content 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源码
结束语
项目Git源码
本项目已上传至Gitee和GitHub,方便各位下载。
Gitee:
https://gitee.com/betaq/react-app-2023spring
GitHub:
https://github.com/Yuezi32/react-app-2023spring
如果您对本次分享感兴趣,请关注我的微信公众号「卧梅又闻花」,继续阅读完整版: