最近接觸了一下 chrome 擴充套件,也就是我們常說的外掛,發現這確實是一個好東西,使用簡單的 HTML,CSS 和 JavaScript 就可為瀏覽器新增我們想要的功能,並且,使用 chrome 擴充套件開發,不用擔心跨域等訊息傳遞問題,還有討厭的相容性問題,結合操作使用者頁面 dom,開發的開心度可謂是可觀的。下面,我們就一起通過一個例項來看看 chrome 擴充套件開發。
這個例項不是很複雜,是我之前一直想做的,就是 通過 chrome 擴充套件來同步每日閱讀的不錯的前端資訊。相信大家也有清晰地感受到,前端社群在諸多社群中可謂最為活躍的,所以,堅持篩選學習的內容是比較考驗耐心,也是尤為重要的,相對微信公眾號,twitter 和 newsletter 等方式,個人感覺 chrome 擴充套件也不失為一種友好的方式。
構思
首先,我們得準備一個倉庫來儲存我們每日收集的資訊,最好大家也能貢獻,這個好像不用想,沒有比 GitHub 更為合適了的吧,並且 GitHub 提供的 REST API 應該可以幫助我們解決一些額外的問題,查了查果然:
POST /markdown
複製程式碼
可以將 text 渲染成 Markdown 文件,測試一下:
await fetch(
'https://api.github.com/markdown',
{
method: 'POST',
body: JSON.stringify({
text: '> # hello fengshangwuqi',
})
}
)
.then(res => res.text());
複製程式碼
返回:
"<blockquote>
<h1>
<a id="user-content-hello-fengshangwuqi" class="anchor" href="#hello-fengshangwuqi" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>hello fengshangwuqi</h1>
</blockquote>
"
複製程式碼
很好,這樣,我們就可以將獲取到的 text 資料通過該 API 轉化為 markdown,然後操作 dom,新增進點選擴充套件圖示彈出的小視窗的 document 中,再用 CSS 修改下樣式,增加一些切換資訊等功能,初始版本應該算完成了吧。現在想想感覺較簡單,不過,在實際演練的過程中,應該會發散一些問題。
於是,立即建立了倉庫 Daily-Front-End-News。
準備
書寫配置檔案 manifest.json
先從 chrome 開發文件 複製配置模板:
{
"manifest_version": 2,
"name": "One-click Kittens",
"description": "This extension demonstrates a browser action with kittens.",
"version": "1.0",
"permissions": [
"https://secure.flickr.com/"
],
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
}
}
複製程式碼
- name 和 version 是必須的;
- Chrome 應用的開發者當前必須指定 'manifest_version': 2;
- permissions 宣告需要申請的許可權,比如訪問瀏覽器選項卡(tabs)、瀏覽器通知(notifications)等,可以根據需要新增,目前,我們的例項主要是從 GitHub 獲取資料並展示資料,暫時不需要申請什麼 permissions;
- browser_action 指定擴充套件的圖示放在 Chrome 工具欄中,它定義了擴充套件圖示檔案位置(default_icon)、懸浮提示(default_title)和點選擴充套件圖示所顯示的頁面位置(default_popup);
- background 是一個長時間執行的指令碼,在擴充套件的整個生命週期都存在,用於管理一些任務和狀態;
- options_page 屬性定義了擴充套件的設定頁面,配置後在擴充套件圖示點選右鍵可以看到選項 ,點選即開啟指定頁面;
- content_scripts 則是直接注入頁面的指令碼。
我們要從 GitHub 獲取資料,並在 popup.html 中展示出來,需要申請 webRequest 許可權,用於點選圖示彈出的視窗和背景頁之間的資料傳遞,並新增需要的 background 和 popup 指令碼,其中,background 指令碼在配置檔案中新增,而 popup 指令碼通過 <script>
標籤在 popup.html 引入。
訊息傳遞
當我們點選圖示,開啟 popup.html 時,popup 指令碼會去從 背景頁 獲取需要的資料,這裡就涉及到內容指令碼與擴充套件其餘部分之間的通訊。chrome 擴充套件的 資料傳遞 方式有多種,比如 簡單的一次性請求,長時間的連線,跨擴充套件程式訊息傳遞 等,目前我們用到的應該只有一次性請求。
一次性請求即使用簡單的 runtime.sendMessage 方法,向擴充套件的另一部分傳送訊息,並且它提供可選的 callback 處理迴應。如下,一條訊息就在 popup 傳送出去了:
chrome.runtime.sendMessage(
{
action: 'getNew',
},
res => {
console.log(res);
}
);
複製程式碼
在背景頁,我們使用 runtime.onMessage 事件監聽器來處理訊息。如下,我們可以迴應來自 popup 的 getNew 請求:
async function handleMessage(message, sender, sendResponse) {
switch (message.action) {
case 'getNew':
sendResponse(await getNew());
break;
default:
sendResponse(false);
}
}
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
handleMessage(message, sender, sendResponse);
return true;
});
複製程式碼
其中,sendResponse() 即背景頁的迴應,並且,某一次事件只有第一次呼叫 sendResponse() 有效,所有其他迴應將被忽略,return true 是用於處理我們的非同步請求,如果刪除,非同步請求仍能正常傳送,但返回給 popup 的資料將不再是請求得到的資料。
使用 REST API 處理資料
在上面,我們已經發現了一個不錯的 API 可以幫助我們渲染 markdown,接下來我們只需專注地思考如何獲取資料。
當然,獲取資料肯定不止一次 POST 請求那麼簡單,因為,例項若不支援檢視之前的資訊,即比如切換到昨天的資訊,那這個例項意義也不大。首先,還是先檢視一下 REST API 是怎麼獲取資料的。在 Repositories 的 Contents 目錄下,我們發現有這樣一個請求:
GET /repos/:owner/:repo/contents/:path
複製程式碼
如果這個 contents 是一個檔案,它將返回下面這樣的一個物件:
{
"type": "file",
"name": "README.md",
"path": "README.md",
...
}
複製程式碼
而如果這個 contents 是一個目錄,它將返回:
{
"type": "dir",
"name": "fileName",
"path": "folder/fileName",
...
}
複製程式碼
哈哈,這下我們就可以放心大膽地在 GitHub 存放資料了。我們可以將我們每日的資訊都記錄在 README 中,然後將之前資訊按照日期放在對應的目錄下,最終目錄結構像這樣:
history
├── 2018
│ ├── 03
│ │ └── 04
│ │ | ├── README.md
│ │ └── 05
│ │ ├── README.md
複製程式碼
然後如下獲取最新資訊的 path:
const year = await GITHUB.getContent('history');
const month = await GITHUB.getContent(
`history/${year[year.length - 1]}`
);
const day = await GITHUB.getContent(
`history/${year[year.length - 1]}/${month[month.length - 1]}`
);
const path = `${year.pop()}/${month.pop()}/${day.pop()}`;
複製程式碼
path 得到了,接下來,就可輕鬆通過上面的 API 獲取對應的 content,至此,通過 REST API 處理資料算是搞定了。下面是示例程式碼:
class API {
constructor(url, owner, repo) {
this.url = url;
this.owner = owner;
this.repo = repo;
}
/* github
* Render an arbitrary Markdown document
* */
async getMarkdown(text) {
const res = await fetch(`${this.url}/markdown`, {
method: 'POST',
body: JSON.stringify({
text,
}),
}).then(res => res.text());
return res;
}
/* github
* Get contents
* */
async getContent(path) {
const res = await fetch(
`${this.url}/repos/${this.owner}/${this.repo}/contents/${path}`,
{
method: 'GET',
}
)
.then(res => res.json())
.then(json => json.map(path => path.name));
return res;
}
}
複製程式碼
上面使用 class 對 API 做了一個封裝,並非完全出於對程式碼的整潔,還有一個重要的原因是避免重名,雖然擴充套件支援根據域名載入不同 js 檔案,但如果有隱藏域名的需求也說不準了。
打通 background 和 popup
接下來的問題主要是處理 popup 給 background 傳送訊息獲取對應的資料,在上面的準備中,其實我們已經搞定的差不多了。
思路相對也很清晰,首先,popup 傳送訊息獲取 paths,即所有可以檢視的資訊的路徑,這個路徑,相當於每條資訊的 title,上面我們已經知道如何獲取最新資訊的 path 了,我們只需做一個簡單的操作和判斷,我們將最新資訊的 path 儲存在 localStorage 中,如果 localStorage 中最新資訊的 path 不全等於實際最新資訊的 path,那麼我們就執行請求獲取實際最新資訊的 path。
看似不使用 localStorage 感覺也沒什麼,實際上,是不得不使用 localStorage。GitHub REST API 有一個 Rate Limit,它限制了每小時你傳送請求的次數,顯然,不使用 localStorage 儲存,結果將是比較尷尬的,你將在多次開啟 popup 後,得到一個尷尬的提示:
{
"message": "API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)",
"documentation_url": "https://developer.github.com/v3/#rate-limiting"
}
複製程式碼
緊接著是傳送一個新的訊息獲取當前想要檢視的資訊,訊息迴應的有資訊的 content,還有資訊的 path,資訊的 content 不用說,肯定是動態地建立 dom 新增到 popup 中,而 path 主要用於切換,之前我們得到了所有可檢視資訊的路徑,接下來,我們就可以結合 paths 和 path 決定是否可以向前或向後切換,切換核心程式碼如下:
NewsCard.getCurrNew = function (path) {
chrome.runtime.sendMessage({
action: 'getCurrNew',
path,
},
res => {
const card = document.getElementById('new-card');
card.innerHTML = `<div id="path">${res.path}</div>
<div id="left-arrow" class="card-arrow"><<<</div>
<div id="right-arrow" class="card-arrow">>>></div>
${res.text}`;
NewsCard.addListeners();
}
);
}
複製程式碼
除錯
popup 跟普通頁面一樣,右鍵檢查,background 需要進入擴充套件程式頁面,點選檢查檢視背景頁。
總結
就這樣,我們的 chrome 初始版本就沒有什麼阻塞項了,目前截圖如下:
該例項還存在很多 TODO 的內容:- 更換 icon;
- 優化 UI;
- 限制切換次數,優化儲存;
- 新增新內容,例如來源,作者等資訊;
- 新增新功能,例如檢視全部等;
- 釋出;
- ...
目前該擴充套件處於內測階段,尚未釋出,如果大夥中有人有興趣,chrome-Daily-Front-End-news 倉庫期待收到你的 PR,Daily-Front-End-News 同上。