专栏首页京程一灯用 Vue 开发自己的 Chrome 扩展

用 Vue 开发自己的 Chrome 扩展

浏览器扩展程序是可以修改和增强 Web 浏览器功能的小程序。它们可用于各种任务,例如阻止广告,管理密码,组织标签,改变网页的外观和行为等等。

好消息是浏览器扩展并不难写。可以用你已经熟悉的 Web 技术(HTML、CSS 和 JavaScript)创建 —— 就像普通网页一样。但是与网页不同的是,扩展程序可以访问许多特定于浏览器的 API,这才是有趣的地方。

在本教程中,我将向你展示如何为 Chrome 构建一个能够改变新标签页行为的简单扩展。这个扩展程序的 JavaScript 部分,我将使用 Vue.js 框架,因为它将允许我们快速启动并运行,而且用 vue 工作是很有趣。

Chrome 扩展程序的基础知识

Chrome扩展程序的核心部分是 manifest 文件 和后台脚本。manifest 文件采用JSON格式,提供有关扩展的重要信息,例如其版本、资源或所需的权限。后台脚本允许扩展对特定的浏览器事件做出反应,例如创建新选项卡。

为了演示这些概念,让我们先写一个“Hello,World!” Chrome 扩展。

创建一个名为 hello-world-chrome 的新文件夹和两个文件:manifest.jsonbackground.js

1mkdir hello-world-chrome
2cd hello-world-chrome
3touch manifest.json background.js

打开 manifest.json 并添加以下代码:

1{
2  "name": "Hello World Extension",
3  "version": "0.0.1",
4  "manifest_version": 2,
5  "background": {
6    "scripts": ["background.js"],
7    "persistent": false
8  }
9}

nameversionmanifest_version 都是必填字段。 nameversion 字段可以是你想要的任何内容; manifest version 应设置为2(从Chrome 18开始)。

background 允许我们注册一个后台脚本, 在scripts 后面的数组中列出。除非扩展需要用 chrome.webRequest API来阻止或修改网络请求,否则 persistent 键应设置为 false

将以下代码添加到 background.js ,使浏览器在安装扩展时弹出出 hello 对话框:

1chrome.runtime.onInstalled.addListener(() => {
2  alert('Hello, World!');
3});

最后安装扩展程序。打开 Chrome 并在地址栏中输入 chrome://extensions/。你应该看到一个显示已安装扩展程序的页面。

由于我们要从文件(而不是Chrome网上应用店)安装自己的扩展程序,因此需要使用页面右上角的切换按钮来激活开发者模式。这应该添加一个额外的菜单栏,其中包含 Load unpacked选项。单击此按钮并选择你之前创建的 hello-world-chrome 文件夹。单击打开,应该能够看到已安装的扩展,并弹出“Hello,World!”窗口。

Hello World Extension

恭喜!你刚刚制作了一个 Chrome 扩展程序。

覆盖 Chrome 的新标签页

为了在打开新选项卡时迎接我们的是自己的扩展程序。可以通过使用 Override Pages API 来完成此操作。

注意:在你取得进展之前,请务必停用其他能够覆盖 Chrome 新标签页的扩展程序。一次只允许一个扩展改变这种行为。

首先创建一个要显示的页面,而不是新的标签页。我们称之为 tab.html。它应该与清单文件和后台脚本位于同一文件夹中:

 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4  <meta charset="UTF-8">
 5  <title>My New Tab Page!</title>
 6</head>
 7<body>
 8  <h1>My New Tab Page!</h1>
 9  <p>You can put any content here you like</p>
10</body>
11</html>

接下来需要让扩展知道页面的存在。可以通过在清单文件中指定 chrome_url_overrides 来实现,如下所示:

1"chrome_url_overrides": {
2  "newtab": "tab.html"
3}

最后,你需要重新加载扩展才能使更改生效。你可以通过在 Chrome 的扩展程序页面上单击 Hello World 扩展程序的 reload 图标来执行此操作。

重新加载扩展

现在,当你打开新标签页时,你的自定义消息会出现。

将Vue添加到扩展

现在我们有一个非常基本的扩展,接下来要实现剩下的需功能了。当用户打开新标签页时,我希望扩展能够:

  • 从精彩的笑话网站 icanhazdadjoke.com 获取一个笑话。
  • 以良好的格式向用户显示该笑话。
  • 显示用户喜欢该笑话的按钮。这样可以把笑话保存到 chrome.storage
  • 显示一个按钮,供用户查看已收藏的笑话。

