chrome插件开发 - github仓库star趋势图

1. 前言

这天,在逛github(就是划水)的时候,忽然想看看某个仓库的star走势,可是在star列表中翻了半天愣是没找到相应的功能。因而乎,谷歌一搜,发现有个叫Star History的谷歌插件,然而居然要收费。。。html

因而,又接着搜索,发现了这个仓库。好巧的是,这个仓库就是那个插件的源码。稍微瞅了下源码,感受我也能行?node

因为以前就想学学怎么写chrome插件,本着学习的态度和好奇心驱使(都是划水,没有什么不一样),因而也作了一个能够查看仓库Star趋势的插件。效果以下:react

效果图

2. 准备工做

2.1 chrome插件简单入门

因为也是第一次写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

2.2 Github API

在正式进入开发以前,咱们再来体验下Github的API调用。官方文档在这儿,概览看完以后,通过一番搜索,终于找到咱们的主角Starring APijson

根据这个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测试一把:

postman-example

果真,咱们顺利拿到了star仓库的时间。不过这里有一个问题,这个请求每次返回的个数只有30条,也就是说假如像react这样十几万star的仓库岂不是要请求3k+次。。。并且,还有另一个重要的问题,那就是Github API对调用的频率也有限制。。。

postman-rate-limit

在上面的图片中,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,你能够在这里申请。

3. 开工

通过前期的一番调研,事实证实想法确实能够实现。咱们再来简单理下思路:

  1. 根据页面的dom结构,找到注入Star Trend按钮的位置(injected.js)
  2. 给Star Trend按钮绑定点击事件,发起获取Star时间的请求,收集数据(fetchHistoryData.js)
  3. 根据返回的数据,利用echart.js绘制趋势图(createChart.js)

3.1 injected.js

按钮dom结构

利用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的字样。

3.2 fetchHistoryData.js

获取数据首先要解决的就是构造请求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列表。不过,这里有几个问题须要解决:

  1. 如何获取第2页,第3页,第N页的star列表?
  2. 如何知道一个仓库有多少页star(即N是多少)?
  3. 当一个仓库的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);
  }
}
复制代码

这里有两个坑,须要特别注意:

  1. 当star数只有1页时,link字段是没有的,因此这里须要判断一下;
  2. 不知道什么缘由,lastPage的值最大是1334(即便仓库有十几万的star),且当page=1334发起请求时会失败。所以,totalPage最大也只能是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数。

至此,咱们拿到了能够构造趋势图的数据(这里就不贴构造图的数据的代码,完整代码能够点这里查看)。

3.3 createChart.js

首先,咱们把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个方法:

  1. show:展现loading状态
  2. ready:展现图表
  3. fail:展现错误信息

因此代码框架能够搭成这样:

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的配置和使用,也能够参考官网上的例子

4. 完结

整个插件的制做过程,到这儿基本上就已经完了。其余的还有网络请求异常(例如因为访问频次被限制)和设置AccessToken没有详细介绍,不过这些都是错误处理的步骤,大致上不影响插件的使用。若是想了解更多的,也能够直接看源码

回过头再来看,此次划水也算有所收获,既体验了一把chrome插件开发,也学到了Github API的调用。虽然用到的都只是一些冰山一角,不过也算是开了个头,为之后的骚操做打下基础。

5. 参考

  1. chrome插件官方文档
  2. timqian/star-history
  3. Github API rate limiting
  4. Github API - starring
  5. Github API - repos

本文全部代码托管在这儿,喜欢的能够给个star

相关文章
相关标签/搜索