从零到一开发一个Chrome插件(二)

引言

大家好啊,我是前端拿破轮。

作为一个前端工程师,Chrome在我们的工作中扮演着重要作用。它不仅是前端的主要运行环境,而且是我们代码调试的重要工具,也是平时学习生活使用的重要软件。

在Chrome中有许多优秀的扩展,包括但不限于我们使用的React Devtools沉浸式翻译插件等等。许多扩展极大地拓展了浏览器的能力边界,提高了我们工作和学校的效率。

所以笔者早有开发一个自己的插件的想法,苦于之前一直忙于学习和工作,没有合适的时间,最近忙里偷闲,决定抽出时间来进行这项工作。而且AI的时代浪潮也给浏览器的插件带来了新的生命力。所以笔者决定做一款浏览器端的AI助手插件。

本文属于Chrome插件开发专栏,会持续更新拿破轮的Chrome插件开发过程,欢迎各位读者订阅,希望对你有所帮助。如果还没有看过之前文章的读者,可以先阅读专栏中前面的文章。

阅读时间估计Demo

概述

在本文中我们会构建一个扩展程序,用来统计当前页面的阅读时间。

在本文中我们会涉及到以下概念:

  • 扩展程序清单
  • 扩展程序使用的图标大小
  • 如何使用内容脚本将代码注入网页
  • 如何使用匹配模式
  • 扩展程序的权限

步骤

第 1 步:添加扩展程序的相关信息

与在从零到一开发一个Chrome插件(一)中的步骤相同,还是要创建一个目录用来构建我们的扩展程序,并在根目录中创建一个清单,即manifest.json文件。

我们这里为了方便,直接在上一篇文章中的Demo的基础上进行改造。

json 复制代码
 // manifest.json
 {
   "name": "Reading time",
   "description": "估计网页阅读时间",
   "version": "1.0",
   "manifest_version": 3,
 }

