开发chrome插件auto reload原理分析

简介: 开发chrome插件auto reload原理分析

之前开发过一款chrome插件,当时使用了一个vue-cli-plugin-browser-extension,配合vue开发chrome插件非常的丝滑,尤其是可以auto reload这块非常的感兴趣。

今天有时间就仔细研究了下

vue-cli-plugin-browser-extension

先说一下使用,在vue.config.js中配置如下:

pluginOptions: {
  browserExtension: {
    components: {
      background: true,
      contentScripts: true,
    },
    componentOptions: {
      contentScripts: {
        entries: {
          content: "src/content.ts",
          inject: "src/inject/index.ts",
        },
      },
      background: {
        entry: "src/background.ts",
      }
    }
  }
}
复制代码

readme.md中写的也非常的详细。

具体是怎么实现的呢?

查看package.json的声明,发现入口在index.js

{
    "main":"index.js"
}
复制代码

非常熟悉的入口函数,直到后来看了vue-cli的源码,才知道这里面的api, options到底是怎么回事,有兴趣的可以阅读下@vue/cli-serve的源码。

因为对auto reload非常感兴趣,所以只关注这块的逻辑

// index.js
const ExtensionReloader = require('webpack-extension-reloader')
module.exports = (api, options) => {
    api.chainWebpack();
    // 重点在这里,使用了一个plugin
    webpackConfig.plugin('extension-reloader').use(ExtensionReloader, ...);
}
复制代码

再次查看下package.jsondevDependenci,还真的有依赖这个

"dependencies": {
  "@vue/cli-shared-utils": "^3.0.0-rc.3",
  "copy-webpack-plugin": "^5.1.2",
  "imports-loader": "^0.8.0",
  "webextension-polyfill": "^0.4.0",
  "webpack": "^4.16.0",
  "webpack-extension-reloader": "^1.1.0",
  "zip-webpack-plugin": "^3.0.0"
}
复制代码

那很明显auto reload就是使用了webpack-extension-reloader插件。

webpack-extension-reloader

还是首先看下package.json的main

{
    "main": "dist/webpack-extension-reloader.js",
    "repository": {
      "type": "git",
      "url": "git://github.com/rubenspgcavalcante/webpack-extension-reloader.git"
    }
}
复制代码

发现npm install后,只有dist代码,当然我是拒绝看这种build之后的代码,一般来说都是有GitHub地址的。

我们clone下来直接查看GitHub的源代码。

那这个main入口代码是如何生成的呢?发现有个webpack.config.js,里面有这个配置

module.exports={
    entry: test({ tests: "./specs/index.ts" }) || {
      [packName]: "./src/index.ts",
      [`${packName}-cli`]: "./client/index.ts"
    },
    output: {
      publicPath: ".",
      path: path.resolve(__dirname, "./dist"),
      filename: "[name].js",
      libraryTarget: "umd"
    }, 
}
复制代码

源码指向了src/index.ts

import ExtensionReloaderImpl from "./ExtensionReloader";
export = ExtensionReloaderImpl;
复制代码

又指向了ExtensionReloader,因为是个webpack插件,所以直接看apply函数即可(不懂的亲自写个webpack plugin就明白了)

import { middlewareInjector } from "./middleware";
export default class ExtensionReloaderImpl extends AbstractPluginReloader{
    apply(compiler:Compiler){
        this._registerPlugin(compiler);
    }
    _registerPlugin(){
        this._injector = middlewareInjector(parsedEntries, { port, reloadPage });
    }
}
复制代码

这里的重点又指向了src/middleware

import _middlewareInjector from "./middleware-injector";
export const middlewareInjector = _middlewareInjector;
复制代码

再看src/middleware/middleware-injector

import middleWareSourceBuilder from "./middleware-source-builder";
const middlewareInjector: MiddlewareInjector = (
  { background, contentScript, extensionPage },
  { port, reloadPage },
) => {
  const source: Source = middleWareSourceBuilder({ port, reloadPage });
  // ...
};
export default middlewareInjector;
复制代码

再看middleware-source-builder

import { template } from "lodash";
// 这里就是模板的源文件,关于这个写法,得参考webpack
import rawSource from "raw-loader!./wer-middleware.raw"; 
import polyfillSource from "raw-loader!webextension-polyfill";
import { RawSource, Source } from "webpack-sources";
import {
  RECONNECT_INTERVAL,
  SOCKET_ERR_CODE_REF,
} from "../constants/middleware-config.constants";
import * as signals from "../utils/signals";
export default function middleWareSourceBuilder({
  port,
  reloadPage,
}: IMiddlewareTemplateParams): Source {
  const tmpl = template(rawSource);
  return new RawSource(
    // 这里都是一些参数
    tmpl({
      WSHost: `ws://localhost:${port}`,
      config: JSON.stringify({ RECONNECT_INTERVAL, SOCKET_ERR_CODE_REF }),
      polyfillSource: `"||${polyfillSource}"`,
      reloadPage: `${reloadPage}`,
      signals: JSON.stringify(signals),
    }),
  );
}
复制代码

这里有个小发现,就是lodash原来也有个template方法,和ejs非常像。

再看wer-middleware.raw

