最近接触了一下 chrome 扩展,也就是咱们常说的插件,发现这确实是一个好东西,使用简单的 HTML,CSS 和 JavaScript 就可为浏览器新增咱们想要的功能,而且,使用 chrome 扩展开发,不用担忧跨域等消息传递问题,还有讨厌的兼容性问题,结合操做用户页面 dom,开发的开心度可谓是可观的。下面,咱们就一块儿经过一个实例来看看 chrome 扩展开发。html
这个实例不是很复杂,是我以前一直想作的,就是 经过 chrome 扩展来同步每日阅读的不错的前端资讯。相信你们也有清晰地感觉到,前端社区在诸多社区中可谓最为活跃的,因此,坚持筛选学习的内容是比较考验耐心,也是尤其重要的,相对微信公众号,twitter 和 newsletter 等方式,我的感受 chrome 扩展也不失为一种友好的方式。前端
首先,咱们得准备一个仓库来储存咱们每日收集的资讯,最好你们也能贡献,这个好像不用想,没有比 GitHub 更为合适了的吧,而且 GitHub 提供的 REST API 应该能够帮助咱们解决一些额外的问题,查了查果真:git
POST /markdown
复制代码
能够将 text 渲染成 Markdown 文档,测试一下:github
await fetch(
'https://api.github.com/markdown',
{
method: 'POST',
body: JSON.stringify({
text: '> # hello fengshangwuqi',
})
}
)
.then(res => res.text());
复制代码
返回:web
"<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 修改下样式,增长一些切换资讯等功能,初始版本应该算完成了吧。如今想一想感受较简单,不过,在实际演练的过程当中,应该会发散一些问题。chrome
因而,当即建立了仓库 Daily-Front-End-News。json
先从 chrome 开发文档 复制配置模板:api
{
"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"
}
}
复制代码
咱们要从 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 的数据将再也不是请求获得的数据。
在上面,咱们已经发现了一个不错的 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 文件,但若是有隐藏域名的需求也说不许了。
接下来的问题主要是处理 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 初始版本就没有什么阻塞项了,目前截图以下:
目前该扩展处于内测阶段,还没有发布,若是大伙中有人有兴趣,chrome-Daily-Front-End-news 仓库期待收到你的 PR,Daily-Front-End-News 同上。