整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          如何開發一款自己的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. 學會推廣:也許一開始你寫的插件只是解決了自己的問題,但同樣的工作環境,別人也許也會遇到,要學會往外分享和推廣

          相約再見

          以上就是這篇文章的全部內容啦!愿大家晚安,下次再見。


          主站蜘蛛池模板: 亚洲乱色熟女一区二区三区丝袜| 无码日韩精品一区二区免费暖暖| 九九久久99综合一区二区| 丝袜人妻一区二区三区网站| 色一情一乱一伦一区二区三区| 国产品无码一区二区三区在线蜜桃| 国产色欲AV一区二区三区| 国产精品第一区揄拍| 午夜性色一区二区三区免费不卡视频| 91无码人妻精品一区二区三区L| 人妻无码一区二区不卡无码av| 久久人做人爽一区二区三区| 78成人精品电影在线播放日韩精品电影一区亚洲 | 在线视频精品一区| 午夜性色一区二区三区不卡视频| 在线免费视频一区二区| 女人和拘做受全程看视频日本综合a一区二区视频| 亚洲AV无码一区二区三区电影| 蜜芽亚洲av无码一区二区三区| 日韩精品一区二区三区在线观看l| 国产伦精品一区二区三区无广告| 无码精品人妻一区二区三区AV| 久久亚洲中文字幕精品一区四| 免费一区二区三区| 精品国产福利一区二区| 国模无码视频一区二区三区| 色一情一乱一伦一区二区三区| 亚洲毛片不卡av在线播放一区| 国产视频一区在线观看| 亚洲欧美日韩中文字幕在线一区| 无码人妻精品一区二区三区不卡 | 天天看高清无码一区二区三区| V一区无码内射国产| 久久一区二区精品综合| 欧美激情一区二区三区成人| 亚洲国产高清在线一区二区三区| 国模私拍一区二区三区| 亚洲av永久无码一区二区三区| 日韩av片无码一区二区三区不卡| 中文人妻无码一区二区三区| 中文字幕乱码亚洲精品一区|