瀏覽器擴充功能其實算是老掉牙的東西了,不過拿 Vue 來開發算是挺方便的一件事情。當然,在這裡你可以把 Vue 換成其他的熱門工具,不過我剛好會寫一點 Vue 所以就拿這個來開發一點小玩具,也是挺合理的。

前置作業

首先呢,你可能會需要一個 Vue 的工具方便作業,在 github 上面,已經有人提供了完整的 Vue Template 可以使用,所以,最快的方式就是用 Vue CLI 的方式去安裝,

vue init kocal/vue-web-extension my-extension

然後,我們的第一個擴充功能就這樣做完了呢,裝起來就會 Hello World 了耶!好棒棒!

這個 Template 本身支援的項目有,

  • Mozilla's web-extension polyfill
  • Vuex
  • Vue Router
  • Axios
  • ESLint
  • Prettier

除了 Vue 之外

雖然這個東西很方便,但是基本上你還是得理解擴充功能之間的溝通關係,這些東西說起來有點冗長,具體來說可以參考一下官方文件:

目前 Google Chrome 或是 Mozilla Firefox 基本上都有互相相容(不就你做什麼我做什麼),只有小部分的操作需要使用不同的方法去呼叫。而關於這一點,上述的 Vue 工具當中,也提供了一個相容性擴充,讓你可以寫一次,就支援兩種不同的瀏覽器。

https://github.com/mozilla/webextension-polyfill

當然啦,這種相容性擴充不意外的是 Mozilla 自己推出的。

首次啟動

當你第一次啟動時,你會拿到 Hello World 的範例,你只需要把 dist 安裝到 Chrome 或 Firefox 裡面就好,

npm run watch:dev

接著你會想說,雖然他提供了 popup, optionsbackground 三種運作方式,但,如果我是想要在特定頁面做手腳呢?這個時候,你必須要回頭去參考官方文件,在 manifest.json 需要額外做出一些設定,

// 你需要參考 content_scripts 章節
"content_scripts": {
    "matches": [
        "*://*"
    ],
    "js": [
        "content.js"
    ],
    "css": [
        "content.css"
    ]
}

然後你在 src/ 目錄底下,就能新增一個 content.js 的檔案,來做你想要做的事情。不過,你可能會發現,即便在 src/ 放好了 content.js,但是好像不會動?是,你還是需要去修改 webpack.config.js 檔案,要把 content.js 放入 entry 區段中。

entry: {
    'background': './background.js',
    'popup/popup': './popup/popup.js',
    'options/options': './options/options.js',
    // 你需要新增在這邊
    'content': './content.js'
}

關於 content.js 與 content.css

如果你的 content.js 當中,有使用到 Vue Components 的話,他會自動把樣式全部打包到 content.css 底下,這一點相當方便。

import Vue from 'vue';
import MyComponent from './myComponent.vue';
// ... 後略

那麼,有一個重點是,你的 Vue 需要一個進入點來做 $mount。所以,你還是需要先做一些事情,才能讓 Vue 有東西可以 $mount。所以我們在 content.js 裡面要做一點東西。

import jq from 'jquery';
import Vue from 'vue';
import MyComponent from './myComponent.vue';

// ... 中略

$('<div id="app"></div>').appendTo($('body'));

new Vue({
    el: '#app',
    render: h => h(myComponent)
});

所以啊,為了省一點寫 JavaScript 的工,拿 jQuery 來用也是很合理的。

關於各種元件溝通的事情

首先呢,你所看到的 popupoptions 頁面,基本上都可以看做是各別獨立的 Vue Instance 來看。所以,倘若你的 content.js 需要跟 popup 溝通,那麼你需要的是靠 background.js 來當中間人。

https://developer.chrome.com/extensions/background_pages

這個中間人可以幫你做一些溝通,利用 chrome.runtime 這一類的元件來透過瀏覽器的運作過程之間的訊息往返,具體可以使用,

  • chrome.runtime.onMessage.addListener 用來監聽訊息。
  • chrome.runtime.sendMessage, chrome.tabs.sendMessage 用來發送訊息。

這兩件事情是比較入門款的操作,實際可以使用的行為還是請參考官方文件 runtime, tabs 說明。在官方文件總覽的 Architecture 區塊,也有大概介紹了元件之間的溝通方式,想要更瞭解細部操作的人可以閱讀一下,

https://developer.chrome.com/extensions/overview

回到 Vue 本身

既然解決了溝通方式,那麼原本就使用 Vue 開發的人應該就沒有什麼困難點了。比較不一樣的地方在於,擴充功能本身的溝通,即便你本身有使用 Vuex 之類的工具,你在整個 App 裡面,還是要有地方去監聽從其他地方送回來的東西(如果你有需要的話)。

// 也就是說,你可能需要在 mounted 放入一些監聽動作
mounted () {
    chrome.runtime.onMessage.addListener(data => {
        // 我拿到別的地方送來的 data
        // 然後我可以更新 Vuex 或是其他的東西
    });
}

至此,你倘若有使用 Vuex 的話,就可以不用每一個元件的 mounted 都放監聽動作,否則,在你需要監聽更動或是需要的地方,都還是得加入這件事情,不然元件本身應該是不會反應的。若是不想,那麼拋去給 EventBus 也是可以,但是操作方法雷同,使用 Vuex 還是比較省事一點。

關於 Polyfill

上述所提到的 chrome.runtime 是特別針對 Google Chrome 所使用,如果你是在 Mozilla Firefox 開發的話,那麼你可能需要使用 browser.extension 來呼叫。如果你有安裝相容性擴充的話,你只要寫成這樣,

browser = require('webextension-polyfill');
browser.runtime.onMessage.addListener(data => {
    // ... 中略
});

這樣就可以了。而你在 Vue Template 當中,看他的範例會這樣寫,

global.browser = require('webextension-polyfill');

所謂的 global 在這個開發工具中,等同於最外部的 window 的意思,換句話說,就是在全域中放入一個叫做 browser 的東西。那麼,你在任何地方使用 global.browser,就等同於 window.browser 的意思。

當然,popup, options 爾或是你自行使用的 content.js,每一個 global.browser 都是不一樣的。寫了那麼多 JavaScript 的你,應該不會覺得 window 在什麼地方都『通用』吧?

寫在文末

會寫這些東西,其實當初只是想做一些方便的小工具。最初的出發點是公司所使用的 Asana,雖然他可以上傳圖片、影片爾等檔案,但是,每次去點那些檔案,都會變成 下載 的動作,這一點其實有點惱人。

我知道現在 Asana 點了圖片會開燈箱,但,其實我當初有去問過 Asana 團隊,問他們有沒有打算做圖片開燈箱可以預覽這件事情,官方的說法是,目前沒有這個打算(大概 3 年前?)

然後,我做了一個 Google Extension,讓圖片跟影片能開燈箱預覽。然後,大概三個月後,官方改版之後圖片就可以預覽了。所以,說好的 目前沒有這個打算 呢!

但,影片預覽功能我還是留著就是了。

Sprint 點數計算機
MP4 / MOV 檔案直接預覽不下載

ps. Firefox 的附加元件,如果你要安裝自行開發的版本,需要進入 about:debugging 才能安裝。