使用ZanProxy远程调试页面(插件增强)

1,862 阅读4分钟
原文链接: coderge.com

目的

远程调试页面的工具有很多,像Charles、spy-debugger等等,但是上次介绍ZanProxy的文章中已经把这些工具都扔掉了……所以要想一个办法能实现远程调试,但是又不打脸,于是就有了使用ZanProxy插件的方案,本文将细细道来。

插件

根据官方文档编写插件 · ZanProxy一节,可以得知:

ZanProxy插件是是一个npm包,这个npm包需要导出一个类,这个类要实现两个方法:proxy和manage,除此之外没有任何特殊要求。 proxy方法用于请求的处理,manage方法用于插件的配置。

// 最简单的结构框架
module.exports = class RemoteDebugPlugin {
  proxy() {}
  manage() {}
}

需求

前端的远程调试,无非就是希望能将远端的资源切换成本地的,以及能够看到开发者工具中的一系列信息(如控制台输出、网络请求等)。那么资源的切换可以通过代理去实现(之前的文章中已经讲过,通过ZanProxy的Hosts管理和Http转发),而查看控制台输出的需求,只有当页面的载体是PC端的浏览器才可以(任何端的Webview以及移动端的任何都不行)。 不使用第三方工具,我们能做的就只有在页面上模拟一个开发者工具,通过大量的search和compare,最终有两家入选——vConsole & eruda

vConsole vs eruda

vConsole

腾讯开发的一个轻量、可拓展、针对手机网页的前端开发者调试面板官方文档

注入后页面右下角将会多出一个绿色的vConsole悬浮按钮:

vConsole

eruda

Eruda 是一个专为手机网页前端设计的调试面板,类似 DevTools 的迷你版,其主要功能包括:捕获 console 日志、检查元素状态、捕获XHR请求、显示本地存储和 Cookie 信息等等。官方文档

注入后页面右下角将会多出一个图标为齿轮浮层:

eruda

各取所需吧,感觉这两个都很不错。但由于vConsole的文档更像详细,所以我在这里选择了vConsole。下文均基于vConsole进行介绍,如需使用eruda,改动是非常小的,将vConsole的配置部分按照文档修改成eruda的即可,对于ZanProxy是无感的。

实现proxy方法

不论是使用vConsole还是eruda,其本质都是在页面内添加一段script脚本,所以我们需要拦截html文件,在header部分 插入对应的script脚本后返回给客户端使用。

const { byteLength } = require('byte-length')
const zlib = require('zlib')

proxy() {
  return async (ctx, next) => {
    const contentType = ctx.res.getHeader('content-type')
    const contentEncoding = ctx.res.getHeader('content-encoding')
    if(contentType === 'text/html') {
      // 只拦截html文件
      ctx.res.body.on('end', () => {
        let contentString = ctx.res.body
        // 如果是gzip的话,需要解压
        if(contentEncoding === 'gzip') {
          contentString = zlib.gunzipSync(contentString)
          ctx.res.removeHeader('content-encoding')
        }
        contentString = contentString.toString()
        // 找到插入点(head闭合标签前、第一个script标签前,取最靠前的一个)
        const headEndIndex = contentString.indexOf('</head>')
        const firstScriptIndex = contentString.indexOf('<script>')
        const injectIndex = Math.max(...[headEndIndex, firstScriptIndex].filter(i => i > -1))
        if(injectIndex > -1) {
          // 插入所需要的脚本代码
          const vconsoleScript = '<script src="https://cdn.bootcss.com/vConsole/3.2.0/vconsole.min.js"></script><script>new VConsole();</script>';
          const result = contentString.slice(0, injectIndex) + vconsoleScript + contentString.slice(injectIndex, contentString.length)
          ctx.res.setHeader('content-length', byteLength(result))
          ctx.res.body = result
        }
      })
    }

    await next()
  }
}

实现manage方法

manage方法是为了提供页面来对当前插件进行管理。按照上面方式实现的zan-proxy-remote-debug-plugin插件,vConsole会永久在所有的html页面中展示,所以我们需要通过manage方法在页面中对vConsole的展示与否进行控制。

const Koa = require('koa')
const Static = require('koa-static')
const Router = require('koa-router')
const BodyParser = require('koa-bodyparser')
const path = require('path')
const jsonfile = require('jsonfile')
const os = require('os')

// 配置文件存放路径
const configFilePath = `${os.homedir()}/.front-end-proxy/plugins/zan-proxy-remote-debug-plugin.config.js`
class RemoteDebugPlugin {
  constructor() {
    this.vconsole = true
    // 读取配置文件覆盖默认配置
    const config = jsonfile.readFileSync(configFilePath)
    config && (this.vconsole = config.vconsole)
  }

  proxy() { /* 此处忽略proxy的实现 */}

  manage() {}
}
manage() {
  // manage方法需要返回一个Koa实例
  const app = new Koa()
  // 我们将静态资源放置在static目录下
  app.use(Static(path.resolve(__dirname, './static')))
  app.use(BodyParser())
  // 通过router接收请求,修改配置
  const router = new Router()
  // 列出配置详情
  router.get('/config', async (ctx) => {
    ctx.body = { vconsole: this.vconsole}
  })
  // 修改vConsole启用状态
  router.post('/vconsole', async (ctx) => {
    this.vconsole = ctx.request.body.enable
    jsonfile.writeFileSync(configFilePath, { vconsole: this.vconsole })
    ctx.body = { vconsole: this.vconsole }
  })
  app.use(router.routes())
  app.use(router.allowedMethods())
  return app
}

既然已经有了配置文件,那么我们需要改造一下proxy方法,通过配置来确定是否需要执行页面请求的拦截。

if(contentType === 'text/html') {}

↓↓↓修改为↓↓↓

if(this.vconsole && contentType === 'text/html') {}

实现manage页面

manage页面其实是上面Koa引用的一个静态资源./static/index.html,我们需要实现这个页面。

<input type="checkbox" id="vconsole">启用vConsole
<script>
const configUrl = '/plugins/zan-proxy-remote-debug-plugin/config'
fetch(configUrl).then(res => res.json()).then(config => {
  // 获取配置文件中的配置并设定
  const vConsoleSwitcher = $('#vconsole')
  vConsoleSwitcher.prop('checked', config.vconsole)
  // 用户修改配置时进行同步
  vConsoleSwitcher.change(function (e) {
    fetch('/plugins/zan-proxy-remote-debug-plugin/vconsole', {
      method: 'post',
      body: JSON.stringify({ enable: e.target.checked }),
      headers: { 'Content-Type': 'application/json' }
    })
  })
})
</script>

完成后的manage页面大概长这样: manage页面

收尾

上面的步骤都完成以后,将代码以npm包的形式发布到任意一个registry就可以了,在ZanProxy的插件管理页面添加一个插件,填写好包名和registry,尽情享受你的调试插件吧!~ 当然也可以直接用我写好且已经发布了的插件,包名:zan-proxy-remote-debug-plugin,registry空着不填就可以(默认是registry.npmjs.org/)。