移动开发快速预览调试的实现

935 阅读3分钟

规律是创造与发现的,记忆是规律的推导与提取

背景

之前在做vue2移动端项目的时候做了一个快速扫码手机端预览的功能,在调试阶段很好用,于是想把它迁入到vue3项目中,本来以为很简单,结果遇到几个问题卡了我好久

  1. 全局变量的引入
  2. 自定义指令
  3. createApp的指令不共用(render的时候指令要已经挂载到元素上)

需求

在web端开发时,想在手机端快速看到效果,扫码并在浏览器端打开是最方便的选择。同时如果想在app中预览,则提供复制功能,通过app的读取粘贴内容+scheme跳转功能实现webview同步预览。

实现思路

  1. 利用hmr同步相同ip的调试页面
  2. 在development阶段动态获取本机IP并注入到开发环境
  3. 构建组件,根据IP动态生成相应的调试url,并生成相应的二维码和复制内容
  4. 通过路由query参数判断是否展示组件

实现

利用hmr同步相同ip的调试页面

webpack和vite都提供了hmr功能

在development阶段动态获取本机IP并注入到开发环境

nodejs可以很容易获取到本机ip:

const os = require("os");
function GetIp() {
  let ifaces = os.networkInterfaces();
  for (let i in ifaces) {
    for (let j in ifaces[i]) {
      let alias = ifaces[i][j];
      if (
        alias.family === "IPv4" &&
        alias.address !== "127.0.0.1" &&
        !alias.internal
      ) {
        return alias.address;
      }
    }
  }
}
export default GetIp()

获取到ip之后,webpack通过process.env注入

new webpack.DefinePlugin({
      'process.env': {IP: GetIp()}
  })

vite通过define注入

  define: {
    VITE_DEV_IP: JSON.stringify(GetIp())
  }

构建组件

这里只展示vue3的组件

<style lang="less" scoped>
.dev-code {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  right: 0;
  background-color: #ffffff;
  .tips {
    font-size: 0.18rem;
    color: #000;
    margin: 0.1rem 0;
    text-align: center;
  }
  .close-btn {
    width: 0.4rem;
    height: 0.4rem;
    position: absolute;
    left: 0;
    right: 0;
    bottom: -0.5rem;
    margin: auto;
    img {
      width: 100%;
      height: 100%;
    }
  }
}
</style>
<template>
  <Teleport to="body">
    <section
      class="dev-code"
      v-show="visible == true"
      v-clipboard:copy="url"
      v-clipboard:success="Tip"
    >
      <qrcode :value="url" id="qrcode" />
      <div class="tips">扫码手机联调</div>
      <div class="tips">点击复制链接</div>
      <a href="javascript:;" class="close-btn" @click.stop="visible = false">
        <img src="https://xxxcdn.xxx.cn/common/close-grey.png" />
      </a>
    </section>
  </Teleport>
</template>
<script lang="ts" setup>
// 调试用二维码
import qrcode from "../qrcode.vue";
import { ref } from "vue";
import { toast } from "../toast/useToast";
defineProps({
  url: {
    type: String,
    // 这里vite通过define定义的全局变量会注入到window对象上,所以命名要独特
    default: `${location.protocol}//${window.VITE_DEV_IP}:${location.port}${location.pathname
      }${location.search.replace("debug=1", "")}`,
  },
});
const visible = ref(true);
function Tip() {
  toast("已成功复制,去浏览器打开吧,注意要和电脑在同一局域网哦~");
}
</script>

// 构建渲染函数
import { mountComponent } from "../../lib/utils/mount-component";
import devCodeConstructor from "./devCode.vue";
const debug = () => {
  return mountComponent(devCodeConstructor);
};
export default debug;

其中渲染函数可以参照上一篇文章

通过路由query参数判断是否展示组件

给router添加一个拦截器,在开发环境读取到指定的query参数则展示组件

router.afterEach((to, from) => {
  if (import.meta.env.MODE === 'development' 
  && !!to.query.debug 
  && +to.query.debug === 1) {
    debug();
  }
});

问题

全局变量的引入

之前webpack是用过 DefinePlugin注入process.env来实现

新框架使用了vite,vite也提供了全局变量注入的功能,主要是采用dotenv从你的环境目录中的文件加载额外的环境变量,不过dotenv文件都是静态的,我不想让其他开发人员还要自己去查ip写进去,所有就找到了define options 把动态的变量注入到window对象上,不过这个方法需要注意,注入的数据必须为Json.stringify反序列化的字符串,字符串本身也要反序列化一下,一切都是为了懒^_^

dotenv方式注入的全局变量可以通过import.meta.env.development.xxx来获取,可以参考官方文档, define options 也可以参考官方文档

自定义指令

自定义指令主要为了实现复制功能,可以参考我之前的一篇文章

createApp的指令不共用(render的时候指令要已经挂载到元素上)

新的createApp不会公用其他createApp实例的指令,于是对之前的方法做了一个扩展:

import { Component, createApp } from "vue";
import DirectiveExtensions from "../../directive";
export function mountComponent(rootComponent: Component) {
  // 添加自定义 指令
  const app = createApp(rootComponent).use(DirectiveExtensions);
  const root = document.createElement("div");
  document.body.appendChild(root);
  return {
    instance: app.mount(root),
    unmount() {
      app.unmount();
      document.body.removeChild(root);
    },
  };
}

实现效果

微信图片_20220325175222.png