写了个vite插件,为vue3跨级通信增加了新选项:离线消息
大家好,我是苏先生,一名热爱钻研、乐于分享的前端工程师,跟大家分享一句我很喜欢的话:人活着,其实就是一种心态,你若觉得快乐,幸福便无处不在
github与好文
正文
要说灵感这东西吧,还真是挺神奇的,写了这么多年业务了,直到昨天晚上,才突然想起这样一个话题:离线消息
那么,什么是离线消息呢?
这个概念一般用在客户端与服务端这种网络交互上,具体而言,是指在通信应用程序中,当接收方不在线或者不可用时,发送方发送的消息会被保存在服务器上,直到接收方再次上线或者可用时,才会被传送给接收方
那么什么是vue3
的离线消息呢?
假设,现在有A
和B
两个组件要进行消息互换,其中B
组件是被v-if
控制的仅在某种条件成立时才会渲染。此时,如果A
消息发出了但B
组件尚未渲染,则消息会丢失
如果能让A
消息留存并等待B组件满足条件渲染后自动重新推送一次,是为vue3
的离线消息
如果不支持离线怎么解决?
首先,由于vue3
已经不支持原生的eventBus
了,所以得选一个第三方库来做跨级消息传递。此处,笔者选择web-localstorage-plus。该包提供了命名空间、pinia
持久化、批量操作、过期时间、bus
等9类操作
倒也不是因为这个库有多牛逼,单纯是因为它是笔者写的,可以很容易进行修改以完成本文的目的
首先使用yarn
安装一下
yarn add web-localstorage-plus
接着,在main.ts
入口文件中初始化一下
import createStorage from 'web-localstorage-plus';
createStorage({
rootName: 'spp-storage',
});
一般对于一个可能不总是在线的组件,常规的做法是搞个“中转站”,一般来说,是放到localstorage
中一份。就,很巧的是web-localstorage-plus
刚好蛮擅长做这件事的。所以我们在A
组件中设置一个标记,至于是什么并不重要
import {useStorage} from 'web-localstorage-plus';
const storage = useStorage()
// 在offline命名空间下存一个key为offline-msg值为1的消息
storage.setItem('offline-msg',1,'offline')
接着,当B
组件渲染后,在其onMounted
生命周期里,读取该标识,并做一些定制化处理
import {useStorage} from 'web-localstorage-plus';
import { onMounted } from 'vue';
const storage = useStorage()
onMounted(()=>{
const offMsg = storage.getItem('offline-msg','offline')
if(offMsg){
// do somthing
}
})
这样就完成了一个类离线的效果。这当然不是不能用啦。只是笔者觉得,每次都手动处理是比较麻烦的!于是,今天就花了几个小时进行了下实现
首先,搭一个vite
插件的架子:
让它的运行周期在组件源码被change
前,这样好处理些;用一个name
字段标识插件唯一的名称,这样出错时候用户也好根据vite
的提示好排查一些;接着利用transformIndexHtml
钩子去做一些初始化的动作;最后借助transform
钩子对源码进行定制化
export function offline(entry?: string): Plugin {
enforce:'pre',
name: "vite:web-localstorage-plus-offline",
transformIndexHtml() {
...
},
transform(){
...
}
}
首先,进入transformIndexHtml
钩子,我们在此处找到项目的入口文件,也就是main.ts
所在的位置,然后返回作为外部变量保存起来备用
如下,通过正则匹配所有的script
标签,并依次读取其src
属性,并与传递的入口进行匹配,默认情况下,就是vite
默认生成的/src/main.ts
此处,笔者就不考虑多入口的情况了,一是不咋常用,二是,真有需要,理论上也是几行代码就能搞定的事情
export default function analize(html: string,entry:string="/src/main.ts") {
let entryPath = "";
let m = helper.scriptRE.exec(html);
while (Array.isArray(m)) {
const [script] = m;
const [_, src] = srcRE.exec(script) || [];
if (src === entry) {
entryPath = src;
break;
}
m = helper.scriptRE.exec(html);
}
if (entryPath && isAbsolute(entryPath)) {
const fullPath = normalize(join(process.cwd(),entryPath))
if(existsSync(fullPath)){
entryPath = fullPath
}else{
entryPath = ''
}
}
return {
html,
fullEntryPath:entryPath
}
}
接着,进入transform
钩子,这是整个程序的核心功能实现处:
首先,笔者对页面中的注释部分进行了清理,因为它可能会影响到后续的overwrite
操作
if (id.startsWith(filtered)) {
try {
code = displace(code);
} catch (_) {}
}
接着,对web-localstorage-plus
库进行重写,笔者之所以没直接在web-localstorage-plus
中进行迭代该功能,是本着功能点分离出发的,但其本身是不能直接支持离线的
如下,笔者对web-localstorage-plus
的postMessage
接口进行重写,在这一过程中对相关的数据进行存储。当然,除了这个postMessage
外,onMessage
也要进行重写处理,另外还要新增online
接口。不过这些都不重要,就像笔者名字所写的那样:思路大于开发,思路才是最重要的
而且,你也可以在文末找到github
源码地址,一边调试一边修改总是要好过干巴巴的文字的吧,你说呢?
const __postMessage = storage.postMessage
storage.postMessage = function(...rest){
const [msg,payload,id] = rest || [];
const key = 'postmessages'
initial(key)
pushOne(key,{id,msg,payload})
__postMessage.call(storage,msg,payload);
}
最后,就是对vue文件进行重写,更准确的讲,是将我们的离线代码塞到vue页面中
众所周知的是,当一个组件被渲染时,会触发onMounted
生命周期,所以我们只需要找到该api
的调用处,然后在其函数体内加上自己的逻辑即可。当然了,这还要考虑兼容组件中是否调用了该api
如下,在ms时间后,调用storage.online
完成离线消息的恢复发送
function overwriteOnMounted(id:string,code?: string) {
const response = {
injectTo: "append",
body: "",
};
if (code) {
let i = code.indexOf(brackets[0]);
if (i > -1) {
i++;
const originBody = helper.extract(code, code.substring(0, i), i, brackets);
const s = helper.str(originBody);
s.appendLeft(originBody.length-1,`import('web-localstorage-plus').then(mod=>{
const timer = setTimeout(()=>{
const storage = mod.useStorage();
storage.online('${id}')
clearTimeout(timer)
},${ms})
})\n`);
response.body = s.toString()+')'
response.injectTo = 'replace'
}
}
return response as {
injectTo:'replace'|'append',
body:string
};
}
最后,还要对web-localstorage-plus
进行下改动,因为笔者期望离线的消息是借助bus
接口触发的,所以要增加ts
定义,但是实际上,也仅仅是为了类型的自动提示,并不作离线相关的逻辑处理
如下,对emit
接口增加mode
参数,用以标记当前消息可能是一次离线消息
function emit(key: string, payload: any): void;
function emit(key: string, payload: any, mode: "__OFFLINE_MODE__"): void;
function emit(key: string, payload: any, mode?: "__OFFLINE_MODE__") {
...
}
使用
- 安装
yarn add @web-localstorage-plus/offline
- 使用
在vite.config.ts中导入作为plugin
import { offline } from '@web-localstorage-plus/offline';
export default defineConfig({
...,
plugins:[
offline()
]
})
了解更多
如果本文对您有用,希望能得到您的点赞和收藏
订阅专栏,每周更新1-2篇类型体操,等你哟😎
转载自:https://juejin.cn/post/7269630060630130749