定制开发小程序chrome V3插件入门到放弃,Plasmo不完全使用指南

chrome V3定制开发小程序插件入门到放弃,Plasmo定制开发小程序不完全使用指南

定制开发小程序没有插件的浏览器是没有灵魂的。定制开发小程序今天来近距离感受一下chrome的灵魂

定制开发小程序开始之前了解一下灵魂的版本。

Chrome 浏览器从88定制开发小程序版本开始支持MV3啦(即Manifest Version 3),定制开发小程序现在浏览器版本都100+了。而MV2(即Manifest Version 2)将会在2023年 退休 。定制开发小程序所以今天要讲的就是MV3版本

定制开发小程序后续的文章中,定制开发小程序因为我没有魔法,定制开发小程序所以贴出来的文档地址定制开发小程序都是国内可以访问的文档(定制开发小程序有条件的同学可以直接定制开发小程序看谷歌的原文档 )

定制开发小程序版本变更的变动

manifest.json 定制开发小程序作为插件的配置清单最定制开发小程序能体现相关的变动了 从 定制开发小程序可以很清楚地看到配置定制开发小程序升级其实主要加了2个 「action」和 「host_permissions」

定制开发小程序比较小的变动

Host Permissions

在V2中,定制开发小程序有两种方法为你的api定制开发小程序或任何主机获得权限,要么在 permissions 数组或 optional_permissions 数组。

{  "permissions": ["https://xxxx.com/*"]}
  • 1
  • 2
  • 3

在V3中,定制开发小程序所有主机权限现在都单定制开发小程序独存在一个新数组中,定制开发小程序该数组的键为 host_permissions。定制开发小程序主机权限不再与其他权定制开发小程序限一起添加。

{  "host_permissions": ["https://xxx.com/*"]}
  • 1
  • 2
  • 3

Actions

在V2中,分为 browser_actionpage_action

  • browser_action 定制开发小程序更多是负责插件的icon定制开发小程序的切换等操作。参考文档:
  • page_action 定制开发小程序更多是针对某个页面进行地址栏的操作 参考文档:

感兴趣的可以在插件开发文档里面看一看。

在V3中,都统一合并为 action 。参考文档:

content_security_policy 变动

在V2的manifest.json 的 content_security_policy 配置是一个字符串类型。升级到 V3 后变成了一个对象类型。详细的变更看文档会更加清晰:

web_accessible_resources

详细变更参考

// v2 写法{	"web_accessible_resources": ["images/my-image.png"]}
  • 1
  • 2
  • 3
  • 4
// v3 写法{  // …  "web_accessible_resources": [    {      "resources": [ "test1.png", "test2.png" ],      "matches": [ "https://web-accessible-resources-1.glitch.me/*" ]    }, {      "resources": [ "test3.png", "test4.png" ],      "matches": [ "https://web-accessible-resources-2.glitch.me/*" ],      "use_dynamic_url": true    }  ],  // …}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

不在允许远程托管代码

以前一些功能可以依赖于网络请求动态加载,V3 则不允许这样的操作了

  • 不再支持加载远程托管的代码主要出于两个原因:
    • 安全因素,远程代码总是有不安全因素存在
    • Chrome 在审核提交的插件时更可靠,更高效,不需要再去关注远程代码,只需要审核包内的代码即可。

只能把以前通过链接加载的js下载到插件包中,改改资源引入就好~

Promises

V3 现在原生支持 Promise。许多常用 API 现在都支出,最终所有合适的 API 都会支持 Promise。

如果使用 callback,就不会返回 Promise,优先执行 callback。

较大的变动

将 Background Scripts 改造成 Service Workers

在V2中,Background是可以通过 persistent 配置来确保页面时候需要 持久化 。而且还能支持 .html

"background": {  "scripts": ["background-script.js"],  "persistent": false}//  或"background": {  "page": "background-page.html",  "persistent": false}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

很多小技巧都依赖于 html 这特性,把数据挂载在 background 的 window 对象上进行数据中转

V3 则是强制使用了 Service Workers,禁止了持久化。background只能使用js文件

"background": { "scripts": ["background.js"] },
  • 1

网络拦截,使用新的declarativeNetRequest来修改请求

这个变动非常的大,在本文后面详细讲这一块的内容。而且 MDN 文档还没更新 declarativeNetRequest 相关的内容,等下要找个新文档来看