/* -------------------------------------------------- */
/*      Start of Webpack Hot Extension Middleware     */
/* ================================================== */
/*  This will be converted into a lodash templ., any  */
/*  external argument must be provided using it       */
/* -------------------------------------------------- */
(function(window) {
  function contentScriptWorker() {
    runtime.sendMessage({ type: SIGN_CONNECT }).then(msg => console.info(msg));
    runtime.onMessage.addListener(({ type, payload }: IAction) => {
      switch (type) {
        case SIGN_RELOAD:
          logger("Detected Changes. Reloading ...");
          // 重点:当收到消息后,调用了reload函数,至此原理就非常清楚了
          reloadPage && window.location.reload();
          break;
        case SIGN_LOG:
          console.info(payload);
          break;
      }
    });
  }
  function backgroundWorker(socket: WebSocket) {
      // 通过socket和background建立链接
    socket.addEventListener("message", ({ data }: MessageEvent) => {
      const { type, payload } = JSON.parse(data);
      if (type === SIGN_CHANGE && (!payload || !payload.onlyPageChanged)) {
          // 重点:当background收到重载消息是,会重新加载这个chrome extension
          // http://www.kkh86.com/it/chrome-extension-doc/extensions/runtime.html#method-reload
          runtime.reload();
        });
      }
    });
    socket.addEventListener("close", ({ code }: CloseEvent) => {
      const intId = setInterval(() => {
        const ws = new WebSocket(wsHost);
        ws.addEventListener("open", () => {
          clearInterval(intId);
          runtime.reload();
        });
      }, RECONNECT_INTERVAL);
    });
  }
  // ======================== Called only on extension pages that are not the background ============================= //
  function extensionPageWorker() {
    runtime.sendMessage({ type: SIGN_CONNECT }).then(msg => console.info(msg));
    runtime.onMessage.addListener(({ type, payload }: IAction) => {
      switch (type) {
        case SIGN_CHANGE:
          reloadPage && window.location.reload();
          break;
      }
    });
  }
  // ======================= Bootstraps the middleware =========================== //
  // 这里应该是多环境复用这份代码
  runtime.reload
    ? extension.getBackgroundPage() === window ? backgroundWorker(new WebSocket(wsHost)) : extensionPageWorker()
    : contentScriptWorker();
})(window);
复制代码

再看下package.json中"ws": "^7.2.0",果然安装了ws,用来创建一个websocket服务器, 在src/hot-reload/HotReloaderServer.ts中就有创建这个server的逻辑。 当webpack重新build并生成文件后,就会触发

this._compiler.hooks.afterEmit.tap
复制代码

具体的实现细节还是非常多的,原理大致也就明白了,就是最终会触发server去通知调用reload接口,实现auto reload,再深入的就没有具体分析了



目录
相关文章
|
2月前
|
Web App开发 JavaScript 前端开发
使用vue快速开发一个带弹窗的Chrome插件
使用vue快速开发一个带弹窗的Chrome插件
67 0
使用vue快速开发一个带弹窗的Chrome插件
|
2月前
|
Web App开发 JavaScript 前端开发
从零开始,轻松打造个人化Chrome浏览器插件
从零开始,轻松打造个人化Chrome浏览器插件
74 0
|
3月前
|
Web App开发 JavaScript 前端开发
chrome调试秘籍,让你的开发速度飞起来
chrome调试秘籍,让你的开发速度飞起来
|
5月前
|
Web App开发 存储 前端开发
一键同步,无处不在的书签体验:探索多电脑Chrome书签同步插件
一键同步,无处不在的书签体验:探索多电脑Chrome书签同步插件
115 0
|
5月前
|
Web App开发 存储 缓存
(新)Chrome浏览器自定义背景插件
(新)Chrome浏览器自定义背景插件
107 0
|
3月前
|
Web App开发 前端开发
Chrome 浏览器插件 V3 版本 Manifest.json 文件中 Action 的类型(Types)、方法(Methods)和事件(Events)的属性和参数解析
Chrome 浏览器插件 V3 版本 Manifest.json 文件中 Action 的类型(Types)、方法(Methods)和事件(Events)的属性和参数解析
156 0
|
19小时前
|
Web App开发 搜索推荐 前端开发
【热门话题】Chrome 插件研发详解:从入门到实践
本文详细介绍了Chrome插件的开发,从基础概念到实战技巧。首先,解释了插件的结构,包括manifest.json、背景脚本、内容脚本和UI界面。接着,阐述了生命周期、通信机制以及开发步骤,包括创建项目结构、编写manifest.json、开发脚本和UI,以及测试与调试。通过一个显示当前页面URL的插件实例,展示了具体实现过程。最后,讲解了如何在Chrome Web Store发布和分发插件。Chrome插件开发为开发者提供了创造个性化体验的平台,本文旨在引导读者入门并深入实践。
11 2
|
2月前
|
Web App开发 内存技术
chrome插件安装方法教程
chrome插件安装方法教程
|
3月前
|
Web App开发 JSON 前端开发
6款开发必备的Chrome谷歌浏览器扩展(部分火狐、edge浏览器商店也可以用)
6款开发必备的Chrome谷歌浏览器扩展(部分火狐、edge浏览器商店也可以用)
40 0
|
4月前
|
Web App开发 安全 定位技术
Chrome浏览器书签同步插件floccus与坚果云的协同使用方法
Chrome浏览器书签同步插件floccus与坚果云的协同使用方法