目的
远程调试页面的工具有很多,像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悬浮按钮:
eruda
Eruda 是一个专为手机网页前端设计的调试面板,类似 DevTools 的迷你版,其主要功能包括:捕获 console 日志、检查元素状态、捕获XHR请求、显示本地存储和 Cookie 信息等等。官方文档
注入后页面右下角将会多出一个图标为齿轮浮层:
各取所需吧,感觉这两个都很不错。但由于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页面大概长这样:
收尾
上面的步骤都完成以后,将代码以npm包的形式发布到任意一个registry就可以了,在ZanProxy的插件管理页面添加一个插件,填写好包名和registry,尽情享受你的调试插件吧!~ 当然也可以直接用我写好且已经发布了的插件,包名:zan-proxy-remote-debug-plugin
,registry空着不填就可以(默认是registry.npmjs.org/)。