规律是创造与发现的,记忆是规律的推导与提取
背景
之前在做vue2移动端项目的时候做了一个快速扫码手机端预览的功能,在调试阶段很好用,于是想把它迁入到vue3项目中,本来以为很简单,结果遇到几个问题卡了我好久
- 全局变量的引入
- 自定义指令
- createApp的指令不共用(render的时候指令要已经挂载到元素上)
需求
在web端开发时,想在手机端快速看到效果,扫码并在浏览器端打开是最方便的选择。同时如果想在app中预览,则提供复制功能,通过app的读取粘贴内容+scheme跳转功能实现webview同步预览。
实现思路
- 利用hmr同步相同ip的调试页面
- 在development阶段动态获取本机IP并注入到开发环境
- 构建组件,根据IP动态生成相应的调试url,并生成相应的二维码和复制内容
- 通过路由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);
},
};
}
复制代码