开发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,再深入的就没有具体分析了



目录
相关文章
|
Web App开发 安全 iOS开发
基于PyCharm与Mac系统的Chrome历史记录清理工具开发实战
《基于PyCharm与Mac系统的Chrome历史记录清理工具开发实战》详细解析了如何在macOS下通过Python脚本自动化清理Chrome浏览器的历史记录。文章以`clear_chrome_history.py`为例,结合PyCharm开发环境,深入讲解技术实现。内容涵盖进程检测、文件清理、虚拟环境配置及断点调试技巧,并提供安全增强与跨平台适配建议。该工具不仅保障个人隐私,还适用于自动化运维场景,具备较高实用价值。
579 0
|
11月前
|
Web App开发 人工智能 IDE
从痛点到解决方案:为什么我开发了Chrome元素截图插件
传统的截图方式要么截取整个页面然后手动裁剪,要么使用浏览器自带的截图功能,但效果都不理想。特别是当内容包含SVG元素或复杂样式时,截图质量和速度、便捷性往往不尽如人意。
411 4
|
Web App开发 JavaScript 前端开发
Node.js 是一种基于 Chrome V8 引擎的后端开发技术,以其高效、灵活著称。本文将介绍 Node.js 的基础概念
Node.js 是一种基于 Chrome V8 引擎的后端开发技术,以其高效、灵活著称。本文将介绍 Node.js 的基础概念,包括事件驱动、单线程模型和模块系统;探讨其安装配置、核心模块使用、实战应用如搭建 Web 服务器、文件操作及实时通信;分析项目结构与开发流程,讨论其优势与挑战,并通过案例展示 Node.js 在实际项目中的应用,旨在帮助开发者更好地掌握这一强大工具。
662 1
|
11月前
|
Web App开发 人工智能 前端开发
产品发布策略:如何让Chrome插件在竞争激烈的市场中脱颖而出
Chrome Web Store每天新增很多个插件。插件刚发布,用户只有我自己,如何在这样的红海市场中找到自己的位置,是我一直在思考的问题。
404 0
|
Web App开发 存储 开发者
Chrome 插件上架发布全流程指南
浏览器插件开发完以后,要发布到 Chrome Web Store上,也是需要颇费一番周折的,本文就从注册账号开始,一直到最后发布上架的全流程进行指导,希望帮助你提供一些经验,避免踩坑,耗时耗力。
1923 8
|
Web App开发 JavaScript 前端开发
使用 Chrome 浏览器的内存分析工具来检测 JavaScript 中的内存泄漏
【10月更文挑战第25天】利用 Chrome 浏览器的内存分析工具,可以较为准确地检测 JavaScript 中的内存泄漏问题,并帮助我们找出潜在的泄漏点,以便采取相应的解决措施。
1971 9
|
Web App开发 人工智能 自然语言处理
WebChat:开源的网页内容增强问答 AI 助手,基于 Chrome 扩展的最佳实践开发,支持自定义 API 和本地大模型
WebChat 是一个基于 Chrome 扩展开发的 AI 助手,能够帮助用户理解和分析当前网页的内容,支持自定义 API 和本地大模型。
1713 1
|
Web App开发 数据采集 存储
WebDriver与Chrome DevTools Protocol:如何在浏览器自动化中提升效率
本文探讨了如何利用Chrome DevTools Protocol (CDP) 与 Selenium WebDriver 提升浏览器自动化效率,结合代理IP技术高效采集微博数据。通过CDP,开发者可直接操作浏览器底层功能,如网络拦截、性能分析等,增强控制精度。示例代码展示了如何设置代理IP、cookie及user-agent来模拟真实用户行为,提高数据抓取成功率与稳定性。适用于需要频繁抓取互联网数据的应用场景。
1897 3
WebDriver与Chrome DevTools Protocol:如何在浏览器自动化中提升效率
|
Web App开发 JSON 安全
Chrome浏览器的跨域问题
【10月更文挑战第6天】
3069 123