关于扩展程序清单(manifest.json)文件,我们有以下几点需要注意:

  • 它必须位于根目录当中。
  • 在这个文件中必须包含的键有manifest_version,nameversion
  • 它在开发期间支持注释(//),但是必须将注释移除后,才能将插件上传到Chrome应用商店。

第 2 步:提供图标

在开发过程中图标可有可无,但是如果我们想要在Chrome应用商店中上架我们的扩展,就必须提供图标

在这个Demo示例中,我们可以使用官方提供的GitHub图标仓库,将images文件夹也放在我们的插件的根目录下,然后在清单文件manifest.json中进行配置。

json 复制代码
 {
   "name": "Reading time",
   "description": "估计网页阅读时间",
   "version": "1.0",
   "manifest_version": 3,
   "icons": {
     "16": "images/icon-16.png",
     "32": "images/icon-32.png",
     "48": "images/icon-48.png",
     "128": "images/icon-128.png"
   }
 }

官方推荐使用png文件,当然其他文件也可以,但是svg不行

在上面的配置中我们可以看到,有16,32,48,128这四种尺寸的图标,他们分别在哪里显示呢?如下表所示:

图标大小 图标使用
16x16 扩展程序页面和上下文菜单中的图标。
32x32 Windows 计算机通常需要此大小。
48x48 显示在"扩展程序"页面上。
128x128 会在安装过程中和 Chrome 应用商店中显示。

第 3 步:声明内容脚本

扩展程序可以运行脚本,来读取和修改网页内容。这些脚本称为内容脚本 。他们运行在隔离环境 中。这意味着他们可以更改自己的JavaScript环境,而不会与其托管页面或其他扩展程序的内容脚本发生冲突。

我们可以将下面的代码添加到manifest.json文件中来注册一个名为content.js的内容脚本。

json 复制代码
 {
   "name": "Reading time",
   "description": "估计网页阅读时间",
   "version": "1.0",
   "manifest_version": 3,
   "icons": {
     "16": "images/icon-16.png",
     "32": "images/icon-32.png",
     "48": "images/icon-48.png",
     "128": "images/icon-128.png"
   },
   "content_scripts": [
     {
       "js": ["scripts/content.js"],
       "matches": [
         "https://developer.chrome.com/docs/extensions/*",
         "https://developer.chrome.com/docs/webstore/*"
       ]
     }
   ]
 }

matches字段可以有一个或多个匹配模式 。这些标记可以让浏览器确定要将内容脚本注入到哪些网站 。匹配模式一般由以下三个部分组成:<scheme>://<host><path>。可以包含*字符。

第 4 步:计算并插入阅读时间

内容脚本可以使用标准DOM读取和更改网页内容。

该扩展会首先检查网页是否包含<article>元素。然后,它会统计元素中所有字词,并创建一个段落来显示总阅读时间。

在根目录下创建scripts文件夹,在其中创建一个content.js文件,然后添加如下代码。

javascript 复制代码
 function renderReadingTime(article) {
   // 如果没有article,就不需要计算,直接返回
   if (!article) {
     return;
   }
 ​
   // 得到文本数据
   const text = article.textContent;
 ​
   // 正则表达式匹配单词: 匹配一个或多个非空白字符
   const wordMatchRegExp = /[^\s]+/g;
 ​
   // 匹配的单词
   const words = text.matchAll(wordMatchRegExp);
 ​
   // 单词数量
   const wordCount = [...words].length;
 ​
   // 阅读时间:每分钟大概读200词
   const readingTime = Math.round(wordCount / 200);
 ​
   // 展示标签DOM
   const badge = document.createElement("p");
 ​
   // 增加css类名
   badge.classList.add("color-secondary-text", "type--caption");
 ​
   // 添加展示内容
   badge.textContent = `⏱️ 预估阅读时间为 ${readingTime} 分钟`;
 ​
   // 获取标题
   const heading = article.querySelector("h1");
 ​
   // 获取时间
   const date = article.querySelector("time")?.parentNode;
 ​
   // 在date的后面加入时间卡片,如果没有date,就在heading后面加入
   (date ?? heading).insertAdjacentElement("afterend", badge);
 }
 ​
 renderReadingTime(document.querySelector("article"));

第 5 步:监听更改

使用我们上面的代码,如果我们使用左侧导航栏切换文章,阅读时间不会添加到新文章中 。这是因为现在很多网站都是作为单页应用(SPA)实现的,这些应用使用History API执行软导航。

所以当我们点击导航栏切换文章时,看起来浏览器的地址栏中的URL变化了,但是实际上我们请求的一直是根文件index.html,所以renderReadingTime函数不会重新执行,阅读时间自然而然就不会添加到新文章中。

为了解决这个问题,我们可以使用MutationObserver来监听更改,并将阅读时间添加到新文章。

所以,我们需要再content.js中添加如下代码:

javascript 复制代码
 function renderReadingTime(article) {
   // 如果没有article,就不需要计算,直接返回
   if (!article) {
     return;
   }
 ​
   // 得到文本数据
   const text = article.textContent;
 ​
   // 正则表达式匹配单词: 匹配一个或多个非空白字符
   const wordMatchRegExp = /[^\s]+/g;
 ​
   // 匹配的单词
   const words = text.matchAll(wordMatchRegExp);
 ​
   // 单词数量
   const wordCount = [...words].length;
 ​
   // 阅读时间:每分钟大概读200词
   let readingTime = Math.round(wordCount / 200);
 ​
   // 确保阅读时间最少为 1
   readingTime = Math.max(readingTime, 1);
 ​
   // 展示标签DOM
   const badge = document.createElement("p");
 ​
   // 增加css类名
   badge.classList.add("color-secondary-text", "type--caption");
 ​
   // 添加展示内容
   badge.textContent = `⏱️ 预估阅读时间为 ${readingTime} 分钟`;
 ​
   // 获取标题
   const heading = article.querySelector("h1");
 ​
   // 获取时间
   const date = article.querySelector("time")?.parentNode;
 ​
   // 在date的后面加入时间卡片,如果没有date,就在heading后面加入
   (date ?? heading).insertAdjacentElement("afterend", badge);
 }
 ​
 renderReadingTime(document.querySelector("article"));
 ​
 const observer = new MutationObserver((mutations) => {
   for (const mutation of mutations) {
     for (const node of mutation.addedNodes) {
       if (node instanceof Element && node.tagName === 'ARTICLE') {
         renderReadingTime(node);
       }
     }
   }
 })
 ​
 observer.observe(document.querySelector('devsite-content'), {
   childList: true,
 });
 ​

验证项目的文件结构是否如下所示:

按照从零到一开发一个Chrome插件(一)的步骤加载本地未打包的扩展程序来进行测试。

由于我们在content.js中写的代码只针对Chrome Developer中的文章,所以我们要用这些文章来进行测试。下面是两个可以进行测试的示例文档。

然后我们可以看到已经在标题下方成功显示了这篇文章的阅读时间。

总结

本文我们通过配置内容脚本,实现了在Chrome Dev的博客页面插入预估阅读时间的插件Demo,对于Chrome插件开发有了基本的认识和理解。

本文属于Chrome插件开发专栏,会持续更新拿破轮的Chrome插件开发过程,欢迎各位读者订阅,希望对你有所帮助。

好了,这篇文章就到这里啦,如果对您有所帮助,欢迎点赞,收藏,分享👍👍👍。您的认可是我更新的最大动力。由于笔者水平有限,难免有疏漏不足之处,欢迎各位大佬评论区指正。

往期推荐✨✨✨

我是前端拿破轮,关注我,一起学习前端知识,我们下期见!

相关推荐
珍宝商店2 小时前
Vue.js 中深度选择器的区别与应用指南
前端·javascript·vue.js
天蓝色的鱼鱼3 小时前
Next.js 预渲染完全指南:SSG vs SSR,看完秒懂!
前端·next.js
月出3 小时前
无限循环滚动条 - 左出右进
前端
aiwery3 小时前
实现带并发限制的 Promise 调度器
前端·算法
熊猫片沃子3 小时前
浅谈Vue 响应式原理
前端
货拉拉技术3 小时前
微前端中的错误堆栈问题探究
前端·javascript·vue.js
前端老鹰3 小时前
HTML `<datalist>`:原生下拉搜索框,无需 JS 也能实现联想功能
前端·css·html
南北是北北3 小时前
Android TexureView和SurfaceView
前端·面试
code_YuJun3 小时前
脚手架架构设计
前端