I’ve been looking at Chrome extensions recently and found that they are a great way to add functionality to the browser using simple HTML, CSS, and JavaScript, without worrying about cross-domain messaging. There are also annoying compatibility issues, and working with the USER page DOM makes development a lot of fun. Let’s take a look at chrome extension development through an example.
This example isn’t too complicated and is something I’ve been trying to do for a while, which is to sync the nice front-end information I read every day with a Chrome extension. I believe you have clearly felt that the front-end community is the most active in many communities, so it is particularly important to insist on screening the learning content is more test patience, compared with wechat public account, Twitter and newsletter and other ways, I feel chrome extension is also a friendly way.
conceived
First of all, we need to create a repository to store the information we collect on a daily basis, preferably with contributions from all of you. There is no better place to do this than GitHub, and the REST API provided by GitHub should help us solve some additional problems.
POST /markdownCopy the code
You can render a text as a Markdown document. Test it out:
await fetch(
'https://api.github.com/markdown',
{
method: 'POST',
body: JSON.stringify({
text: '> # hello fengshangwuqi',
})
}
)
.then(res => res.text());
Copy the code
Returns:
"<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>
"
Copy the code
In this way, we can convert the obtained text data into markdown through the API, and then operate dom, add it to the Document in the small window that pops up by clicking the extension icon, modify the style with CSS, add some switching information and other functions, and the initial version should be finished. It’s easier to think about it now, but in the course of the actual practice, there should be some questions.
Therefore, the repository daily-front-end-news was immediately created.
To prepare
Write the configuration file manifest.json
Start by copying the configuration template from the Chrome development document:
{ "manifest_version": 2, "name": "One-click Kittens", "description": Demonstrates a browser action with Kittens.", "version": "1.0", "permissions": [ "https://secure.flickr.com/" ], "browser_action": { "default_icon": "icon.png", "default_popup": "popup.html" } }Copy the code
- Name and version are required;
- The Developer of a Chrome app must currently specify the ‘Manifest_version’ : 2;
- Permissions declare required permissions, such as access to browser tabs, notifications, etc., which can be added as needed. For now, our example is mainly fetching and displaying data from GitHub. Permissions are not required for the time being;
- browser_actionThe specified extension icon is placed in the Chrome toolbar, which defines the extension icon file location (default)Icon, hover prompt (default)Title) and the page position shown by clicking the extension icon (default_popup);
- Background is a long-running script that exists throughout the life of the extension to manage some tasks and state;
- The options_page property defines the Settings page of the extension. After configuration, right-click the extension icon to see the options, and click to open the specified page.
- Content_scripts is the script that is injected directly into the page.
We need to obtain data from GitHub and display it in popup.html, we need to apply for webRequest permission, which is used to transfer data between the popup window and the background page by clicking the icon, and add the required background and popup script, where, Background scripts are added in the configuration file, and popup scripts are introduced in popup.html via the script tag.
The messaging
When we click the icon to open popup.html, the PopUp script will fetch the required data from the background page, which is where the communication between the content script and the rest of the extension comes in. Chrome extensions can transfer data in a variety of ways, such as simple one-time requests, long connections, cross-extension messaging, etc. At present, we should only use one-time requests.
A one-time request sends a message to another part of the extension using the simple Runtime. sendMessage method, which provides an optional callback processing response. As follows, a message is sent from Popup:
chrome.runtime.sendMessage( { action: 'getNew', }, res => { console.log(res); });Copy the code
On the background page, we use the Runtime. onMessage event listener to process the message. We can respond to a getNew request from PopUp as follows:
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;
});
Copy the code
Where, sendResponse() is the response of the background page, and an event is only valid for the first call to sendResponse(), all other responses will be ignored. Return true is used to process our asynchronous request. If deleted, the asynchronous request will still be sent normally. However, the data returned to PopUp will no longer be the requested data.
Use REST apis to process data
Now that we’ve found a nice API to render markdown, we just need to focus on getting the data.
Of course, getting the data must be more than a POST request, because an instance doesn’t make much sense if it doesn’t support viewing previous information, such as switching to yesterday’s information. First, let’s look at how the REST API gets the data. In the Contents directory of Repositories, we find a request for Repositories:
GET /repos/:owner/:repo/contents/:pathCopy the code
If the contents were a file, it would return an object like this:
{
"type": "file",
"name": "README.md",
"path": "README.md",
...
}
Copy the code
And if the contents were a directory, it would return:
{
"type": "dir",
"name": "fileName",
"path": "folder/fileName",
...
}
Copy the code
Now we can safely store our data on GitHub. We can record our daily information in the README, and then put the previous information in the corresponding directory by date. The final directory structure looks like this:
History ├ ─ ─ 2018 │ ├ ─ ─ 03 │ │ └ ─ ─ 04 │ │ | ├ ─ ─ the README. Md │ │ └ ─ ─ 05 │ │ ├ ─ ─ the README, mdCopy the code
Then the path to get the latest information is as follows:
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()}`;
Copy the code
Now that you have your path, you can easily retrieve the content using the API above, and you’re done with the REST API. Here is the sample code:
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; }}Copy the code
The above use of class encapsulates the API, not entirely for code cleanliness, but also for the important reason of avoiding duplicate names. Although the extension supports loading different JS files based on the domain name, it is not always possible to hide the domain name.
Open background and PopUp
The next problem is dealing with popup sending a message to the background to retrieve the corresponding data. In the preparation above, we have almost solved the problem.
First, popup sends a message to get paths, i.e. paths for all information that can be viewed. This path is equivalent to the title of each information. We already know how to get the path of the latest information. We store the path of the latest information in localStorage. If the path of the latest information in localStorage is not exactly the path of the actual latest information, we perform the request to obtain the actual latest information path.
It seems not to use localStorage feel nothing, in fact, is forced to use localStorage. The GitHub REST API has a Rate Limit that limits the number of requests you can send per hour. Obviously, if you don’t use localStorage, the result will be awkward. You will get an awkward message after opening PopUp several times:
{
"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"
}
Copy the code
This is followed by sending a new message to retrieve the information that you want to view. The message responds with the content of the information, and the path of the information. The content of the information, needless to say, must be dynamically created to add the DOM to popup, and the path is mainly used for switching. Now that we have all the paths to view information, we can combine paths and Paths to decide whether we can switch forward or backward. The core code for switching is as follows:
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(); }); }Copy the code
debugging
Popup is the same as normal page, right click to check background, go to the extension page, click check View background page.
conclusion
As such, our initial version of Chrome has no blocking items, as shown in the screenshot below:
This instance currently has a lot of TODO content:
- Change the icon;
- Optimize the UI;
- Add new content, such as source, author information, images, etc.
- Add new functions, such as view all, etc.
- …
The initial version of this extension is now available, click on the link above to see more. If anyone is interested, chrome-daily-front-end-news is looking forward to receiving your PR. Ditto for daily-front-end-news.