弃用的API

  • chrome.extension.getExtensionTabs()
  • chrome.extension.getURL()
  • chrome.extension.lastError
  • chrome.extension.onRequest
  • chrome.extension.onRequestExternal
  • chrome.extension.sendRequest()
  • chrome.tabs.getAllInWindow()
  • chrome.tabs.getSelected()
  • chrome.tabs.onActiveChanged
  • chrome.tabs.onHighlightChanged
  • chrome.tabs.onSelectionChanged
  • chrome.tabs.sendRequest()
  • chrome.tabs.selected

在查看 MDN 文档时会有相关的提示

使用chrome官方文档时的提示

查阅官方文档时,那些标签也能帮助到我们。
Promise 标签:支持 Promise
<=MV2 标签:该API仅在V2前支持
>=MV3标签 :该API在V3后支持
Deprecated 标签:已废弃的 API


使用 Plasmo 开发

都2022年了,或许每次开发一些新东西的时候你都会在想:

  • 我要用什么技术栈?Vue3/React?
  • 开发插件,能不能用npm上的包啊,如果能的话我是不是还得配webpack或者vite?
  • webpack的话,我是不是得配置多个入口和出口才能满足插件的入口要求

好麻烦啊,上github找找有没有现成的。
好像都还可以,收藏吃灰,下次在开发把

不要下次了!就这次把,强烈推荐 Plasmo

官网:
github:

官方自己的介绍(说的非常的朴素,我一个路人都觉得这功能写少了)

作为一个过来人的感受,我只能说用 Plasmo 很舒服~

Plasmo 上手体验

想体验先安装Plasom,快速上手文档:

注意下自己的 pnpm 版本或者 npm 版本,我用的是pnpm

# 使用下面的命令进行项目初始化pnpm dlx plasmo init# OR npm v7npm x plasmo init
  • 1
  • 2
  • 3
  • 4
  • 5

这时候如果你是新手,建议直接从 找一个模版看一下他的目录结构,然后找到自己想要的功能进行开发

比如我想基于vue,开发一个 popup 的界面。可以在示例中直接找到

还有各种技术栈(React,svelte,tailwindcss,nextjs…)

不止 popup 页,还有 background, devtool, options 页面都能在 examples 仓库找到相关的模版

Plasmo 有一个很方便的地方在于:我开发 popup 的页面,我只需要有一个叫 popup.(tsx | vue) 的文件,开发background,只需要有一个 background.ts 文件。

这些作为对应的入口文件我们只需要按命名规范写好(甚至可以写成 popup/index.vue ),剩下的 manifest.json 配置就交给 Plasmo

从安装脚手架到现在,我们都没见到 manifest.json 文件,更加说明了这些入口不需要我们显示声明

Plasmo 修改 manifest.json 配置

虽然没有 manifest.json ,但是该要写的配置还是得写的

比如我们开发一个针对 http://xxxx.com 网页的插件,首先得申请权限 host_permissions

这部分配置写在了 package.json 中的 "manifest" 下。包括申请权限,注入资源都在 "manifest" 中去配置。

// package.json{	// ...	"manifest": {		"permissions":["declarativeNetRequest"], // 获取拦截网络请求的权限		// 页面注入静态资源		"web_accessible_resources": [			{				"resources": [					"inject.js"				],				// 针对全部界面注入				"matches": [					"<all_urls>"				]			}		],		// 针对哪些页面生效		"host_permissions": [			"https://xxxx.com/*",			"http://xxxx.com/*"		]	}	// ...}
  • 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

有个例外就是 content.ts (注入到网页的那部分内容)

因为 content.ts 对应的配置是

正常的配置应该是这样的

"content_scripts": [  {    "matches": ["*://*.mozilla.org/*"],    "js": ["content.js"]  }]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

因为 content.ts 是动态入口,也就是说 content_scripts[0].js 的内容是框架去生成的,而不是我们自己手动填的

这也就造成了 content_scripts 的配置只能是写在 content.ts 这个页面中。这样 Plasmo 才能既知道入口路径,也知道对应的配置

以下示例代码来自:

