整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

如何开发一款自己的chrome扩展插件

如何开发一款自己的chrome扩展插件

到现如今最流行的浏览器,那么一定是chrome,无论是它的速度,还是它的稳定性,还是它的简洁,都让人爱不释手,此外,更多的人选择它的理由是它有着丰富的扩展插件,这些扩展插件让你的浏览器变得异常强大,让你的浏览器不仅仅是浏览器。

chrome扩展结构

chrome的扩展是以.crx结尾的安装包,如果你把它下载下来,并把它重命名为.rar压缩包文件,然后你就可以使用压缩软件对它进行解压,加压之后,就会发现其实chrome的扩展包里面就是一些js,css,html文件,可以说你只要会写前端,那么开发一个chrome扩展插件将会非常容易。

在这些文件中,有一个manifest.json文件,它是扩展的描述文件,定义了扩展的名称和版本号等信息。

{
    "name": "BrowserActionExtension",
    "version": "0.0.1"
    "manifest_version": 2,
    "browser_action": {
        "default_title": "That's the tool tip",
        "default_popup": "popup.html"
    }
}

在这个配置文件中,你还可以添加其它属性,只要你的扩展需要的属性,你都可以在这里添加配置。

每一个扩展都有一个被浏览器运行的背景页,此外还有事件页面,背景页面一直都是激活状态,而事件页面只是在触发事件的时候才会激活,因此为了节省内存和提高浏览器的性能,尽可能选择事件页面。两者通过persistent属性进行区分。

"background": {
    "scripts": ["background.js"],
    "persistent": false/true
}

当我们的扩展想要访问浏览器当前页面的dom树的时候,我们需要使用内容脚本,这些脚本会在页面刷新的时候执行。

"content_scripts": [
    {
        "matches": ["https://*/*", "https://*/*"],
        "js": ["content.js"]
    }
]

对于扩展的UI界面,我们可以通过browser_action属性进行配置,通过此属性,我们可以设置扩展的图标,设置点击弹出的页面。

"browser_action": {
    "default_icon": {
        "19": "icons/19x19.png",
        "38": "icons/38x38.png"
    },
    "default_title": "That's the tool tip",
    "default_popup": "popup.html"
}

除了browser_action可以配置扩展图标之外,page_action可以配置图标,两者的区别是,browser_action总是显示在扩展栏,而page_action则是满足一定条件才会显示,比如页面有vue脚本时候才会显示vue调试图标。

"page_action": {
    "default_icon": {
        "19": "images/icon19.png",
        "38": "images/icon38.png"
    },
    "default_title": "Google Mail",
    "default_popup": "popup.html"
}

chrome被开发人员所喜爱的另一个原因是它提供了非常强大的调试工具栏,而我们的扩展也是可以加入到调试工具栏的。

通过使用devtools_page属性,我们就可以将我们的扩展加入到调试工具栏的一个tab中。

"devtools_page": "devtools.html"

我们在devtools.html中只需要添加一个js引入语句就可以。

<script src="devtools.js"></script>

在devtools.js文件里,我可以可以放入我们实际的扩展内容。

chrome.devtools.panels.create(
    "MyExtension", 
    "img/icon16.png", 
    "index.html",
    function() {

    }
);

扩展能够做什么

扩展能够做什么主要取决于浏览器为我们提供了哪些API,庆幸的是,chrome为我们提供了足够多好用的API。

  • 我们可以操作用户的书签和浏览记录
  • 我们可以控制下载,管理下载内容
  • 我们可以监听网络请求,监听事件响应
  • 我们可以修改界面样式,可以添加自定义css
  • 我们可以在页面添加想要的元素

总之,chrome几乎为我们提供了完整控制浏览器的扩展api,正是有了这些api,才诞生了几十万的扩展插件。

扩展的调试

在我们本地开发好扩展之后,我们可以通过本地浏览器进行调试。

首先,我们需要先进入扩展程序页面,打开开发者模式

然后,我们可以通过选择加载已解压的扩展程序加载我们的扩展。

