基于Vue.js开发移动端工程时,一些特定的问题和场景下,只能在移动端运行工程复现、追踪问题(比如在微信端内,在App容器内),桌面端的Devtools就没法用了。数次安装Electron版本devtools后,觉得调试起来太麻烦了,所以我决定把devtools搬进vConsole里
如果不知道什么是vConsole,可以看这里vConsole Readme
最终效果:
Sample Code 体验地址: CodePen
Github 地址 欢迎Star
引入非常简单
import VConsole from "vconsole";
import Devtools from 'vue-vconsole-devtools'
Devtools.initPlugin(new VConsole());
效果图:
实现过程
分析完Vue、Vue-devtools、vConsole源码以后,似乎可行。步骤如下:
- 剥离Vue-devtools @front部分
- 实现@backend 和 @front 部分通信
- 实现front注入iframe
- iframe嵌入vConsole
- 制作npm包并发布
1. 剥离front
首先一个问题就是 Vue-devtools并不是一个库,所以npm上没有它,无法引用,其次这个工程也不适合作为库输出,因为它的打包方式比较特殊,还有这个工程本身的目的就是打包成一个可执行的App或者chrome插件,所以如果想引用它里面的代码,我想到最简单的方式就是拷贝了。。。
所以剥离frontend非常简单,在Vue-devtools工程的package.json中增加script:
"buildvc": "cd packages/shell-dev && cross-env NODE_ENV=production webpack --progress --hide-modules",
同时在shell-dev里增加一个文件叫 inject.js:
import { initDevTools } from '@front'
import Bridge from '@utils/bridge'
const targetWindow = window.parent;
document.body.style.overflow = "scroll";
initDevTools({
connect (cb) {
cb(new Bridge({
listen (fn) {
window.addEventListener('message', evt => {
fn(evt.data)
})
},
send (data) {
targetWindow.postMessage(data, '*')
}
}))
},
onReload (reloadFn) {
reloadFn.call();
}
})
当然shell-dev里的webpack.config.js里 增加一个入口配置 inject: './src/inject.js'
打出来的包就是我们要的front部分,最终嵌入iframe里。
2. 实现通信
上面的inject.js中已经包含了 front部分 接收和发送消息的代码了。 接下来完成backend部分的消息发送和接收,
import { initBackend } from '@back'
import Bridge from '@utils/bridge'
const initBackendWithTargetWindow = function(win,targetWindow){
const bridge = new Bridge({
listen (fn) {
win.addEventListener('message', evt => {
fn(evt.data)})
},
send (data) {
targetWindow.postMessage(data, '*')
}
})
initBackend(bridge)
}
export default { initBackendWithTargetWindow }
3. front嵌入iframe
这个比较麻烦,也遇到了一些兼容性问题,最终方案是:
- 把第一步打包的inject.js 重命名为 inject.txt
- 增加一个rawloader规则,识别txt
- 使用script.txt的方式插入到script标签中,然后插入iframe的body中
import injectString from './inject.txt'
function inject (scriptContent, done) {
const script = document.getElementById('vue-iframe').contentWindow.document.createElement('script')
script.text = scriptContent
document.getElementById('vue-iframe').contentWindow.document.body.appendChild(script)
}
inject(injectString)
4. iframe嵌入vconsole
实现一个plugin类,继承VConsolePlugin,然后实现对应的方法即可,具体文档可以查看VConsole文档vConsole Readme
class VConsoleVueTab extends VConsolePlugin {
constructor(...args) {
super(...args);
}
onRenderTab(cb){
cb(`<iframe id="vue-iframe" style="width:100%;position:absolute;top:0;bottom:0;min-height:100%;"></iframe>`);
}
onReady() {
target = document.getElementById('vue-iframe')
targetWindow = target.contentWindow;
be.initBackendWithTargetWindow(window,targetWindow);
}
onShow() {
injectOnce(injectString)
}
}
5. 制作npm包并发布
这一步需要打包发布,并且优化
const initPlugin = function(vConsole){
var tab = new VConsoleVueTab('vue', 'Vue');
vConsole.addPlugin(tab);
}
export default {
initPlugin
}
21-08-03更新,支持CDN引入
<script src="https://unpkg.com/vconsole/dist/vconsole.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-vconsole-devtools@1.0.5/dist/vue_plugin.js"></script>
<script>
var vConsole = new window.VConsole();
const Devtools = window.vueVconsoleDevtools["default"];
Devtools.initPlugin(vConsole);
</script>