原文地址: https://segmentfault.com/a/11...
转载请在文首注明起源

TL;DR

  • 背景介绍
  • 需要剖析
  • 常识储备
  • 文档举荐
  • 构造组成
  • 组件通信
  • 开发调试
  • 打包公布
  • 问题解决

背景介绍

本文历时较久,2020年写了一半,写到迷茫,索性搁置,2021年翻出来持续写的;通过了半年多的工夫再看,会有一些不一样的了解,存在局部配图格调不统一.然而我会确保内容的正确和表白的统一.

2020年的某一天我把之前写的QQ空间批量删除说说&留言的脚本写了个chrome插件版,名字也改成了QQ空间小助手.
因为也是第一次写插件,所以记录这个过程,分享开发过程和期间遇到的问题,并做一些关键点的梳理.
所以,这并不是一篇大而全的chrome extension开发教程,这是一篇以QQ空间助手为实战来切入插件开发然而偏重讲chrome extension开发的文章.

需要剖析

QQ空间小助手性能很简略,外围性能(其实也就这点性能)是批量删除qq空间的说说和留言.
实现上也很简略,只须要两个步骤.

  1. 获取说说或者留言列表
  2. 顺次遍历id删除说说或者留言

这外面的艰难一个在于找到获取&删除 留言和说说 所须要的参数(这个咱们不开展介绍),
另外一个艰难是这些性能怎么在chrome extension中实现.

依据下面这些需要,我了解大略操作流程是这样的,

点击icon,或者点击icon弹出来的页面而后就开始获取列表,而后遍历列表开始删说说.

外面大略须要用到,组件,组件间通信这些内容.

然而因为是没有接触过这块,所以得搞清楚都须要有什么常识储备,那就先从这里开始介绍.

常识储备

整体上,插件开发这个货色没那么难,须要的技术也比较简单.基本上就是前端那些常识:

  • JavaScript
  • HTML + CSS

外加一些软技能

  • chrome devtools的应用和根本的调试能力
  • 文档浏览能力

基本上做过我的项目的前端都能hold住.

如果你在开发之前有些不确定的问题的话须要提前理解的话,也能够看这篇文档高频问题Q&A.

介绍完了常识储备,咱们就得看文档了.

文档举荐

开发文档举荐看这份官网文档 加上一些 官网demo.

接触新的技术,文档必定是不可少的,然而网上各种教程 + 文档,泥沙俱下,咱们到底应该看教程还是看文档.
我集体了解是,不论教程还是文档,都能够看,然而要优先看官网的文档和教程,看一手的材料,因为我看文档的过程中发现,非官方的货色(非一手文档)信息传播不残缺,甚至有纰漏.所以尽着官网的来,除非有很好的非官方教程或者其余的起因.

也有一份非官方文档不错,然而不举荐间接看,倡议联合官网文档作为对照来看,因为我在开发的时候发现这份文档有些内容和官网最新的文档不统一(比方对于browser actionpage action的定义).

另外,在理论的浏览过程中,还会有些中央看文档看不明确的,这时候有针对的搜一些博客/教程就能够.比方这次组件通信的中央有困惑,就找了些不错的文档看.

  • 一篇文章教你顺利入门和开发chrome扩大程序(插件)(文章中的代码有笔误,不要间接拿来用)
  • Chrome插件中 popup,background,contantscript消息传递机制

还有一些别的文章,都整顿下来能够参考.

  • 官网文档(举荐) | 官网Demo
  • 举荐的非官方文档
  • 插件通信相干辅助

    • 一篇文章教你顺利入门和开发chrome扩大程序(插件)
    • Chrome插件中 popup,background,contantscript消息传递机制
  • 其余

    • Chrome插件开发全攻略
    • 调试相干 | Debugging extensions
    • 打包公布相干 | 创立和公布自定义 Chrome 利用和扩大程序

看完文档咱们大抵会失去一些信息.上面就来讲讲.

构造组成

UI组成

在这一部分你只须要理解一个插件会由哪些局部组成就能够了,具体性能前面介绍.

失常状况下咱们看到的浏览器和插件大略长这样

当咱们鼠标点击插件icon之后就变成了这样

从下面的图上来看,次要由 icon(任务栏的图标) 和 点击之后的弹窗两个大件组成,局部插件还会有一个专门的用于配置的页面(这里没用到,就不介绍了).

另外,在UI之外,还会backgroundcontent_script存在.
再加上这两个重要的概念之后整个构造就是这样的


上面看看文件构造.

文件构造

文件构造比拟能直观的反馈组件的组成和性能.从文件构造来看。个别一个插件的文件构造长这样的(之所以说个别是因为不是每个插件都能用到全副的性能).

qzone_helper_extension│  background.js│  manifest.json│  popup.html│  popup.js│  qq_icon.png└─ content_script.js