当然你也可以用纯 JavaScript 或像 jQuery 这样的库来完成所有这些 —— 你开心就好!

但是出于本教程的目的,我将用 Vue 和令人敬畏的 vue-web-extension 样板来实现此功能。

用 Vue 可以让我又快又好地编写更有条理的代码。正如我们所看到的,样板文件提供了几个脚本,可以在构建 Chrome 扩展程序时解决一些痛苦的常见任务(例如:每当你进行更改时都必须重新加载扩展程序)。

vue-web-extension-boilerplate

我们还需要安装 Vue CLI 和 @vue/cli-init package:

1npm install -g @vue/cli
2npm install -g @vue/cli-init

完成后,让我们得到样板的副本:

1vue init kocal/vue-web-extension new-tab-page

这将打开一个向导,询问你一堆问题。为了保证本教程的重点,我把回答列出来:

 1? Project name new-tab-page
 2? Project description A Vue.js web extension
 3? Author James Hibbard <jim@example.com>
 4? License MIT
 5? Use Mozilla's web-extension polyfill? No
 6? Provide an options page? No
 7? Install vue-router? No
 8? Install vuex? No
 9? Install axios? Yes
10? Install ESLint? No
11? Install Prettier? No
12? Automatically install dependencies? npm
13

你可以根据自己的喜好调整答案,但是你一定要安装 axios。我们会用它来获取笑话。

接下来,切换到项目目录并安装依赖项:

1cd new-tab-page
2npm install

然后就可以用样板提供的脚本构建我们的新扩展了:

1npm run watch:dev

这会将扩展构建到项目根目录中的 dist 文件夹中,来进行开发并监视更改。

要将扩展程序添加到 Chrome,请执行上述相同的步骤,要选择 dist 文件夹作为扩展程序目录。如果一切按计划进行,那么当扩展程序初始化时,你应该看到“Hello world!”消息。

项目设置

让我们花一点时间来看看样板给了我们些什么。当前文件夹结构应如下所示:

 1.
 2├── dist
 3│   └── <the built extension>
 4├── node_modules
 5│   └── <one or two files and folders>
 6├── package.json
 7├── package-lock.json
 8├── scripts
 9│   ├── build-zip.js
10│   └── remove-evals.js
11├── src
12│   ├── background.js
13│   ├── icons
14│   │   ├── icon_128.png
15│   │   ├── icon_48.png
16│   │   └── icon.xcf
17│   ├── manifest.json
18│   └── popup
19│       ├── App.vue
20│       ├── popup.html
21│       └── popup.js
22└── webpack.config.js

在项目根目录中可以看到,样板文件正在使用 webpack。这很好,因为这为我们的后台脚本提供了 Hot Module Reloading。

src文件夹包含我们将用于扩展的所有文件。manifest 文件和 background.js 对于我们来说是熟悉的,但也要注意包含Vue 组件的 popup 文件夹。当样板文件将扩展构建到 dist 文件夹中时,它将通过 vue-loader 管理所有 .vue 文件并输出一个浏览器可以理解的 JavaScript 包。

src 文件夹中还有一个 icons 文件夹。如果你看一眼 Chrome 的工具栏,会看到我们的扩展程序的新图标(也被称为 browser action)。这就是从此文件夹中拿到的。如果单击它,你应该会看到一个弹出窗口,显示“Hello world!” 这是由 popup/App.vue 创建的。

最后,请注 scripts 文件夹的两个脚本:一个用于删除 eval 用法以符合 Chrome Web Store 的内容安全策略,另一个用于当你要把扩展上传到Chrome Web Store时将其打包到 .zip 文件中,。

package.json 文件中还声明了各种脚本。我们将用 npm run watch:dev 来开发扩展,然后使用 npm run build-zip 生成一个ZIP文件以上传到 Chrome Web Store。

在新标签页中使用 Vue 组件

首先从 background.js 中删除烦人的 alert 语句。

src 文件夹中创建一个新的 tab 文件夹来存放新标签页的代码。我们将在这个新文件夹中添加三个文件 —— App.vuetab.htmltab.js