最后,我们通过在控制台输出调试信息来调试我们的扩展。

完整的示例

manifest.json

{
    "name": "BrowserExtension",
    "version": "0.0.1",
    "manifest_version": 2,
    "description" : "Description ...",
    "icons": { "16": "icons/16x16.png", "48": "icons/48x48.png", "128": "icons/128x128.png" },
    "omnibox": { "keyword" : "yeah" },
    "browser_action": {
        "default_icon": { "19": "icons/19x19.png", "38": "icons/38x38.png" },
        "default_title": "That's the tool tip",
        "default_popup": "browseraction/popup.html"
    },
    "background": {
        "scripts": ["background.js"],
        "persistent": false
    },
    "chrome_url_overrides" : {
        "newtab": "newtab/newtab.html"
    },
    "content_scripts": [{
        "matches": ["http://*/*", "https://*/*"],
        "js": ["content.js"]
    }],
    "devtools_page": "devtools/devtools.html"
}

background.js

// omnibox
chrome.omnibox.onInputChanged.addListener(function(text, suggest) {
    suggest([
      {content: "color-divs", description: "Make everything red"}
    ]);
});
chrome.omnibox.onInputEntered.addListener(function(text) {
    if(text=="color-divs") colorDivs();
});

chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
    switch(request.type) {
        case "color-divs":
            colorDivs();
        break;
    }
    return true;
});

chrome.extension.onConnect.addListener(function (port) {
    port.onMessage.addListener(function (message) {
        switch(port.name) {
            case "color-divs-port":
                colorDivs();
            break;
        }
    });
});

// send a message to the content script
var colorDivs=function() {
    chrome.tabs.getSelected(null, function(tab){
        chrome.tabs.sendMessage(tab.id, {type: "colors-div", color: "#F00"});
        // setting a badge
        chrome.browserAction.setBadgeText({text: "red!"});
    });
}

popup.html

<script type="text/javascript" src="popup.js"></script>
<div style="width:200px">
    <button id="button">Color all the divs</button>
</div>

popup.js

window.onload=function() {
    document.getElementById("button").onclick=function() {
        chrome.extension.sendMessage({
            type: "color-divs"
        });
    }
}

devtools.html

window.onload=function() {
    var port=chrome.extension.connect({ name: "color-divs-port" });
    document.getElementById("button").onclick=function() {
        port.postMessage({ type: "color-divs"});
    }
}

content.js

chrome.extension.onMessage.addListener(function(message, sender, sendResponse) {
    switch(message.type) {
        case "colors-div":
            var divs=document.querySelectorAll("div");
            if(divs.length===0) {
                alert("There are no any divs in the page.");
            } else {
                for(var i=0; i<divs.length; i++) {
                    divs[i].style.backgroundColor=message.color;
                }
            }
        break;
    }
});

总结

chrome浏览器的扩展开发其实并不难,用到的知识都是基础的js,html,css,我们只需要知道一些和浏览器交互的属性和操作的api,就可以开发出一个属于自己的浏览器扩展。

技术者写文章,基本少不了Markdown了,但是很多自媒体平台(大而全那种),往往都是坑爹的富文本编辑器(还很多是魔改UEditor,人家官方三年没更新了喂)。

小白学逻辑,内行看门道。

类似这种:

这是很麻烦的一件事,尤其是那些没有代码块的编辑器,没错,说的就是你,头条!这种坑爹玩意儿,就得让程序员手动粘贴代码过来,然后遇到排版不友好的,呵呵,对,说的还是你,头条! 于是吧,我就想着,奶奶个熊,没有我就自己写个插件来搞吧。

事实上,我自己的网站上有自己依赖marked做的一套编辑器,还挺好用,但是由于图床问题,还是得每次把富文本粘贴到头条后,删除图片,重新上传,没办法,穷是本命。 咳咳,最后做出来了,但是发现,没卵用……喵的,Markdown有代码块,人家富文本还是不支持啊……总之写出来分享下方案与思路。


