这篇文章你能够学习到:
- 快速使用油猴插件进行浏览器插件开发
- 用 wokoo 脚手架开发 2 个有意思的插件: 划词搜索MoveSearch 和 知乎目录zhihu-helper
- 有精力的话可以继续学习wokoo 脚手架的搭建。
为什么要学习浏览器插件开发
浏览器插件开发有的同学可能接触的不多,或者有疑问,学习开发浏览器插件有什么用呢?这里我列举一些工作中的场景:
场景 1:
告警平台收到某个前端项目的告警日志,为了复现定位问题,需要登录某个账号内进行查看。此时 rd 需要联系产品同学,让产品同学联系对应的用户,让用户提供账号密码给 rd。此流程很长,而且给用户造成不好的印象。而如果公司内部支持免密登录,只是需要拼接一个特定的 url,url 规则比较复杂,需要有账号 id 等信息。此时如果将拼接规则写到插件里,插件提供一个按钮就能够跳转到对应的用户账号中,是不是降低了 rd 的工作量?
场景 2:
当测试的同学发现问题时,需要截图,打开测试平台,填写具体的问题,提交测试报告。如果能有插件在当前页面中弹出要填写的测试表单,是不是就减少了测试同学的工作流程?
学会了浏览器插件开发后,可以做一些工作场景相关的插件,来帮助自己以及同事提高效率。这不管是述职晋升还是跳槽换工作都会是一个加分的亮点。
正式开始插件开发
一、划词搜索 MoveSearch
成品展示
安装地址:
- 安装油猴插件
- 安装MoveSearch
开发步骤
1.1 项目安装 & 初始化配置
1 | npm i wokoo -g |
选择模板
- vue
- react
这里选择 react
安装完成后,出现安装成功的界面
根据提示执行下命令
1 | cd MoveSearch |
打开油猴脚本编辑器,把 tampermonkey.js 的内容复制进去。(注意:这里是指把 tampermonkey.js 里的所有内容都复制进去,包括注释。因为此文件中被注释掉的//@xxx 都有含义,可以对应着 tampermonkey 开发文档 理解。比如
@match https://
代表在 https 页面下才会启动插件)打开网页开发者搜索,右上角出现猴子的 logo,说明环境跑通
1.2 实现基本功能
先整理一下思路,要实现划词搜索功能,主要实现以下步骤:
- 监听 mouseup 事件,触发时检查是否有文字被选中,如果有则弹窗展示
- 弹窗展示前发请求到 baidu 的开发者搜索的接口,获取搜索内容
- 如果搜索内容不为空,将内容展示到弹窗中
- 边界检测,点击弹窗区域外的内容,弹窗关闭
此处有个难点,就是 baidu 的开发者搜索接口不允许跨域,比如在掘金的网页中去请求开发者搜索的接口会被拦住。为解决这个问题,我使用vercel提供的 serverless 服务做了一次代理,在代理中给响应头增加了Access-Control-Allow-Origin
等字段,开启跨域。具体方法在1.3给出,此时我们先在开发者搜索页面里面进行开发,先保证是同域下。
步骤 1. 监听 mouseup 事件,触发时检查是否有文字被选中
编辑/src/app.js
文件,增加对 mouseup 的监听:
1 | import React from 'react' |
步骤 2. 弹窗展示前发请求到开发者搜索的接口,获取搜索内容
- 安装 axios,引入 axios
- 对 componentDidMount 内的代码进行改造,请求开发者搜索 获取搜索结果
1 | componentDidMount() { |
- 修改 render,展示搜索结果
1 | render() { |
此时,网页中能够看到搜索的结果了,基本功能搞定了。
步骤 3. 计算弹窗出现的位置,让弹窗出现在选中文字下方
我们可以得出计算公式为:
left = x + width/2 - MODAL_WIDTH/2
top = y + height
此时,app.js
的代码被改造成这样:
1 | import React from 'react' |
别忘了增加样式,修改 app.less
1 | .move-search { |
步骤 4. 边界检测,点击弹窗外部,弹窗隐藏
边界检测的判断条件如下:
left+width > x > left && top+height > y > top
1 | function boundaryDetection(x, y, modalPosition = { left: 0, top: 0 }) { |
修改app.js
增加弹窗消失的逻辑,在 selectedText 为空时判断鼠标位置是否在弹窗内。
1 | import React from 'react' |
此时,项目的基本功能已经开发完成 🎉,一些样式问题可以自行调整。
具体的代码逻辑可看 github 地址:MoveSearch
1.3 解决跨域问题
百度的接口不允许跨域访问。也就是说如果我们在一个第三方页面,比如 cdn,掘金等的网页里,要访问百度的接口就存在跨域限制,没法访问。我采用增加 node 中间层,在 node 中给响应增加Access-Control-Allow-Origin
请求头的方式开启跨域。
目前有现成的提供 serverless 服务的第三方厂家,提供服务器,node 环境等服务,我们只要关心 node 的代码逻辑即可,部署服务器配置环境等问题交给第三方厂家解决。我采用vercel提供的服务。
下面的内容和插件开发关系不大,如果不感兴趣的同学可以直接使用我配置好的域名:https:/movesearch.vercel.app/api/baidu
。
也就是将代码中 axios 请求的 url 由
1 | `https://kaifa.baidu.com/rest/v1/search?query=${selectedText}&pageNum=1&pageSize=10` |
替换成
1 | `https://movesearch.vercel.app/api/baidu?query=${selectedText}&pageNum=1&pageSize=10` |
如果对于如何实现跨域配置感兴趣的话,可以继续往下看 👇。
- 登录vercel官网,根据提示绑定 github 账号;
- 点击新建 next 项目。vercel 会自动给你的 github 上创建新的仓库,并有一个初始化项目;(注意:此处会让你填写仓库名,此仓库名和域名是相关联的。)
- git clone 此项目到本地,根据 readme 的提示,执行
npm run dev
,启动项目 - 增加
src/api/baidu.js
文件,内容如下:
稍微解释一下,src/api/baidu.js
下的文件对应的就是/api/baidu
接口
1 | const { createProxyMiddleware } = require('http-proxy-middleware') |
修改完成后直接 git push 就行。代码推仓库后会,vercel 会自动拉取最新代码更新到它的服务器,我们只要调用一下接口看是否通就行。
具体的代码逻辑可看 github 地址:nextjs
二、知乎专栏目录 zhihu-helper
成品展示
安装地址:
- 安装油猴插件 (如果安装过则不用再安)
- 安装 zhihu-helper
代码地址:wokoo/zhihu-helper
开发步骤
1.1 项目安装 & 初始化配置
1 | npm i wokoo -g |
选择模板
- vue
- react
这里选择 react,等待项目安装。项目安装完成后,根据提示执行下命令:
1 | cd zhihu-helper |
打开油猴脚本编辑器,把 tampermonkey.js 的内容复制进去。
打开网页知乎专栏,右上角出现一只猴子图标,说明项目已跑通。
补充说明
此处有的浏览器不会出现猴子图标。打开控制台可见报错:
再看请求的 html 资源的响应头,可以发现多了一条content-security-policy
规则。也就是说知乎使用了 csp 内容安全策略,通过content-security-policy
中的script-src
字段可知,知乎只允许加载指定域名的 js。具体情况可阅读 👉 内容安全策略( CSP )
如何绕过此安全策略,此处给两个方法:
- 安装插件Disable Content-Security-Policy, 在调试知乎页面时开启插件,自动把 html 页面的
content-security-policy
给设置为空。
注意,开启页面后,要点一下插件,按钮变成彩色了才算开启成功
会配置 charles 的同学,可以设置一条转发规则
这两种方法选一种就行。
遇到这中 csp 内容安全策略的网页,在上线到油猴商店的时候不能用托管 cdn 的方式,要将代码复制到编辑框中。
1.2 开发基本功能
整理思路
- 绘制左侧抽屉弹窗
- 弹窗弹出时请求知乎列表接口,拿到列表数据
- 下拉时实现加载更多的功能
下面我们来逐步实现吧~
步骤 1. 绘制左侧抽屉弹窗
主要通过 this.state.show 控制弹窗的显示隐藏。此步骤挺简单的,可以直接看代码:step1,可以将 index.js 入口里的 app 替换成 step1 查看效果。
步骤 2. 请求知乎列表接口,获取列表数据
- 安装 axios,并引入
1 | npm install axios |
- 计算请求参数
分析请求的 url 可知,请求接口为:
1 | ;`https://www.zhihu.com/api/v4/columns${this.queryName}/items?limit=20&offset=${offset}` |
其中 this.queryName 是专栏名称,当页面为专栏列表页时就是 pathname; 当页面为专栏详情页时,需要通过类名为ColumnPageHeader-TitleColumn
的 a 标签的 href 来获取。
通过这两张图应该更好理解 getQueryName 方法
1 | getQueryName = () => { |
而列表页又存在两种域名:https://www.zhihu.com/column/mandy 和 https://zhuanlan.zhihu.com/mandy 所以在else
逻辑里针对https://www.zhihu.com/column/mandy
做了处理,只保留/mandy
通过 getQueryName 方法,我们获取到了请求参数
- 发送请求,拉取目录列表
1 | getList = async () => { |
第二步的过程实现完了,代码在这里 👉step2 ,可以将 index.js 入口里的 app 替换成 step2 查看效果。
这时插件的效果是这样的,除了没有下拉加载功能,其他基本完工了。
步骤 3. 下拉时实现加载更多的功能
无限滚动的组件引了第三方库react-infinite-scroll-component:
1 | npm install react-infinite-scroll-component |
主要是在 render 函数里增加 InfiniteScroll 组件,注意 InfiniteScroll 的 height 需要通过计算给一个固定的值,否则无法触发滚动。
1 | <ul className="list-ul" onMouseLeave={this.handleMouseLeave}> |
此时功能已经开发完成,具体代码查看 👉app.js
部署插件到油猴商店
3.1 构建
执行命令
1 | npm run build |
3.2 确认油猴脚本文件 tampermonkey.js
此文件中被注释掉的//@xxx 都有含义,可以对应着 tampermonkey 开发文档 理解。
@description 插件描述
@match 指定某些域名下开启此插件,默认配了两条,
// @match https://*/*
和// @match https://*/*
表示在所有域名下都开启。但是此处希望只在 zhihu 专栏里使用此插件,所以要修改@math 字段。1
2// @match https://zhuanlan.zhihu.com/*
// @match https://www.zhihu.com/column/*@require 油猴脚本内部帮忙引入第三方资源,比如 jquery,react 等。
1
2// @require https://unpkg.com/react@17/umd/react.production.min.js
// @require https://unpkg.com/react-dom@17/umd/react-dom.production.min.js
3.3 发布插件到油猴市场
发布油猴市场的优点是不用审核,即发即用,非常方便。
- 将/dist/app.bundle.js 文件部署到 cdn 上,获取到对应 url。
注意:
js 文件可放到 github 上,如果托管到 github 上最好做 cdn 加速(我使用 cdn.jsdelivr.net 进行 cdn 加速)。
如果没有 cdn 服务器可跳过此步骤,在步骤 4 直接将 app.bundle.js 复制到油猴脚本编辑器中
登录油猴市场,谷歌账号或 github 账号都可使用。
点击账号名称,再点击「发布你编写的脚本」
进入编辑页,将 tampermonkey.js 里的内容复制到编辑框中
注意:
步骤 1 中如果托管了 cdn,需要将代码中的
localhost:8080
网址替换成静态资源 url步骤 1 中没有托管 cdn,不能直接将/dist/app.bundle.js 文件里的内容复制编辑框。因为编辑框内代码有最大限制,我们构建的 app.bundle.js 把 react 等三方库构建进去超过最大限制了。
需要对构建结果进行拆包
4.1 修改 tampermonkey.js ,通过@require 方式引入 react 和 react-dom
1
2
3
4
5
6
7
8
9
10
11
12
13
14// ==UserScript==
// @name zhihu-helper
// @namespace http://tampermonkey.net/
// @version 0.0.1
// @description 知乎目录
// @author xx
// @match https://zhuanlan.zhihu.com/*
// @match https://www.zhihu.com/column/*
// @require https://unpkg.com/react@17/umd/react.production.min.js
// @require https://unpkg.com/react-dom@17/umd/react-dom.production.min.js
// ==/UserScript==
// app.bundle.js构建好的代码4.2 修改 webpack.config.base.js 的 entry 字段
1
2
3
4
5
6
7
8entry: {
app: '/src/index.js',
vendor: [
// 将react和react-dom这些单独打包出来,减小打包文件体积
'react',
'react-dom',
],
},4.3 重新执行
npm run build
构建出新的 app.bundle.js,复制到油猴市场的编辑框内。
点击 「发布脚本」即可
wokoo 脚手架的搭建
感兴趣的同学可以阅读 wokoo 脚手架(搭建篇)