TypeScript 浏览器插件开发
总结一篇 TypeScript 浏览器插件开发经验
推荐模块
浏览器插件API的TS包,开发插件必备
1
import { browser } from 'webextension-polyfill-ts'
避坑:封装网络请求用
browser.runtime.onMessage.addListener
不能直接返回axios
,虽然TS不会报错,但运行结果会是undefined
正确方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15browser.runtime.onMessage.addListener((request) => {
const params =
request.type === 'GET' ? { params: request.params } : { data: request.data }
return new Promise((resolve, reject) => {
axios({
method: request.type,
url: request.url,
...params,
headers: request.headers || {}
}).then((response) => {
resolve(response.data)
})
})
})操作
json
文件的插件,用于修改manifest.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// webpack.config.js
const WriteJsonWebpackPlugin = require('write-json-webpack-plugin')
// ...
module.exports = () => {
let manifestJSON = require('./src/manifest.json')
// 版本号
manifestJSON.version = '2.0.0'
const config =
// ...
plugins:
manifestJSON &&
new WriteJsonWebpackPlugin({
pretty: false,
object: manifestJSON,
path: '/',
filename: 'manifest.json'
})
]
// ...
}
return config
}自动重新加载插件,自动刷新插件作用的网页,测试神器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const ExtensionReloader = require('webpack-extension-reloader')
// 也是 webpack plugins 以下就简单一写了
const config = {
entry: {
content: './src/content.ts',
background:'./src/background/background.ts',
popup: './src/popup/popup.ts',
options: './src/options/options.ts'
},
// ...
plugins: [
new ExtensionReloader({
reloadPage: true,
// entry
entries: {
contentScript: 'content',
background: 'background',
extensionPage: ['popup', 'options']
}
})
]
}
Btools
最后宣传一下自己写的插件,Btools,以B站为主的网站页面优化,增强用户体验
现阶段正进行重构,使用TypeScript
+Vue
,欢迎参考:Github、Gitee
对流程的封装
Btools 分为两大模块:Linstener 模块 和 Watcher 模块
Linstener 模块
用于监听来自
background-js
的反馈,background-js
会监听网页中指定的请求比如我需要获取页面上的评论,以前的做法是傻傻的用计时器循环获取页面元素,现在先通过
background-js
监听,如果监听到获取评论的API请求会发消息给content-js
,接到消息后调用相应的模块这样做的好处是虽然也会用到计时器循环获取页面元素,但至少知道页面元素马上会加载好,一定程度上避免不必要的消耗
Watcher 模块
根据不同的网址进行不同的操作,举个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81import { WatcherBase, HandleOptions } from '@/Watcher/WatcherBase'
export class GetPicWatcher extends WatcherBase {
// 初始化函数中设置网址
protected init(): void {
this.urls[GetPicEnum.Video] = /bilibili\.com\/video/
this.urls[GetPicEnum.Bangumi] = /bilibili\.com\/bangumi/
this.urls[GetPicEnum.Watchlater] = /bilibili\.com\/medialist\/play\/watchlater/
this.urls[GetPicEnum.Read] = /bilibili\.com\/read/
this.urls[GetPicEnum.LiveRoom] = /live\.bilibili\.com/
}
// 处理函数
protected handle(options: HandleOptions): void {
// 父级调用时会把 GetPicEnum 的值传回来,这里就可以区分是哪个网址
switch (options.index) {
case GetPicEnum.Video:
this.video()
break
case GetPicEnum.Bangumi:
this.bangumi()
break
case GetPicEnum.Watchlater:
this.watchlater()
break
case GetPicEnum.Read:
this.read()
break
case GetPicEnum.LiveRoom:
this.liveRoom()
break
}
}
video() {
// ...
}
bangumi() {
// ...
}
watchlater() {
// ...
}
read() {
// ...
}
liveRoom() {
// ...
}
}
// 用于区分网址
enum GetPicEnum {
/**
* 视频页面
*/
Video,
/**
* 番剧、电影
*/
Bangumi,
/**
* 稍后再看
*/
Watchlater,
/**
* 专栏
*/
Read,
/**
* 直播间
*/
LiveRoom
}
对本地存储的封装
Btools 有着非常完善的本地存储封装,不同模块之间不会互相干扰
首先有一个模板基类
1 | export default class TemplateBase { |
这里的_name
非常关键,其他模板类继承后被实例化事,这个_name
就会变成子类类名,这就是不同模块之间不会互相干扰的关键
然后看一下某一个子类
1 | /** |
在构造函数中会把一个类型为IRetrieveInvalidVideo
的参数传进去
然后是对本地存储的读写封装
1 | /** |
传进来的config
类型是TemplateBase
,这个类有个方法是GetName
,也就是获取类名
存取时space[configs.GetName()] = configs.GetData()
在外面包一层类名
这样即使是有重名,也是OK的
使用时是这样的:
1 | import { |
避坑:谷歌插件中,获取时传入一个默认值,如果没有读取到则会返回默认值。但火狐不会自动返回默认值,所以需要自己处理
也就是扩展本地存储
代码中的23
、24
行,先解构传过来的configs
,再解构读取到的items
比如我configs
传入的{ a: [], b: [] }
,但本地存储里只有{ a: [1,2,3] }
那么先解构configs
获得{ a: [], b: [] }
,再解构items
会把a
替换掉,获得{ a: [1,2,3], b: [] }
相当于自己封装了返回默认值
收工
OK,以上就是一些小总结,告辞