如何愉快的开发 Chrome 插件

4,028 阅读4分钟

作为一位前端开发人员,估计是离不开 chrome 浏览器吧,可是你在使用 chrome 时,有遇到一些痛点吗?

你有想过自己开发一款插件来解决自己使用上的不便吗?其实 chrome 插件已经提供了挺多实用的 API,我们完全可以根据自己的需求或兴趣开发自己的插件。

chrome 插件没有严格的项目结构的要求,只要求根目录存在一个 manifest.json 类似安卓开发中的 AndroidManifest.xml 这是一个非常重要的功能清单文件,chrome 根据这个文件开放给扩展相应的功能权限。

所以 chrome 扩展的开发完全可以选择我们自己熟悉的技术栈开发,比如我们可以用 React(Vue、Angular) 来开发。下面就是记录我用 React 开发一款好用代理插件 Just Proxy 的过程

已上架:chrome.google.com/webstore/de…

首先需要确定我们的插件需要使用 chrome 开发的那些功能,先用户提供哪些界面。

因为现在要开发的是一款代理插件,我们向用户提供一下功能:

  • 配置界面(options_page,配置代理服务器设置)
  • 当前 tag 的代理状态(通过右上角的扩展的 Icon 展示,高亮代表当前 tag 使用代理,灰色代表不使用)
  • 代理的切换(点击右上角扩展的 Icon 交互,代理 和 取消代理 之间的状态切换)
  • 代理设置的持久化存储

根据上述的需求,我们确定的如下的 manifest.json 的内容。

{
  "manifest_version": 2,
  "name": "Just proxy",
  "description": "A simple proxy tool",
  "version": "1.0.2",
  "permissions": ["proxy", "storage", "tabs"],
  "browser_action": {
    "default_icon": "emoticon.png"
  },
  "icons": {
    "64": "emoticon.png"
  },
  "options_page": "index.html",
  "background": {
    "scripts": ["background.js"],
    "persistent": false
  }
}

关于 manifest.json 更加详细的内容可以查看 developer.chrome.com/extensions/…

热更新配置

接下来我们就可以使用 Webpack(推荐) 来作为扩展的构建工具,因为 chrome 扩展是需要读取硬盘的文件的,我们为了提高我们扩展开发的体验(不要每次修改代码,都需要重新构建扩展,支持热更新),我们需要借助 write-file-webpack-plugin 来将我们的内存的编译文件写回硬盘(不严谨)。因为我们使用 React 构建我们的扩展程序,所以也要 react-hot-loader 来实现热更新。webpack-dev-server 的配置和我们开发网页时一致,这里就不再说明了。

因为 chrome 扩展程序 不是网页,并且它对功能权限有着比网页加严格要求所以我们需要改造下我们的 manifest.json 以便支持扩展的热更新

{
  "manifest_version": 2,
  "name": "Just proxy",
  "description": "A simple proxy tool",
  "version": "1.0.2",
  "permissions": ["proxy", "storage", "tabs", "http://127.0.0.1:8000/*"],
  "browser_action": {
    "default_icon": "emoticon.png"
  },
  "icons": {
    "64": "emoticon.png"
  },
  "options_page": "index.html",
  "background": {
    "scripts": ["background.js"],
    "persistent": false
  },
  "content_security_policy": "default-src 'self' http://127.0.0.1:8000 http://localhost:8000; script-src 'self' http://127.0.0.1:8000 http://localhost:8000 'unsafe-eval'; connect-src http://127.0.0.1:8000 http://localhost:8000 ws://127.0.0.1:8000 ws://localhost:8000; style-src * 'unsafe-inline' 'self' blob:; img-src 'self' data:;"
}

这里值得关注的点就是 content_security_policy 这一项,这里不再做详细的说明,想要了解更多的话可以戳下面的链接

developer.mozilla.org/zh-CN/docs/…

webpack.config.js 的配置需要注意的点,需要注明 publicPath

module.exports = {
  // ...
  output: {
    // ...
    publicPath: "http://127.0.0.1:8000/"
  }
  // ...
};

通过上面的设置我们就可以拥有一个良好的开发体验了(开发环境和打包环境需要不同的配置,主要是针对热更新方面)

数据的持久化

项目的状态管理我使用的是 redux,关于状态的持久化我们可以借助 redux-persist 实现。因为 redux-persist 默认的是使用 localStorage,但是 chrome 扩展中是不支持的,需要自己实现 chrome 插件版本的 storage。 代码如下:

export default class ChromeLocalStorage {
  getItem(key: string) {
    return new Promise(resolve => {
      chrome.storage.local.get(key, item => resolve(item[key]));
    });
  }
  setItem(key: string, value: string) {
    return new Promise(resolve =>
      chrome.storage.local.set({ [key]: value }, resolve)
    );
  }
  removeItem(key: string) {
    return new Promise(resolve => chrome.storage.local.remove(key, resolve));
  }
}

Redux 自动设置代理及多个 store 之间的数据同步

在我们这个扩展存在两个操作比较麻烦的地方

  1. 如何保证 chrome 的代理设置和 redux store 的代理设置数据一致
  2. 如何保证多个不同环境(这个扩展中是存在两个环境 options_page 和 background_script)的 store 数据一致

为了处理上面两个问题我们可以实现一个 redux 中间件来简化上面的操作,实现如下:

const chromeProxyMiddleware = store => {
  // 处理其他环境发过来的 action
  chrome.runtime.onMessage.addListener(request => {
    store.dispatch({ ...request, passed: true });
  });

  return next => action => {
    // 给其他环境发生 action
    if (passingActions.indexOf(action.type) >= 0 && !action.passed) {
      chrome.runtime.sendMessage(action);
    }
    // 设置代理
    chrome.proxy.settings.set(/* proxy config */);
  };
};

export default chromeProxyMiddleware;

项目地址:github.com/0jinxing/ju…

插件地址:chrome.google.com/webstore/de…