框架

manifest.json 配置

这要是看下content_scripts,这个说是scripts,你也可以看到,是可以塞一些css进去的,不过这里就看js。 util.js主要提供一个编辑时候使用的函数,作用是避免每次编辑触发input都转义Markdown2HTML,也就是debounce消抖了。

核心如下(附带throttle节流):

然后是turndown.js,这个是marked.js的反向。marked是把Markdown2HTML,那么turndown就是把HTML2Markdown了。这种东西当然是轮子了,安全好用(npm)。

至于content/index.js,就是核心页面插入的js(不是注入inject,这俩有差,这里不细说),就是document有了就运行的函数,一般都是document_start。这个等下结合插件的js说。

这个文件最后就是看popup.html,这个文件名随意区,作用是点击插件显示的那个小窗户,拿FeHelper看就是这样的:

看下内容:

常规内容,长这样:

就一个输入框和header,没了,监听这个输入框变化。

然后引入js,marked.js就不用说了,popup.js就是这个页面核心js了,下面细说。

到这里,功能页面与资源齐全了(不算icon什么的)。


逻辑

  1. 插件的页面输入内容要同步到网页的输入框里面,而且由于网页的输入框是富文本,所以得是Markdown2HTML化之后的HTML字符;
  2. 网页启动时候,由于content/index.js加载早于富文本生成,所以想办法获取到富文本的标签;
  3. 网页启动时候,如果有草稿,得把草稿内容HTML2Markdown给插件输入框;
  4. 基于3,得提示用户在传HTML2Markdown之前,打开popup页面(插件页面),不然传给鬼了(插件页面打开关闭都是重新运行页面)。

一共上面4个核心问题处理,这个简易版插件就完成了(虽然没什么卵用)。

问题1

popup.js

具体都是chrome插件的api,主要看逻辑即可。

问题2,3,4

content/index.js

没错,灵魂是哪个alert,YES!


效果

bug是有的,因为我也没去优化,反正也没用。而且头条这富文本标签挺奇葩的,得去魔改下marked.js才行。

主要是分享下逻辑,以及熟悉下chrome的api。

简单的知识点搭配合适的业务场景,往往能起到意想不到的效果。这篇文章会用三个最基础人人都知道的前端知识来说明如何助力运营小姐姐、公司48+前端开发同学的日常工作,让他们的工作效率得到极大地提升。

