這天,在逛github(就是划水)的時候,忽然想看看某個倉庫的star走勢,可是在star列表中翻了半天愣是沒找到相應的功能。因而乎,谷歌一搜,發現有個叫Star History的谷歌插件,然而居然要收費。。。html
因而,又接着搜索,發現了這個倉庫。好巧的是,這個倉庫就是那個插件的源碼。稍微瞅了下源碼,感受我也能行?node
因爲以前就想學學怎麼寫chrome插件,本着學習的態度和好奇心驅使(都是划水,沒有什麼不一樣),因而也作了一個能夠查看倉庫Star趨勢的插件。效果以下:react
因爲也是第一次寫Chrome插件,做爲小白,就先搜搜你們都是怎麼寫chrome插件的吧。果真,一搜一大堆。。。不過,最終仍是選擇了官方文檔,畢竟是第一手資料,雖然是英文,但寫得還算通俗易懂,閱讀起來沒啥問題。webpack
這裏推薦看Getting Started,很是友好,一步步教你完成一個最簡單的修改網頁背景顏色的Chrome插件。跟着教程完成以後你就會發現,原來Chrome插件就像完成一個web項目同樣。ios
manifest.json是項目的配置文件(相似於package.json),插件所須要的一些能力(例如Storage)就在這個文件中聲明。剩下的工做,無非就是根據Chrome插件提供的API實現你想要的功能便可。git
咱們來看下要建立的項目目錄
和manifest.json
配置文件:github
├── README.md ├── dist │ └── bundle.js ├── images │ ├── trending128.png │ ├── trending16.png │ ├── trending32.png │ └── trending48.png ├── manifest.json ├── package.json ├── src │ └── injected.js └── webpack.config.js
{ "name": "Github-Star-Trend", "version": "1.0", "manifest_version": 2, "description": "Generates a star trend graph for a github repository", "icons": { "16": "images/trending16.png", "32": "images/trending32.png", "48": "images/trending48.png", "128": "images/trending128.png" }, "content_scripts": [ { "matches": ["https://github.com/*"], "js": ["dist/bundle.js"] } ] }
這裏須要解釋一點,根據最一開始咱們看到的效果圖,能夠發現咱們正在瀏覽的頁面上多了一個Star Trend
按鈕。因此咱們要完成的插件須要可以往頁面注入一個按鈕,而這正是經過manifest.json
中的content_scripts
字段實現的。它容許咱們往matches
字段匹配的網頁中注入js
字段中的腳本文件。web
所以,上面的配置意思很簡單,就是在匹配到url是https://github.com/*
的網頁時,注入咱們dist目錄下的bundle.js
文件。而bundle.js
實際上是咱們爲了在項目中用上ES6而採用webpack編譯獲得的,源碼就是src/injected.js
。接下來的工做就是在咱們的src目錄下開發就好了(都是寫js,沒什麼不一樣)。chrome
在正式進入開發以前,咱們再來體驗下Github的API調用。官方文檔在這兒,概覽看完以後,通過一番搜索,終於找到咱們的主角Starring APi。json
根據這個API,咱們能夠拿到某個倉庫的Star列表。仔細看文檔,可以看到有這麼一條:
You can also find out when stars were created by passing the following custom media type via the Accept header:
Accept: application/vnd.github.v3.star+json
太棒了,這不正是咱們所需的star時間嗎?趕忙打開postman測試一把:
果真,咱們順利拿到了star倉庫的時間。不過這裏有一個問題,這個請求每次返回的個數只有30條,也就是說假如像react這樣十幾萬star的倉庫豈不是要請求3k+次。。。並且,還有另一個重要的問題,那就是Github API對調用的頻率也有限制。。。
在上面的圖片中,Response Header中告訴咱們limit
是60次,remaning
還有59次。再發幾回請求會發現,remaning
一直在持續減小。。。在翻閱了一番文檔以後,我找到了這個。
For API requests using Basic Authentication or OAuth, you can make up to 5000 requests per hour. For unauthenticated requests, the rate limit allows for up to 60 requests per hour. Unauthenticated requests are associated with the originating IP address, and not the user making requests.
其中明確提到,它會根據ip來限制API調用的頻次。對於未受權的訪問,一小時最多60次;而受權的訪問,一小時最多5000次。因此,爲了儘量避免的訪問頻次帶來的問題,咱們在請求中須要帶上access_token
。有關access_token
,你能夠在這裏申請。
通過前期的一番調研,事實證實想法確實能夠實現。咱們再來簡單理下思路:
利用chrome的元素審查功能,咱們能夠很輕鬆地找到要注入按鈕的位置,並給它綁定上相應的點擊事件。
/** * star趨勢按鈕點擊事件 */ function onClickStarTrend() { // todo: 發起請求 console.log('u click star trend'); } /** * 建立star趨勢按鈕 */ const createStarTrendBtn = () => { const starTrendBtn = document.createElement('button'); starTrendBtn.setAttribute('class', 'btn btn-sm'); starTrendBtn.innerHTML = `Star Trend`; starTrendBtn.addEventListener('click', onClickStarTrend); return starTrendBtn; }; /** * 注入star趨勢按鈕 */ const injectStarTrendBtn = () => { var newNode = document.createElement('li'); newNode.appendChild(createStarTrendBtn()); var firstBtn = document.querySelector('.pagehead-actions > li'); if(firstBtn && firstBtn.parentNode) { firstBtn.parentNode.insertBefore(newNode, firstBtn); } }; (function run() { injectStarTrendBtn(); }());
若是你已經安裝了本地的這個插件,這個時候刷新頁面你會發現多了一個Star Trend
的按鈕,點擊的時候會在控制檯打印出u click star trend
的字樣。
獲取數據首先要解決的就是構造請求url,根據文檔所示,咱們須要當前的倉庫信息。這個卻是簡單,直接上正則從當前的location.href中匹配出來便可:
const repoRegRet = location.href.match(/https?:\/\/github.com\/([^/]+\/[^/]+)\/?.*/);
而後是請求參數:
const requestConfig = {headers: {Accept: 'application/vnd.github.v3.star+json'}};
這樣,咱們就能夠用axios發起一次請求:
const url = `https://api.github.com/repos/${repoRegRet[1]}/stargazers`; axios.get(url, requestConfig).then(firstResponse => console.log(firstResponse));
查看log,咱們成功地獲取到了一個倉庫第一頁的star列表。不過,這裏有幾個問題須要解決:
第一個問題很好解決,在上面的url後面,跟上?page=n就表示請求第n頁的star數據。
第二個問題有兩種解法。一種是知道該倉庫有多少star,而後除以30(一頁返回30條數據)就能夠知道有多少頁了;還有一種方法其實API文檔已經告訴咱們了,第一次請求返回的數據已經告訴咱們有多少頁了,只不過這個數據被放在了response的headers中。其中有一個link字段:
<https://api.github.com/repositories/10270250/stargazers?page=2>; rel="next", <https://api.github.com/repositories/10270250/stargazers?page=1334>; rel="last"
以上就是link字段的一個例子,能夠看到它包含了lastPage的url地址。所以,咱們能夠再次用正則提取出來:
let totalPage = 1; const linkVal = firstResponse.headers.link; if(linkVal) { const pageRegRet = linkVal.match(/next.*?page=(\d+).*?last/); if(pageRegRet) { totalPage = Math.min(pageRegRet[1], 1333); } }
這裏有兩個坑,須要特別注意:
第三個問題其實並無完美的解決方法,經過第二個問題咱們知道最多須要發1333次請求。姑且不論服務器是否對訪問頻次是否有限制,這麼多的請求所須要的耗時其實也是不能接受的,那麼怎麼辦呢?對於一個趨勢圖,其實咱們不必用成千上萬的點來繪製,也許咱們只用10個點(能夠作成配置)來繪製就夠了。所以,咱們只要用均分的策略從[1, totalPage]中選取10個page就能夠了。看代碼:
// 最多10個請求 const URL_NUM = 10; // 構造待請求的urls const urls = new Array(totalPage - 1).fill(1).slice(0, URL_NUM - 1).map((_, idx) => { let page = idx + 2; if(totalPage > URL_NUM) { page = Math.round(page / URL_NUM * totalPage); } return {page, url: `https://api.github.com/repos/${repoRegRet[1]}/stargazers?page=${page}`}; }); // 構造請求 const requests = [ {page: 1, request: Promise.resolve(firstResponse)}, ...urls.map(item => ({page: item.page, request: axios.get(item.url, requestConfig)})) ]; // 發起請求 Promise.all(requests.map(_ => _.request)).then(responses => console.log(responses));
到這兒,請求數據的問題基本都已經解決了。不過還有一個容易忽視的坑,那就是因爲lastPage最大隻能到1333,因此當倉庫的star數大於3990時,咱們拿到的數據實際上是少於該倉庫真實的star數。所以針對這種狀況,咱們還須要調用這個API接口拿到倉庫的基本信息,也就知道了這個倉庫的總star數。
至此,咱們拿到了能夠構造趨勢圖的數據(這裏就不貼構造圖的數據的代碼,完整代碼能夠點這裏查看)。
首先,咱們把injected.js中的onClickStarTrend這個坑先給填上:
let chart = createChart(); function onClickStarTrend() { chart.show(); fetchHistoryData(location.href).then(data => { chart.ready(data); }).catch(err => { chart.fail(err); }); }
從上面的代碼中,咱們能夠看到chart須要暴露出3個方法:
因此代碼框架能夠搭成這樣:
class Chart { show() { this.node = document.createElement('div'); this.node.style = ""; // 添加合適的樣式 this.loadingNode = document.createElement('div'); this.loadingNode.innerHTML = ""; // 用一個svg動畫,增長趣味性 this.node.appendChild(this.loadingNode); document.body.appendChild(this.node); } ready(data) { this.node.innerHTML = `<div id="chart"/>`; ECharts.init(document.getElementById('chart')).setOption({ color: '#40A9FF', title: {text: 'STAR TREND'}, xAxis: { type: 'time', boundaryGap: false, splitLine: {show: false} }, yAxis: {type: 'value'}, tooltip: {trigger: 'axis'}, series: [{ data, type: 'line', smooth: true, symbol: 'none', name: 'star count' }] }); } fail(err) { this.node.innerHTML = ""; // 錯誤節點內容 } }
限於篇幅,這裏就不貼詳細的dom節點代碼,完整版能夠看這裏。而對於echarts的配置和使用,也能夠參考官網上的例子。
整個插件的製做過程,到這兒基本上就已經完了。其餘的還有網絡請求異常(例如因爲訪問頻次被限制)和設置AccessToken
沒有詳細介紹,不過這些都是錯誤處理的步驟,大致上不影響插件的使用。若是想了解更多的,也能夠直接看源碼。
回過頭再來看,此次划水也算有所收穫,既體驗了一把chrome插件開發,也學到了Github API的調用。雖然用到的都只是一些冰山一角,不過也算是開了個頭,爲之後的騷操做打下基礎。