瀏覽器擴充功能其實算是老掉牙的東西了,不過拿 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 之外
雖然這個東西很方便,但是基本上你還是得理解擴充功能之間的溝通關係,這些東西說起來有點冗長,具體來說可以參考一下官方文件:
- https://developer.chrome.com/extensions
- https://developer.mozilla.org/zh-TW/docs/Mozilla/Add-ons/WebExtensions
目前 Google Chrome 或是 Mozilla Firefox 基本上都有互相相容(不就你做什麼我做什麼),只有小部分的操作需要使用不同的方法去呼叫。而關於這一點,上述的 Vue 工具當中,也提供了一個相容性擴充,讓你可以寫一次,就支援兩種不同的瀏覽器。
當然啦,這種相容性擴充不意外的是 Mozilla 自己推出的。
首次啟動
當你第一次啟動時,你會拿到 Hello World 的範例,你只需要把 dist 安裝到 Chrome 或 Firefox 裡面就好,
npm run watch:dev
接著你會想說,雖然他提供了 popup, options 與 background 三種運作方式,但,如果我是想要在特定頁面做手腳呢?這個時候,你必須要回頭去參考官方文件,在 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 來用也是很合理的。
關於各種元件溝通的事情
首先呢,你所看到的 popup 與 options 頁面,基本上都可以看做是各別獨立的 Vue Instance 來看。所以,倘若你的 content.js 需要跟 popup 溝通,那麼你需要的是靠 background.js 來當中間人。
這個中間人可以幫你做一些溝通,利用 chrome.runtime 這一類的元件來透過瀏覽器的運作過程之間的訊息往返,具體可以使用,
chrome.runtime.onMessage.addListener用來監聽訊息。chrome.runtime.sendMessage,chrome.tabs.sendMessage用來發送訊息。
這兩件事情是比較入門款的操作,實際可以使用的行為還是請參考官方文件 runtime, tabs 說明。在官方文件總覽的 Architecture 區塊,也有大概介紹了元件之間的溝通方式,想要更瞭解細部操作的人可以閱讀一下,
回到 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,讓圖片跟影片能開燈箱預覽。然後,大概三個月後,官方改版之後圖片就可以預覽了。所以,說好的 目前沒有這個打算 呢!
但,影片預覽功能我還是留著就是了。


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