看完您可以会收获

  1. 用vue从零开始写一个chrome插件
  2. 如何用Object.defineProperty拦截fetch请求`
  3. 如何使用油猴脚本开发一个扩展程序
  4. 日常提效的一些思考

油猴脚本入门示例

因为接下来的两个小工具都是基于油猴脚本来实现的,所以我们提前先了解一下它

油猴脚本是什么?

油猴脚本(Tampermonkey)是一个流行的浏览器扩展,可以运行用户编写的扩展脚本,来实现各式各样的功能,比如去广告、修改样式、下载视频等。

如何写一个油猴脚本?

1. 安装油猴

以chrome浏览器扩展为例,点击这里先安装

安装完成之后可以看到右上角多了这个

2. 新增示例脚本 hello world


//==UserScript==// @name         hello world // 脚本名称
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://juejin.cn/* // 表示怎样的url才执行下面的代码
// @icon         https://www.google.com/s2/favicons?domain=juejin.cn
// @grant        none
//==/UserScript==(function() {
    'use strict';
  alert('hello world')
    // Your code here...
})();


复制代码

没错当打开任意一个https://juejin.cn/*掘金的页面时,都会弹出hello world,而其他的网页如https://baidu.com则不会。

到此你就完成了一个最简单的油猴脚本,接下来我们看一下用同样简单的代码,来解决一个实际问题吧!O(∩_∩)O

3行代码让SSO自动登录

问题是什么?

1. 有一天运营小姐姐要在几个系统之间配置点东西

一顿操作,终于把事情搞定了,心情美美的。

但是她心想,为啥每个系统都要我登录一次,不开心 o( ̄ヘ ̄o#)

2. 下午一觉醒来,领导让把上午的配置重新改一下(尽职的小姐姐马上开始操作)

但是让她没想到的是:上午的登录页面仿佛许久没有见到她一样,又和小姐姐来了一次亲密接触

此时,她的内心已经开始崩溃了

3. 但是这不是结束,以后的每一天她都是这种状态

痛点在哪里?

看完上面的动图,我猜你已经在替小姐姐一起骂娘了,这做的什么玩意,太垃圾了。SSO是统一登录,你们这搞的是什么东西。

是的,我的内心和你一样愤愤不平, 一样有一万个草泥马在奔腾,这是哪个sb设计的方案,简直不配做人,一天啥事也不干,尽是跳登录页,输入用户名密码点登录按钮了,久而久之,朋友间见面说的第一句话不是“你吃了吗?”,而是“你登录了吗?”。

不过吐槽完,我们还是要想想如何通过技术手段解决这两个痛点,达到只需要登录一次的目的

1. 在A系统登录之后,跑到其他系统需要重新登录。

2. 登录时效只有2小时,2小时后,需要重新登录

该如何解决?

根本原因还是公司的SSO统一登录方案设计的有问题,所以需要推动他们修改,但是这是一个相对长期的过程,短期内有没有什么办法能让我们愉快的登录呢?

痛点1: 1. 在A系统登录之后,跑到其他系统需要重新登录。已无力回天

痛点2: 2. 登录时效只有2小时,2小时后,需要重新登录已无力回天

我们不好直接侵入各个系统去改造登录逻辑,改造其登录时效,但是却可以对登录页面(示例)做点手脚

最关键的是:

  1. 用户名输入框
  2. 密码输入框
  3. 点击按钮

所以可以借助油猴脚本,在DOMContentLoaded的时候,插入一下代码,来实现自动登录,减少手动操作的过程,大概原理如下。

//==UserScript==// @name         SSO自动登录
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://*.xxx.com/login* // 这里是SSO登录页面地址,表示只有符合这个规则的才注入这段代码
// @grant        none
//==/UserScript==document.querySelector('#username').value='xxx' // 用户名
document.querySelector('#password').value='yyy' // 密码
document.querySelector('#login-submit').click() // 自动提交登录
复制代码

是不是太简单了,简单到令人发指,令人痛恨,令人想吐口水!!!,没有一点技术含量

是不是太简单了,简单到令人发指,令人痛恨,令人想吐口水!!!,没有一点技术含量

是不是太简单了,简单到令人发指,令人痛恨,令人想吐口水!!!,没有一点技术含量

是的,就这 ,第一次帮小姐姐解决了困扰她许久的问题,晚上就请我吃了麻辣烫,还夸我"技术"好(此处不是开车

试试效果

gif中前半部分没有开启自动登录的脚本需要手动登录,后半部开启了就可以自动登录了。

拦截fetch请求,只留你想要的页面

问题是什么?

前端常见的调试方式

  1. chrome inspect
  2. vconsole
  3. weinre
  4. 等等

这些方式都有各自的优缺点,比如chrome inspect第一次需要翻墙才能使用,只适用于安卓; vconsole不方便直接调试样式; weinre只适用于调试样式等。

基于这些原因,公司很久之前搞了一个远程调试工具,可以很方便的增删DOM结构、调试样式、查看请求、查看application 修改后手机上立即生效。

远程调试平台使用流程

他的使用流程大概是这样的

  1. 打开远程调试页面列表此页面包含测试环境所有人打开的调试页面链接, 多的时候有上百个

  1. 点击你要调试的页面,就可以进入像chrome控制台一样调试了

看完流程你应该大概知道问题在哪里了, 远程调试页面列表不仅仅包含我自己的页面,还包括很多其他人的,导致很难快速找到自己想要调试的页面

该如何解决?

问题解析

有什么办法能让我快速找到自己想要调试的页面呢?其实观察解析这个页面会发现列表是

  1. 通过发送一个请求获取的
  2. 响应中包含设备关键字

拦截请求

所以聪明的你已经猜到了,我们可以通过Object.defineProperty拦截fetch请求,过滤设备让列表中只存在我们指定的设备(毕竟平时开发时调试的设备基本是固定的,而设备完全相同的概率是很低的,所以指定了设备其实就是唯一标识了自己)页面。

具体如何做呢?


//==UserScript==// @name         前端远程调试设备过滤
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://chii-fe.xxx.com/ // 指定脚本生效的页面
// @grant        none
// @run-at       document-start // 注意这里,脚本注入的时机是document-start
//==/UserScript==;(()=> {
  const replaceRe=/\s*/g
  // 在这里设置设备白名单
  const DEVICE_WHITE_LIST=[
      'Xiaomi MI 8',
      'iPhone9,2',
  ].map((it)=> it.replace(replaceRe, '').toLowerCase())
  
  const originFetch=window.fetch
  const recordListUrl='record-list'
  const filterData=(source)=> {
    // 数据过滤,返回DEVICE_WHITE_LIST指定的设备的数据
    // 详细过程省略
    return data
  }
  // 拦截fetch请求  
  Object.defineProperty(window, 'fetch', {
    configurable: true,
    enumerable: true,
    get () {
      return function (url, options) {
        return originFetch(url, options).then((response)=> {
          // 只处理指定的url
          if (url.includes(recordListUrl)) {
            if (response.clone) {
              const cloneRes=response.clone()

              return new Promise((resolve, reject)=> {
                resolve({
                  text: ()=> {
                    return cloneRes.json().then(json=> {
                      return filterData(JSON.stringify(json))
                    });
                  }
                })
              })
            }
          }

          return response
        })
      }
    }
  })
})()


复制代码

试试效果

通过下图可以看出,过滤前有37个页面,过滤后只剩3个,瞬间就找到你要调试页面,再也不用从几百个页面中寻找你自己的那个啦!

助力全公司45+前端开发 - chrome插件的始与终

通过插件一键设置ua,模拟用户登录状态,提高开发效率。

先看结果

插件使用方式

插件使用结果

团队48+小伙伴也使用起来了

背景和问题

日常c端业务中有很多场景都需要用户登录后才能正常进行,而开发阶段基本都是通过chrome模拟手机设备来开发,所以往往会涉及到在chrome浏览器中模拟用户登录,其涉及以下三步(这个步骤比较繁琐)。

备注:保持用户的登录态一般是通过cookie,但也有通过header来做,比如我们公司是改写ua来做的

  1. 获取ua: 前往公司UA生成平台输入手机号生成ua
  2. 添加ua: 将ua复制到chrome devtool设置/修改device
  3. 使用ua: 选择新添加的ua,刷新页面,重新开发调试

来看一段对话

隔壁98年刚毕业妹子:

又过期了,谁又把我挤下去了嘛

好的,稍等一会哈,我换个账号测测

好麻烦哎!模拟一个用户信息,要这么多步骤,好烦呀!!!

我,好奇的大叔:

“细心”了解下,她正在做一个h5活动项目,场景复杂,涉及的状态很多,需要用不同的账号来做测试。

模拟一两个用户还好,但是此刻小姐姐测这么多场景,已经模拟了好多个(谁都会烦啊)

公司的登录体系是单点登录,一个好不容易模拟的账号,有可能别人也在用,结果又被顶掉了,得重新生成,我TM

看着她快气哭的小眼神,作为隔壁桌友好的邻居,此刻我心里只想着一件事...!帮她解决这个恼人的问题。

分析和解决问题

通过上面的介绍您应该可以感觉到我们开发阶段遇到需要频繁切换账号做测试时的烦恼,相对繁琐的ua生成过程导致了它一定是个费时费力的麻烦事。

有没有什么办法让我们的开发效率得到提升,别浪费在这种事情上呢?一起一步步做起来

需求有哪些

提供一种便捷地模拟ua的方式,助力开发效率提升。

  1. 基本诉求:本地开发阶段,希望有更便捷的方式来模拟用户登录
  2. 多账号: 一个项目需要多个账号,不同项目间的账号可以共享也可以不同
  3. 指定域: 只有指定的下才需要模拟ua,不能影响浏览器正常使用
  4. 过期处理: 账号过期后,可以主动生成,无需手动重新获取

如何解决

  1. 需求1:结合前面生成ua阶段,我们可以通过某种方式让用户能直接在当前页面生成ua,无需跳出,一键设置省略手动过程
  2. 需求2:提供多账号管理功能,能直接选中切换ua
  3. 需求3:限定指定域,该ua才生效
  4. 需求4:当使用到过期账号时,可一键重新生成即可

为什么是chrome插件

  1. 浏览器中发送ajax请求的ua无法直接修改,但是chrome插件可以修改请求的ua(很重要的一点
  2. chrome插件popup模式可直接在当前页面打开,无需跳出开发页面,减少跳出过程

用vue从零开始写一个chrome插件

篇幅原因,这里只做示例级别的简单介绍,如果您希望详细了解chrome插件的编写可以参考这里

从一个小例子开始

接下来我们会以下页面为例,说明用vue如何写出来。

基本功能

  1. 底部tab切换区域viewAviewBviewC
  2. 中间内容区域:切换viewA、B、C分别展示对应的页面

content部分

借助chrome浏览器可以向网页插入脚本的特性,我们会演示如何插入脚本并且在网页加载的时候弹一个hello world

popup与background通信部分

popup完成用户的主要交互,在viewA页面点击获取自定义的ua信息

修改ajax请求ua部分

会演示如果通过chrome插件修改请求header

1. 了解一个chrome插件的构成

  1. manifest.json
  2. background script
  3. content script
  4. popup

1. manifest.json

几乎所有的东西都要在这里进行声明、权限资源页面等等


{
  "manifest_version": 2, // 清单文件的版本,这个必须写
  "name": "hello vue extend", // 插件的名称,等会我们写的插件名字就叫hello vue extend
  "description": "hello vue extend", // 插件描述
  "version": "0.0.1", // 插件的版本
  // 图标,写一个也行
  "icons": {
    "48": "img/logo.png"
  },
  // 浏览器右上角图标设置,browser_action、page_action、app必须三选一
  "browser_action": {
    "default_icon": "img/logo.png",
    "default_title": "hello vue extend",
    "default_popup": "popup.html"
  },
  // 一些常驻的后台JS或后台页面
  "background": {
    "scripts": [
      "js/hot-reload.js",
      "js/background.js"
    ]
  },
  // 需要直接注入页面的JS
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["js/content.js"],
    "run_at": "document_start"
  }],
  // devtools页面入口,注意只能指向一个HTML文件
  "devtools_page": "devcreate.html",
  // Chrome40以前的插件配置页写法
  "options_page": "options.html",
  // 权限申请
  "permissions": [
    "storage",
    "webRequest",
    "tabs",
    "webRequestBlocking",
    "<all_urls>"
  ]
}

复制代码

2. background script

后台,可以认为是一个常驻的页面,权限很高,几乎可以调用所有的API,可以与popup、content script等通信

3. content script

chrome插件向页面注入脚本的一种形式(js和css都可以)

4. popup

popup是点击browser_action或者page_action图标时打开的一个小窗口网页,焦点离开网页就立即关闭。

比如我们要用vue做的页面。

2. 改写vue.config.js

manifest.json对文件引用的结构基本决定了打包后的文件路径

打包后的路径

// dist目录用来chrome扩展导入

├── dist
│   ├── favicon.ico
│   ├── img
│   │   └── logo.png
│   ├── js
│   │   ├── background.js
│   │   ├── chunk-vendors.js
│   │   ├── content.js
│   │   ├── hot-reload.js
│   │   └── popup.js
│   ├── manifest.json
│   └── popup.html

复制代码

源码目录


├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   └── js
│       └── hot-reload.js
├── src
│   ├── assets
│   │   ├── 01.png
│   │   ├── disabled.png
│   │   └── logo.png
│   ├── background
│   │   └── background.js
│   ├── content
│   │   └── content.js
│   ├── manifest.json
│   ├── popup
│   │   ├── App.vue
│   │   ├── main.js
│   │   ├── router.js
│   │   └── views
│   │       ├── viewA.vue
│   │       ├── viewB.vue
│   │       └── viewC.vue
│   └── utils
│       ├── base.js
│       ├── fixCaton.js
│       └── storage.js
└── vue.config.js


复制代码

修改vue.config.js

主需要稍微改造变成可以多页打包,注意输出的目录结构就可以了


const CopyWebpackPlugin=require('copy-webpack-plugin')
const path=require('path')
// 这里考虑可以添加多页
const pagesObj={}
const chromeName=['popup']
const plugins=[
  {
    from: path.resolve('src/manifest.json'),
    to: `${path.resolve('dist')}/manifest.json`
  },
  {
    from: path.resolve('src/assets/logo.png'),
    to: `${path.resolve('dist')}/img/logo.png`
  },
  {
    from: path.resolve('src/background/background.js'),
    to: `${path.resolve('dist')}/js/background.js`
  },
  {
    from: path.resolve('src/content/content.js'),
    to: `${path.resolve('dist')}/js/content.js`
  },
]

chromeName.forEach(name=> {
  pagesObj[name]={
    css: {
      loaderOptions: {
        less: {
          modifyVars: {},
          javascriptEnabled: true
        }
      }
    },
    entry: `src/${name}/main.js`,
    filename: `${name}.html`
  }
})

const vueConfig={
  lintOnSave:false, //关闭eslint检查
  pages: pagesObj,
  configureWebpack: {
    entry: {},
    output: {
      filename: 'js/[name].js'
    },
    plugins: [new CopyWebpackPlugin(plugins)]
  },
  filenameHashing: false,
  productionSourceMap: false
}

module.exports=vueConfig


复制代码

3. 热刷新

我们希望修改插件源代码进行打包之后,chrome插件对应的页面能主动更新。为什么叫热刷新而不是热更新呢?因为它其实是全局刷新页面,并不会保存状态。

这里推荐一个github上的解决方案crx-hotreload

4. 完成小例子编写

文件目录结构


├── popup
│   ├── App.vue
│   ├── main.js
│   ├── router.js
│   └── views
│       ├── viewA.vue
│       ├── viewB.vue
│       └── viewC.vue


复制代码

main.js


import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip=false

new Vue({
  router,
  render: h=> h(App)
}).$mount('#app')


复制代码

router.js

import Vue from 'vue'
import Router from 'vue-router'

import ViewA from './views/viewA.vue'
import ViewB from './views/viewB.vue'
import ViewC from './views/viewC.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      redirect: '/view/a'
    },
    {
      path: '/view/a',
      name: 'viewA',
      component: ViewA,
    },
    {
      path: '/view/b',
      name: 'viewB',
      component: ViewB,
    },
    {
      path: '/view/c',
      name: 'viewC',
      component: ViewC,
    },
  ]
})


复制代码

App.vue


<template>
  <div id="app">
    <div class="app-router">
      <router-view />
    </div>
    <div class="app-tab">
      <div class="app-tab-item" v-for="(tabName, i) in tabs" :key="i" @click="onToView(tabName)">
        {{ tabName }}
      </div>
    </div>
  </div>
</template>

<script>

export default {
  name: 'App',
  data () {
    return {
      tabs: [
        'viewA',
        'viewB',
        'viewC',
      ]
    }
  },
  methods: {
    onToView (name) {
      this.$router.push({
        name
      })
    }
  }
}
</script>

<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;

  width: 375px;
  height: 200px;
  padding: 15px;
  box-sizing: border-box;

  display: flex;
  justify-content: space-between;
  flex-direction: column;

  .app-router{
    flex: 1;
  }

  .app-tab{
    display: flex;
    align-items: center;
    justify-content: space-between;

    .app-tab-item{
      font-size: 16px;
      color: coral;
      cursor: pointer;
    }
  }
}

</style>


复制代码

viewA、viewB、viewC

三个页面基本长得是一样的,只有背景色和文案内容不一样,这里我就只贴viewA的代码了。

需要注意的是这里会演示popup与background,通过sendMessage方法获取background后台数据


<template>
  <div class="view-a">我是A页面

    <button @click="onGetCUstomUa">获取自定义ua</button>
  </div>
</template>

<script>
export default {
  name: 'viewA',
  methods: {
    onGetCUstomUa () {
      
      chrome.runtime.sendMessage({type: 'getCustomUserAgent'}, function(response) {
        alert(JSON.stringify(response))
      })
    }
  }
}
</script>

<style lang="less">
.view-a{
  background-color: cadetblue;
  height: 100%;
  font-size: 60px;
}
</style>


复制代码

background.js

const customUa='hello world ua'
// 请求发送前拦截
const onBeforeSendCallback=(details)=> {
  for (var i=0; i < details.requestHeaders.length; ++i) {
    if (details.requestHeaders[i].name==='User-Agent') {
      details.requestHeaders.splice(i, 1);
      break;
    }
  }
  // 修改请求UA为hello world ua
  details.requestHeaders.push({
    name: 'User-Agent',
    value: customUa
  });
  
  return { requestHeaders: details.requestHeaders };
}

// 前面的sendMessage获取getCustomUserAgent,会被这里监听
const onRuntimeMessageListener=()=> {
  chrome.runtime.onMessage.addListener(function (msg, sender, callback) {
    if (msg.type==='getCustomUserAgent') {
      callback({
        customUa
      });
    }
  });
}

const init=()=> {
  onRuntimeMessageListener()
  onBeforeSendHeadersListener()
}

init()

复制代码

content.js

演示如何往网页中插入代码


function setScript({ code='', needRemove=true }=params) {
  let textNode=document.createTextNode(code)
  let script=document.createElement('script')

  script.appendChild(textNode)
  script.remove()

  let parentNode=document.head || document.documentElement

  parentNode.appendChild(script)
  needRemove && parentNode.removeChild(script)
}

setScript({
  code: `alert ('hello world')`,
})


复制代码

关于一键设置ua插件

大体上和小例子差不都,只是功能相对复杂一些,会涉及到

  1. 数据本地存储chrome.storage.sync.get|setchrome.tabs.query等API
  2. popup与background通信、content与background通信
  3. 拦截请求修改UA
  4. 其他的大体就是常规的vue代码编写啦!

这里就不贴详细的代码实现了。

日常提效的一些思考

工作中咱们时长会遇到一些阻碍我们提高工作效率的问题,这些问题或许是因为老方案设计不合理、或许是因为流程又臭又长,又或许是现有功能不满足新的需求。等等,如果能做到这几点,不仅对自己的成长有所帮助,对团队也会有所贡献。

  1. 主人翁心态: 发现了问题主动尝试去解决问题,不做旁观者
  2. 保持学习力: 发现问题之后,解决方案如果不在你的知识储备范围,一定要尝试去学习新的东西(惭愧,没写一键设置UA插件之前,我自己完全没写过chrome插件),走出舒适圈,会学会更多
  3. 保持热心态:每个人遇到的问题是不一样的,主动和同事或者朋友讨论,需要时伸出你的双手
  4. 执行力:把影响效率(举例,还有其他)的事情看成魔鬼,马上行动起来,达到魔鬼,不要一拖再拖
  5. 学会推广:也许一开始你写的插件只是解决了自己的问题,但同样的工作环境,别人也许也会遇到,要学会往外分享和推广

相约再见

以上就是这篇文章的全部内容啦!愿大家晚安,下次再见。