前面开发移动端功能的时候,发现调试并不是很方便,主要体现在部分兼容性问题和体验上,浏览器模拟的手机并不能完全替代。
部分功能只有上线后,才能在手机上发现错误,然后机缘巧合下发现了腾讯开源的 vConsole
工具。
vConsole
一个轻量、可拓展、针对手机网页的前端开发者调试面板。和框架无关,可以在任何地方进行使用。(微信小程序的官方调试工具)
特性介绍
查看
console
日志查看网络请求
查看页面
element
结构查看
cookie、localStorage、sessionStorage
查看系统版本相关信息
执行命令行脚本
自定义插件
可以理解为就是浏览器 DevTools
的一个缩略版。
安装及初始化
cdn
方式
<!-- 安装 -->
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<!-- 使用 -->
<script>
// VConsole 默认会挂载到 `window.VConsole` 上
var vConsole = new window.VConsole();
</script>
相关资源:
https://unpkg.com/vconsole@latest/dist/vconsole.min.js
>https://cdn.jsdelivr.net/npm/vconsole@latest/dist/vconsole.min.js
npm
方式
npm install vconsole
import VConsole from 'vconsole';
const vConsole = new VConsole();
// 完成调试后,可销毁 vConsole
vConsole.destroy();
TIPS:
VConsole 实例化后,项目里所有的console.log
语句以及错误信息、网络请求等等,都默认会在vConsole
面板进行展示,无需手动额外操作有个前提就是vConsole
完成挂载之后的日志才会在vConsole
面板显示 😲😲😲
使用
在 vConsole
模块成功引入后,会在页面的右下角出现 vConsole
按钮,可以展开/收起调试面板。注意在生产环境屏蔽该功能。
支持原生 console
的几乎大部分功能和语法。eg:
import VConsole from 'vconsole';
const vConsole = new VConsole();
// 测试打印信息均会展示在 vConsole 面板中
console.log({ name: 'hello', age: 18 }); // 支持 object 和 array 等类型变量打印
console.log([{ name: 'hello', age: 18 }]);
console.debug('hello world ', 'hhh'); // 支持多参数打印
console.error('hello world');
console.warn('hello world');
console.info('hello world');
console.table([{ name: 'hello', age: 18 }]); // 不支持
console.dir(document.body); // 不支持
console.time('foo');
console.timeEnd('foo');
console.clear();
// ...
效果如下图所示:
文档
vConsole
本体
初始化 option
配置见如下:公共属性及方法
自定义插件
第三方插件列表
- vConsole-sources
- vconsole-webpack-plugin
- vconsole-stats-plugin
- vconsole-vue-devtools-plugin
- vconsole-outputlog-plugin
- vite-plugin-vconsole
源码剖析
思路
通过
window
监听页面加载,加载完成后向页面追加调试相关的DOM
。类似
log、network
面板等相关的渲染显示,则是通过重写window
下对应的系统方法,追加一些自定义操作完成。
原理实现
构造函数
入口在 src/core/core.js
,采用单例模式,只允许有一个 vconsole
实例:
// ...
class VConsole {
constructor(opt) {
if (!!$.one(VCONSOLE_ID)) {
console.debug('vConsole is already exists.');
return;
}
// ...
vConsole
面板里的模块则是通过插件的形式集成进去的独立模块:
// built-in plugins
import VConsolePlugin from '../lib/plugin.js';
import VConsoleLogPlugin from '../log/log.js';
import VConsoleDefaultPlugin from '../log/default.js';
import VConsoleSystemPlugin from '../log/system.js';
import VConsoleNetworkPlugin from '../network/network.js';
import VConsoleElementPlugin from '../element/element.js';
import VConsoleStoragePlugin from '../storage/storage.js';
加载 DOM
监听 window
事件,确定加载结束后挂载 vconsole
相关 dom
:
// try to init
let _onload = function () {
if (that.isInited) {
return;
}
that._render();
that._mockTap();
that._bindEvent();
that._autoRun();
};
if (document !== undefined) {
if (document.readyState === 'loading') {
$.bind(window, 'DOMContentLoaded', _onload);
} else {
_onload();
}
} else {
// if document does not exist, wait for it
let _timer;
let _pollingDocument = function () {
if (!!document && document.readyState == 'complete') {
_timer && clearTimeout(_timer);
_onload();
} else {
_timer = setTimeout(_pollingDocument, 1);
}
};
_timer = setTimeout(_pollingDocument, 1);
}
Log
模块实现
通过重写 window.console..
的相关方法,执行自己的 printLog
并记录,最后渲染到 vconsole
面板:
// src/log/log.js
// ...
/**
* replace window.console with vConsole method
* @private
*/
mockConsole() {
const that = this;
const methodList = ['log', 'info', 'warn', 'debug', 'error'];
if (!window.console) {
window.console = {};
} else {
methodList.map(function (method) {
that.console[method] = window.console[method];
});
that.console.time = window.console.time;
that.console.timeEnd = window.console.timeEnd;
that.console.clear = window.console.clear;
}
methodList.map(method => {
window.console[method] = (...args) => {
this.printLog({
logType: method,
logs: args,
});
};
});
// ...
}
当 vconsole destroy
后,恢复 window.console
方法:
// src/log/log.js
onRemove() {
window.console.log = this.console.log;
window.console.info = this.console.info;
window.console.warn = this.console.warn;
window.console.debug = this.console.debug;
window.console.error = this.console.error;
window.console.time = this.console.time;
window.console.timeEnd = this.console.timeEnd;
window.console.clear = this.console.clear;
this.console = {};
const idx = ADDED_LOG_TAB_ID.indexOf(this.id);
if (idx > -1) {
ADDED_LOG_TAB_ID.splice(idx, 1);
}
}
Network
模块实现
也是重写了原生的 window.XMLHttpRequest.prototype.open/send
等方法,添加了拦截器:
// src/network/network.js
/**
* mock ajax request
* @private
*/
mockAjax() {
let _XMLHttpRequest = window.XMLHttpRequest;
if (!_XMLHttpRequest) { return; }
let that = this;
let _open = window.XMLHttpRequest.prototype.open,
_send = window.XMLHttpRequest.prototype.send;
that._open = _open;
that._send = _send;
// mock open()
window.XMLHttpRequest.prototype.open = function() {
let XMLReq = this;
let args = [].slice.call(arguments),
// ...
// mock send()
window.XMLHttpRequest.prototype.send = function() {
let XMLReq = this;
let args = [].slice.call(arguments),
data = args[0];
// ...
篇幅原因就不完整呈现源码了,另外文档和第三方插件列表摘自:官方文档:https://github.com/Tencent/vConsole/blob/dev/README_CN.md#L160-L186