1mkdir src/tab
2touch src/tab/{App.vue,tab.html,tab.js}

打开 tab.html 并添加以下内容:

 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4  <meta charset="UTF-8">
 5  <title>New Tab Page</title>
 6  <link rel="stylesheet" href="tab.css">
 7</head>
 8<body>
 9  <div id="app"></div>
10  <script src="tab.js"></script>
11</body>
12</html>

这里没什么特别的。这是一个简单的 HTML 页面,它将保存我们的 Vue 实例。

接下来在 tab.js 中添加:

1import Vue from 'vue';
2import App from './App';
3
4new Vue({
5  el: '#app',
6  render: h => h(App)
7});

在这里导入 Vue,用它为元素传递一个选择器,然后告诉它渲染 App 组件。

最后在 App.vue 中写如下代码:

 1<template>
 2  <p>{{ message }}</p>
 3</template>
 4
 5<script>
 6export default {
 7  data () {
 8    return {
 9      message: "My new tab page"
10    }
11  }
12}
13</script>
14
15<style scoped>
16p {
17  font-size: 20px;
18}
19</style>

在使用这个新标签页之前,我们需要更新 manifest 文件:

1{
2  "name":"new-tab-page",
3  ...
4  "chrome_url_overrides": {
5    "newtab": "tab/tab.html"
6  }
7}

为了使它们可用于扩展,我们还需要让样板编译我们的文件并复制到 dist 文件夹。