其中各个文件的性能如下:

  • manifest.json是咱们最先须要理解的局部,这个文件的性能相似于package.json文件,这里定义插件的配置信息,这些信息小到插件名称,图标,版本号等形容信息,大到你须要申请的权限和各种引入的资源,入口文件等重要配置。所以这里应该是咱们最先须要理解的局部。
  • qq_icon.png是最不值得咱们了解的局部,这就是个图标文件而已。
  • popup.html是比拟直观的一个文件,这个就是你点击插件图标之后弹出来的界面,那个界面是html写的,同样的,对应的popup.js用来解决popup页面的交互和popup模块和别的模块之间的通信。
  • background.jscontent_script.js是外围.其中,background.js能够了解为我的项目的app.js文件,而content_script.js是和网页是一伙的,能够简略的了解为是咱们在网页那边的代理,帮咱们做些网页相干的操作.

加上这些构造和文件之间的对应关系是这样的

到这里,你大略理解的一个插件的根本组成构造.理解完构造,咱们分来看看各局部的具体情况.

插件各局部性能

Icon(按钮)

一般来说,Icon是咱们插件的性能入口,也能够显示一些徽标.
在chrome插件中,这种icon按钮是分两种的:

  • page action
  • browser action

他们的区别与应用场景如下

类型反对性能利用场景
page action不能应用徽章(就是揭示你有多少未读音讯的红点和数字)非应用在所有页面的状况,比方我这次开发的QQ空间小助手就只是利用在user.qzone.qq.com的一个插件,应用的就是page action
browser action领有残缺的性能(tooltip / popup / badges 对于这些性能不再赘述)对所有页面都能够应用的插件,比方adblock / vimium这种

你可能有点困惑,我理解了这个区别了,然而有什么用吗?有用,在定义manifest.json的时候会用到.page action对应的定义字段是page_action,browser action对应的定义字段是browser_action.

pupop(弹出页面)

pupop这个页面次要承载简略的配置和信息展现性能.
这就是个一般的html文件,外面写你的cssjs逻辑.
须要留神的是,这里的js只能操作pupop外面的DOM.

回到咱们的需要,咱们能够在popup页面搁置一些用于触发操作的按钮.比方删除说说按钮
manifest.json

这个文件很重要,然而一上来就放这个文件,让人有点摸不着头脑,所以,放在这里.

然而这个文件没必要每个字段都分明啥意思,搞清楚你用到的就行了.

{  // Require  "manifest_version": 2, // 不同的manifest版本会有不同的性能  "name": "My Extension",  "version": "versionString",  // Recommended  "default_locale": "en",  "description": "A plain text description",  "icons": {...}, // icon文件门路  // Pick one (or none)  "browser_action": {...},  "page_action": {...},  // Optional  "action": ...,  "author": ...,  "automation": ...,  "background": { // background对应的配置    // Recommended    "persistent": false,    // Optional    "service_worker":  },  "chrome_settings_overrides": {...},  "chrome_ui_overrides": {    "bookmarks_ui": {      "remove_bookmark_shortcut": true,      "remove_button": true    }  },  "chrome_url_overrides": {...},  "commands": {...},  "content_capabilities": ...,  "content_scripts": [{...}], // content_script对应的配置  "content_security_policy": "policyString",  "converted_from_user_script": ...,  "current_locale": ...,  "declarative_net_request": ...,  "devtools_page": "devtools.html",  "event_rules": [{...}],  "externally_connectable": {    "matches": ["*://*.example.com/*"]  },  "file_browser_handlers": [...],  "file_system_provider_capabilities": {    "configurable": true,    "multiple_mounts": true,    "source": "network"  },  "homepage_url": "http://path/to/homepage",  "import": [{"id": "aaaa"}],  "incognito": "spanning, split, or not_allowed",  "input_components": ...,  "key": "publicKey",  "minimum_chrome_version": "versionString",  "nacl_modules": [...],  "oauth2": ...,  "offline_enabled": true,  "omnibox": {    "keyword": "aString"  },  "optional_permissions": ["tabs"],  "options_page": "options.html",  "options_ui": {    "chrome_style": true,    "page": "options.html"  },  "permissions": ["tabs"], // 须要申请的权限  "platforms": ...,  "replacement_web_app": ...,  "requirements": {...},  "sandbox": [...],  "short_name": "Short Name",  "signature": ...,  "spellcheck": ...,  "storage": {    "managed_schema": "schema.json"  },  "system_indicator": ...,  "tts_engine": {...},  "update_url": "http://path/to/updateInfo.xml",  "version_name": "aString",  "web_accessible_resources": [...]}
background.js

这根本能够了解为是一个常驻后盾的js文件,在外面能够解决一些事件监听.比方监听页面初始化的事件(chrome.runtime.onInstalled),监听通信事件(chrome.runtime.onMessage).
另外,从background收回去的申请能够跨域.

在V3的实现中,background引入了service worker的概念.

在chrome的设置-更多工具-工作管理器外面能够看到咱们的background工作过程.

content_script.js

content_script文件是和浏览器关上的页面一块加载的,能够操作关上页面的DOM.
例如,点击插件的图标,而后页面扭转色彩.这种状况是不能在background.js中间接扭转页面色彩的,而是须要通过事件发送音讯到content_script.js中,通过content_script来操作页面DOM.

理解完这些咱们大略晓得了,咱们获取发申请的哪些参数包含发申请都能够在content_script中实现.因为只有之类能够操作页面的DOM.

组件通信

下面介绍完了各个组成部分,上面介绍这些局部之间的通信.
chrome extension的通信是通过事件来进行的,通信的内容是无效的JSON对象.共有三种通信形式:

  • Simple one-time requests (就像短连贯)
  • Long-lived connections (就像长连贯)
  • Cross-extension messaging (多个插件间通信)

咱们这里只开展这次用到的Simple one-time requests.
插件内的通信分为这几种:

  • content script => background
  • content script => popup
  • background => content script
  • background => popup
  • popup => content script
  • popup => background

content script => background/content script => popup/background => popup/popup => background这么发送事件

chrome.runtime.sendMessage({greeting: "hello"}, function(response) {  console.log(response.farewell);});
  1. content script发送的事件popupbackground都能够收到,所以再发送的时候须要加上发给谁的标识,而后收到音讯的时候解决
  2. popup => background的通信其实也能够通过chrome.extension.getBackgroundPage()来获取到background的所有办法,间接调用

background => content script/popup => content script这么发送事件

chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {  chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {    console.log(response.farewell);  });});
  1. 因为可能关上了多个同一个url的tab,所以须要辨别tab.
  2. 通过chrome.tabs.sendMessage发送的事件只有指定页面的content能够收到