// file - content.tsimport type { PlasmoContentScript } from "plasmo"// 进行 content_scripts 的配置export const config: PlasmoContentScript = {  matches: ["https://www.plasmo.com/*"]}window.addEventListener("load", () => {  console.log("content script loaded")  document.body.style.background = "pink"})// 运行后出来的配置可能就是// "content_scripts": [//   {//     "matches": ["https://www.plasmo.com/*"],//     "js": ["content.[hash].js"]//   }// ]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 可能又有同学有疑问,就是这样的配置写完,那我岂不是只能写一个 content.ts ? 如果我想一个插件针对不用的站点做不同的操作呢?

好问题,去example找找模版就知道了 。这里提供了多个 content.ts 的示例,这样就能针对不同页面注入不同的 content.ts

框架也提供了 自定义 manifest.json 的能力。更多的配置可以看

Plasmo 提供的一些库和功能

  1. @plasmohq/storage

参考文档

@plasmohq/storage 是一个来自 plasmo 的实用程序库,它抽象了浏览器扩展可用的持久存储 API。当扩展存储 API 不可用时,它会回退到本地存储,允许在弹出窗口 - 选项 - 内容 - 背景之间进行状态同步。

官网还说了一句,如果使用了这个库,配置会自动把 storage 的权限加上

我觉得还是挺好的,这样依赖抹平了不同平台之间存储的差异,也做了保底方案


  1. 一些比较特殊的标记符 url: data-text:~ data-base64:~

这部分标记符可以在这些文档中找到 、、

用起来就类似这样的:

import cssText from "data-text:~/contents/plasmo-overlay.css"import someCoolImage from "data-base64:~assets/some-cool-image.png"import myJavascriptFile from "url:./path/to/my/file/something.js"
  • 1
  • 2
  • 3
  • 4
  • 5

这部分更多的可能是为了相对路径,或者引入一些特殊的内容。比如 data-text:~ 这个就很有用,我可以在 .css 文件中更好的编写我的内容,然后通过 data-text:~ 把文件的内容以 text 引入,用于我注入到页面上

url: 这个也是为了获取这个文件在打包后所处的位置。

比方说我们按正常模式写文件,写完后可能要给 content.js 动态注入到页面去,这时候可以动态创建script标签,
src = chrome.runtime.getURL('xxx.js')

不过因为我们这个是进过了 Plasmo 打包的,有可能对应的资源被加上了hash值,这时候 url: 就是获取文件的路径了(类似 chrome.runtime.getURL(‘xxx.js’) 的功能了)

在示例仓库 就有这么一段代码:

