瀏覽器擴充功能其實算是老掉牙的東西了,不過拿 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
才能安裝。