而对于事件的监听解决是一样的

chrome.runtime.onMessage.addListener(  function(request, sender, sendResponse) {    console.log(sender.tab ?                "from a content script:" + sender.tab.url :                "from the extension");    if (request.greeting == "hello")      sendResponse({farewell: "goodbye"});  });

整个通信用一张图示意就是

加上通信的局部,咱们大略分明了,咱们在popup中触发操作,而后popup发送事件给content_script,content_script开始获取申请参数,申请数据列表,而后遍历数据做删除操作.整个过程大略就这样,没用到background.

开发调试

开发时候如何运行插件

在地址栏关上chrome://extensions/,而后开启右上角的开发者模式

而后点击加载已解压的拓展程序,在关上的文件抉择框中抉择你的目录就算装置你开发的插件了.

如何关上控制台

background / popup / content script的控制台都是独立的,能够通过上面的形式关上

  • 关上background的控制台
    chrome://extensions/页面,点击对应的插件背景页三个字即可关上background的控制台

  • 关上popup的控制台
    点击icon在弹出的popup页面上右键,而后点击查看,就能够关上popup的控制台.须要留神的是popup页面一旦敞开,控制台也会随之敞开.

  • 关上content script的控制台
    content script的控制台其实就是tab页的控制台,然而须要切换一下

如何debug

插件的调试和一般前端开发的debug形式是一样的.

更多详情能够参考文档:Debugging extensions

打包公布

咱们的插件开发实现之后能够抉择打包成crx文件公布到chrome 网上利用店,上传到chrome是须要注册开发者的,注册账号须要5$,注册之后能够公布多个chrome插件.

更多详情能够参考文档创立和公布自定义 Chrome 利用和扩大程序



打包性能在加载已解压的拓展程序按钮的旁边,而后依照提醒走就能够了

也能够间接打成压缩包放到网上让他人下载应用,之前能够本地装置crx文件,当初不反对了,都是通过加载已解压的拓展程序在本地应用.

问题解决

chrome.tabs.query失去空的tabs

开发的过程中发现有的时候chrome.tabs.query失去的后果为[],起初发现这是chrome的一个bug,在2015年就存在了,始终没有修复.解决办法如下:

var activeTabId;chrome.tabs.onActivated.addListener(function(activeInfo) {  activeTabId = activeInfo.tabId;});// https://bugs.chromium.org/p/chromium/issues/detail?id=462939function getActiveTab(callback) {    chrome.tabs.query({currentWindow: true, active: true}, function (tabs) {        let tab = tabs[0];        if (tab) {            callback(tab.id);        } else {            chrome.tabs.get(activeTabId, function (tab) {                if (tab) {                    callback(tab.id);                } else {                    console.log('No active tab identified.');                }            });        }    });}

更多相干探讨见: Why doesn't chrome.tabs.query() return the tab's URL when called using RequireJS in a Chrome extension?

让插件只在特定页面可用性能实现

需要是在某些页面插件的icon才亮起来,点击icon才会展现popup页面.
这个性能的实现思路比拟多,这里讲两种
一种是通过监听conect事件,在事件的解决办法中setIcon和初始化对应的popup页面.
Vimium是这么做的,能够看这里.
还有一些利用chromeonPageChanged事件的规定来实现,这种解决只有在合乎规定的时候才展现popup页面.

chrome.runtime.onInstalled.addListener(function() {    chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {        chrome.declarativeContent.onPageChanged.addRules([{            conditions: [                new chrome.declarativeContent.PageStateMatcher({                    pageUrl: {                        hostContains: 'qzone.qq.com'                    }                })            ],            actions: [new chrome.declarativeContent.ShowPageAction()]        }]);    });});

操作插件(点击或者别的操作)没有响应

这种状况要注意插件治理页面你的插件那里有没有报错提醒,像这样

这里的报错必须点进去革除了,能力持续向下进行,不然就会呈现操作没有响应的状况.