import fontPickerHTML from "url:./panels/font-picker/index.html"import fontPropertiesHTML from "url:./panels/font-properties/index.html"chrome.devtools.panels.create(  "Font Picker",  null,  // See: https://github.com/PlasmoHQ/plasmo/issues/106#issuecomment-1188539625  fontPickerHTML.split("/").pop())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可以自己打印一下 fontPropertiesHTML 变量,其实是一个网页的路径。(使用.split(“/”)是为了处理一个bug,issuse链接也在备注里了,可以看看了解了解)

文档链接在都贴出来了,更多的用法就自己去摸索了

插件运行和打包

不管是运行 npm run dev 还是 run build,都会生成一个 build/xxxx 目录。里面就是存放着可以运行的chrome插件代码

默认是 chrome-mv3-dev 代表开发 chrome 插件,v3 版本,dev环境

当然你也可以用 --target 指定是开发 firefox 版本/开发 mv2版本, 毕竟都不推荐开发 mv2 的东西了。就不细说了

运行 npm run dev 后,把 build/chrome-mv3-dev 这个文件夹拖到浏览器安装插件的位置,就能看到了。不知道怎么操作的建议看下文档:

build/chrome-mv3-dev 目录下也有 manifest.json 文件,也就是我们在 package.json 里面 + content.ts 的配置,所有的配置都汇总在这里了。想看配置有没有生效看这里就行


插件打包,打包为zip

pnpm build -- --zip# ORnpm run build -- --zip# ORplasmo build --zip打包到firefoxplasmo build --target=firefox-mv2 --zip
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

插件在多平台的兼容性问题

build --target=firefox 的作用体现在哪里?

说实话我也没发现,可能是为了多区分开一个目录,或者 firefox 没升级到 mv3 版本,又或者是同样的配置 firefox 有细微差别,Plasmo就可以自动处理掉

至于代码兼容性

在开发过程中,我们都是用 chrome 作为插件API,比如 chrome.runtime.sendMessage

  • chrome 这个标识各大平台也都识别,比如 360浏览器,包括火狐也兼容 chrome.xxx.xxx 。正常来说不用特别的适配,写的话也按 chrome 来写即可

开头也提到,我因为不能看外网的chrome插件开发文档,好在国内还可以访问 MDN ,API这一块同步的还是比较快的,甚至有些页面有中文翻译了,平时查API可以到这里查

  • MDN 的文档的API是用的 browser 开头,兼容性可以看对应文档下面的表格。(如果你用browser,在开发过程是没有智能提示的,毕竟我们装的ts包是 @types/chrome)。

也会真的发生有兼容性问题,毕竟chrome更新一直都很快的

  • 用尤大开发的 vue-devtool 插件来看看尤大都是如何处理兼容性问题的(虽然尤大用的不是 Plasmo,不过不影响我们学习代码)

判断运行环境:

// env.ts 节选代码export const isBrowser = typeof navigator !== 'undefined'export const target: any = isBrowser  ? window  : typeof global !== 'undefined'    ? global    : {}export const isChrome = typeof target.chrome !== 'undefined' && !!target.chrome.devtoolsexport const isFirefox = isBrowser && navigator.userAgent.indexOf('Firefox') > -1export const isWindows = isBrowser && navigator.platform.indexOf('Win') === 0export const isMac = isBrowser && navigator.platform === 'MacIntel'export const isLinux = isBrowser && navigator.platform.indexOf('Linux') === 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

根据环境,使用不同的处理方式,比如网页快照

在chrome中,能直接使用 chrome.tabs.captureVisibleTab

当然还有随处可见的这样的判断

  • 如果一定要说框架有帮我们处理什么兼容问题,那可能就是本地存储了

提供了 @plasmohq/storage 抹平各个平台的存储api差异,还提供了快捷的方式然我们更新本地存储的内容

处理兼容性问题从来都不是一件容易的事情,搞不好开发人员都处理的很头大,所以更加别指望框架能自动处理。

总的看下来 --target 好像更多是用于发布到不同平台的时候有用,而不是帮我们处理不同浏览器的兼容问题(我的插件不发布到商店去,所以暂时找不到用途)

Plasmo 的介绍就到这里了。我也没开发什么出名的插件(很惭愧)都是处理公司需要的内容,所以可能还有很多好玩的功能没发掘到

Plasmo 还能一键发布到各个平台之类的功能,等着你们自己去探索了


如何应对 MV3 版本中“较大的变动”

一开始介绍的时候有提到版本变动有较小的,还有2个较大的。在我看来较小的变动可能只是改一下配置,不用影响太多业务逻辑代码就能运行的。

而较大的变动就影响挺大的

background 变动的影响

说一个场景,比如我们都很熟悉的浏览器拦截插件,或者其他的插件,下面都有角标。关键是这些角标是根据当前的域名记录的。

怎么做到的呢?依赖 popup 的页面记录吗?
popup 几乎不可能,因为在我开发过程中,popup 在每次打开的时候其实都会重新运行一遍。同一个站点如果打开2次popup.tsx对应的组件就会在执行2次

所以这部分的数据就得留给 background.ts 或者 content.ts 去做

为了搞懂这其中的技巧,我看了一下 猫抓 这个插件的代码

以下代码节选自 猫抓 插件

// js/popup.jsvar BG = chrome.extension.getBackgroundPage();var tabid;chrome.windows.getCurrent(function(wnd) {    chrome.tabs.getSelected(wnd.id, function(tab) {        tabid = tab.id;        var id = "tabid" + tab.id;        ShowMedia(BG.mediaurls[id]);    });});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
// js/background.js//初始化if (typeof mediaurls === 'undefined') {    var mediaurls = new Array();}// ...// 中间的代码用了 chrome.webRequest.onResponseStarted 监听请求// 然后筛选出 .m3u8 和 分析出对应的 .ts 文件,感兴趣的自己在看看// ...//标签更新,清除该标签之前记录chrome.tabs.onUpdated.addListener(function(tabId, changeInfo) {    if (changeInfo.status == "loading") //在载入之前清除之前记录    {        var id = "tabid" + tabId; //记录当前请求所属标签的id        if (mediaurls[id])            mediaurls[id] = [];    }});//标签关闭,清除该标签之前记录chrome.tabs.onRemoved.addListener(function(tabId) {    var id = "tabid" + tabId; //记录当前请求所属标签的id    if (mediaurls[id])        delete mediaurls[id];});
  • 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

可以看到,在 popup.js 里面获取了一个BG

因为 MV2的background是有window对象的。所以 BG 可以理解为 background.html 的 window对象 var BG = chrome.extension.getBackgroundPage();

从 window 对象中获取 mediaurls 参数,获取对应tab要显示的角标数,然后给到 popup 显示

如果 background 变成了一个 Service Worker ,那就不存在 window 对象了

解决方案就是改用通信的方式,popup发起一个sendMessage。background来监听,并且进行回调给popup
整体的思路还是用 background 来存储和转发消息,background 收到的内容后存储到本地去。

【非常严重】网络请求拦截的变动 declarativeNetRequest

只能感叹一句,V2 版本的拦截请求还是很好用的。

推荐一篇教程: 这里面的的攻略很多都没过时,除了上面说的改动其他都很值得参考学习。我也是看这个入的门

可以看到教程的 看看v2版本的拦截网络请求写法

之前是通过声明 webRequestwebRequestBlocking 等权限来进行网络请求的拦截。不过现在声明了也没用了

都要改为 declarativeNetRequest 来拦截

虽然新版的API也能拦截请求,修改head头之类的操作
但是,这些操作都没有回调!!(V2版本是有回调的,猫抓就是基于回调才抓的请求地址)

不过进过一通瞎找,找到另外一个文档(MDN还没更新 declarativeNetRequest 的内容)

这个方法有这么一段话

Fired when a rule is matched with a request. Only available for unpacked extensions with the declarativeNetRequestFeedback permission as this is intended to be used for debugging purposes only.

当规则与请求匹配时触发。 仅适用于具有 declarativeNetRequestFeedback 权限的解压扩展,因为这仅用于调试目的。

注意是 解压扩展,意思就是必须是解压的包/zip包,并且声明了这个权限才能用。
如果你的插件是想发布到应用市场,或者生成 .crx 后缀的插件包,一样是用不了 onRuleMatchedDebug 滴(累了)


虽然background不能直接监听返回的内容,不过 devtool 面板可以啊 。但是如果你想用devtool面板的API话,你得打开F12才能用 (累了*2)

所以目前拦截回调的这一块还没有想出非常通用的方案,或许这就是chrome口中的安全,隐私…

如果想粗暴点解决的话其实可以把要拦截的源文件下载下来,然后手动添加一个 window.postMessage(xxx) 主动给 content.ts 发消息,然后 content.ts 在转发到后台去
background 部分就拦截网络请求,redirect 到插件下载的源文件那边去(其实就是针对性很强针对某个网页的某个js可以这么搞)

如果是想篡改某些js的内容,而且自己会本地开一个服务的话,用 redirect 真的是很方便的

既然都讲到拦截了,顺便讲讲 如何拦截网页发出的请求。原理就是用 content.js 注入js,修改 window.XMLHttpRequestwindow.fetch 方法就能拦截到了

推荐直接学习: 这里面的代码

唯一的问题可能就是 content.js 注入的速度没有页面发起请求的快,就会有几条漏网之鱼。

最后

这篇文章主要还是想介绍下 Plasmo。个人感觉用下来还是挺好用的

至于chrome 插件要升级到 MV3 最严重的其实还是网络请求相关的。其余的应该都还好(最起码有解决方案)

讲了那么多其实没有讲到一些开发的技巧类东西,主要是一些需要注意的坑。所以汇总一下链接方便查找学习

文档类

  • chrome原汁原味文档:)

  • MDN文档插件开发文档:

  • 热心网友同步的原文文档(MDN找不到的时候就来这里看看):

入门教程推荐

  • 小茗同学 【干货】Chrome插件(扩展)开发全攻略 (http://blog.haoji.me/chrome-plugin-develop.html)[http://blog.haoji.me/chrome-plugin-develop.html]

插个题外话,如果你既没有魔法,又想看原汁原味文档(挺好的,很有追求)

可以上 github

把整个 developer.chrome.com 搞下来(下面的命令不用我细说了把)

# 安装依赖的npm run ci# dev 后 打开 http://localhost:8080/ 就可以看到npm run dev# 如果你想同步一份到自己服务器,就运行把npm run production && npm start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

插件开发介绍就到这了,如果你有好的插件记得也推荐给我

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发