像下面这样修改 webpack.config.js,更新entryplugins键:

 1entry: {
 2  'background': './background.js',
 3  'popup/popup': './popup/popup.js',
 4  'tab/tab': './tab/tab.js'
 5}
 6plugins: [
 7  ...
 8  new CopyWebpackPlugin([
 9    { from: 'icons', to: 'icons', ignore: ['icon.xcf'] },
10    { from: 'popup/popup.html', to: 'popup/popup.html', transform: transformHtml },
11    { from: 'tab/tab.html', to: 'tab/tab.html', transform: transformHtml },
12    ...
13  })

你需要重新启动 npm run watch:dev 任务才能使这些更改生效。完成此操作后,重新加载扩展程序并打开新选项卡。你应该会看到“My new tab page”。

My new tab page

获取并显示笑话

好的,我们已经覆盖了 Chrome 的新标签页,并且将其替换为了 mini Vue app。但是我们要做的不仅仅是显示一条消息。

更改 src/tab/App.vue 中的模板部分如下:

 1<template>
 2  <div>
 3    <div v-if="loading">
 4      <p>Loading...</p>
 5    </div>
 6    <div v-else>
 7      <p class="joke">{{ joke }}</p>
 8    </div>
 9  </div>
10</template>

<script> 部分更改为如下代码:

 1<script>
 2import axios from 'axios';
 3
 4export default {
 5  data () {
 6    return {
 7      loading: true,
 8      joke: "",
 9    }
10  },
11  mounted() {
12    axios.get(
13      "https://icanhazdadjoke.com/",
14      { 'headers': { 'Accept': 'application/json' } }
15    )
16      .then(res => {
17        this.joke = res.data.joke
18        this.loading = false;
19      });
20  }
21}
22</script>
23

最后,将 <style> 部分更改为如下代码:

 1<style>
 2body {
 3  height: 98vh;
 4  text-align: center;
 5  color: #353638;
 6  font-size: 22px;
 7  line-height: 30px;
 8  font-family: Merriweather,Georgia,serif;
 9  background-size: 200px;
10  display: flex;
11  align-items: center;
12  justify-content: center;
13}
14
15.joke {
16  max-width: 800px;
17}
18</style>

如果你正在运行 npm run watch:dev 任务,则扩展程序会自动重新加载,并且每当你打开新标签页时都会看到一个笑话。

How does a French skeleton say hello? Bone-jour

接下来花点时间来了解一下自己都做了些什么。

在模板中,我们使用 v-if 块来显示加载消息或笑话,具体取决于 loading 的状态。最初它被设置为 true(显示加载消息),然后我们的脚本将触发 Ajax 请求来检索笑话。一旦 Ajax 请求完成,loading 属性将被设置为 false,导致组件被重新渲染并显示笑话。

<script> 部分,我们导入了 axios,然后声明了几个数据属性——前面提到的 loading 属性和一个 joke 属性来保存这个笑话。然后使用了 mount 生命周期钩子,一旦我们的 Vue 实例被挂载就会触发,向 joke API 发出 Ajax 请求。请求完成后,更新两个数据属性使组件重新渲染。

到目前为止还挺好。

将笑话持持久化到 Chrome Storage

接下来,添加一些能够让用户喜欢一个笑话和列出喜欢的笑话列表的按钮。由于我们将使用 Chrome’s storage API 来保存这些笑话,所以可能需要添加第三个按钮来删除 storage 中的笑话。

将按钮添加到 v-else 块:

1<div v-else>
2  <p class="joke">{{ joke }}</p>
3
4  <button @click="likeJoke" :disabled="likeButtonDisabled">Like Joke</button>
5  <button @click="logJokes" class="btn">Log Jokes</button>
6  <button @click="clearStorage" class="btn">Clear Storage</button>
7</div>

没有什么令人兴奋的东西了。请注意我们将类似按钮的 disabled 属性绑定到 Vue 实例上的数据属性来确定其状态。这是因为用户不应该多次喜欢一个笑话。

接下来,将 click handler 和 Like Button Disabled 添加到脚本部分:

 1export default {
 2  data () {
 3    return {
 4      loading: true,
 5      joke: "",
 6      likeButtonDisabled: false
 7    }
 8  },
 9  methods: {
10    likeJoke(){
11      chrome.storage.local.get("jokes", (res) => {
12        if(!res.jokes) res.jokes = [];
13        res.jokes.push(this.joke)
14        chrome.storage.local.set(res);
15        this.likeButtonDisabled = true;
16      });
17    },
18    logJokes(){
19      chrome.storage.local.get("jokes", (res) => {
20        if(res.jokes) res.jokes.map(joke => console.log(joke))
21      });
22    },
23    clearStorage(){
24      chrome.storage.local.clear();
25    }
26  },
27  mounted() { ... }
28}

在这里,我们声明了三个新方法来处理这三个新按钮。

likeJoke 方法在 Chrome 的存储中查找 jokes 属性。如果它不存在(也就是说,用户尚未喜欢一个笑话),会将其初始化为空数组。然后它将当前的笑话推送到此数组并将其保存到 storage。最后,将 likeButtonDisabled 数据属性设置为 true,并禁用 like 按钮。

logJokes 方法还在 Chrome storage 中查找 jokes 属性。如果找到了,会遍历其所有条目并将它们输出到控制台。

clearStorage 方法负责清除数据。

继续在扩展中调整这个新功能,直到自己满意。

将笑话输出到控制台

为扩展做一些美化

它能够工作了,但是按钮是很丑,页面也有点简单。下面就要给扩展做一些润色。

下一步,安装 vue-awesome 库。它能够使我们在页面上使用 Font Awesome 图标,并使这些按钮看起来更漂亮一些:

1npm install vue-awesome

src/tab/tab.js 中对库进行注册:

 1import Vue from 'vue';
 2import App from './App';
 3import "vue-awesome/icons";
 4import Icon from "vue-awesome/components/Icon";
 5
 6Vue.component("icon", Icon);
 7
 8new Vue({
 9  el: '#app',
10  render: h => h(App)
11});

修改模板:

 1<template>
 2  <div>
 3    <div v-if="loading" class="centered">
 4      <p>Loading...</p>
 5    </div>
 6    <div v-else>
 7      <p class="joke">{{ joke }}</p>
 8
 9      <div class="button-container">
10        <button @click="likeJoke" :disabled="likeButtonDisabled" class="btn"><icon name="thumbs-up"></icon></button>
11        <button @click="logJokes" class="btn"><icon name="list"></icon></button>
12        <button @click="clearStorage" class="btn"><icon name="trash"></icon></button>
13      </div>
14    </div>
15  </div>
16</template>
17

最后,让我们为按钮添加更多样式,并添加一张图片:

 1<style>
 2body {
 3  height: 98vh;
 4  text-align: center;
 5  color: #353638;
 6  font-size: 22px;
 7  line-height: 30px;
 8  font-family: Merriweather,Georgia,serif;
 9  background: url("https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2018/12/1544189726troll-dad.png") no-repeat 1% 99%;
10  background-size: 200px;
11  display: flex;
12  align-items: center;
13  justify-content: center;
14}
15
16.joke {
17  max-width: 800px;
18}
19
20.button-container {
21  position: absolute;
22  right: 0px;
23  top: calc(50% - 74px);
24}
25
26.btn {
27  background-color: #D8D8D8;
28  border: none;
29  color: white;
30  padding: 12px 16px;
31  font-size: 16px;
32  cursor: pointer;
33  display: block;
34  margin-bottom: 5px;
35  width: 50px;
36}
37
38.btn:hover {
39  background-color: #C8C8C8;
40}
41
42.btn:disabled {
43  background-color: #909090;
44}
45</style>

重新加载扩展并打开一个新标签,你应该看到这样的东西。

完成的扩展

将扩展程序上传到 Chrome Web Store

如果想让其他人也可以使用你的扩展程序,可以通过 Chrome Web Store 做到。

首先你需要有一个 Google 帐户,可以用该帐户登录 Developer Dashboard 。系统会提示你输入开发人员详细信息,在发布第一个应用程序之前,你必须支付 5 美元的开发人员注册费(通过信用卡)。

接下来,你需要为自己的应用创建一个 ZIP 文件。你可以通过 npm run build-zip 在本地执行这项操作。这会在项目根目录中创建一个名为 dist-zip 的文件夹,其中包含准备上传到 Web Store 的 ZIP 文件。

对于简单的小扩展,这就够了。但是,在你上传自己的扩展之前,请务必阅读官方 Publish in the Chrome Web Store 指南。

总结

在本教程中,我重点介绍了 Chrome 扩展程序的主要部分,并展示了如何用在 Vue.js 中 vue-web-extension 样板构建扩展程序,最后讲解了如何将扩展上传到 Web Store。

希望你喜欢本教程,并用它指导你始构建自己的 Chrome 扩展。

本文分享自微信公众号 - 前端先锋(jingchengyideng),作者:疯狂的技术宅

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 使用 HTTPS 部署你的站点势在必行

    如果你有一个站点,或者近期你正在考虑部署自己的站点,那么你有必要考虑使用 HTTPS 来武装你的网站了。

    疯狂的技术宅
  • 有效使用 Node.js 事件循环

    对于 Node.js 应用程序开发新手而言,作为学习曲线的一部分,他们需要了解单线程事件循环的工作原理,以及它可能导致意外结果的方式。您可以使用本教程中的 3 ...

    疯狂的技术宅
  • 在 vue 项目中使用各种 javascript 类库

    Lodash, Moment, Axios, Async…这些都是非常有用的 Javascript 库,而且你会希望使用在你的各种 Vue.js 应用中。

    疯狂的技术宅
  • 动手学深度学习(八) NLP 文本预处理

    文本是一类序列数据,一篇文章可以看作是字符或单词的序列,本节将介绍文本数据的常见预处理步骤,预处理通常包括四个步骤:

    致Great
  • 用c#添加Networkservice对文件夹的写权限

          一般的bs程序都会涉及到上传文件,这就要求network service用户有对文件夹的写权限,通常情况下我们都是用打包时调用打包程序的脚本去设置权...

    用户1183026
  • 老焦专栏 | 为什么需要用业务补偿服务和TCC 型服务实现数据一致性

    分布式事务解决的问题很明确,就是在服务分布在不同进程、数据分布在不同数据库时,如何解决数据一致性问题。对于这个问题,业界的共识是不要启用数据库 XA 模式,因为...

    yuanyi928
  • 三歪这次搞一波「过滤器和监听器」总结

    这周工作有点忙,本来想着休息一周再写「电子书」的,但上周的Spring「在看」超出了期望,所以又只能肝了。

    Java3y
  • Java 用反射实现实体类属性 not null 校验

    夹胡碰
  • MYSQL & PostgreSQL 时区问题

    有时候使用一样东西用习惯了,就不大会多想,而出现问题的时候也不会想到那里去。所以MYSQL 的时间这个问题可能就属于这个list.

    AustinDatabases
  • 学会等待

    内心极为渴望得到某种东西的时候,也许正是大脑停止工作的时候。因为太过渴望,那件东西的诱人之处就会散发出如日中天的光芒,以至于眼睛看不到别的任何东西了,注意力的过...

    雷大亨

扫码关注云+社区

领取腾讯云代金券