likes
comments
collection
share

写了个vite插件,为vue3跨级通信增加了新选项:离线消息

作者站长头像
站长
· 阅读数 9

大家好,我是苏先生,一名热爱钻研、乐于分享的前端工程师,跟大家分享一句我很喜欢的话:人活着,其实就是一种心态,你若觉得快乐,幸福便无处不在

github与好文

正文

要说灵感这东西吧,还真是挺神奇的,写了这么多年业务了,直到昨天晚上,才突然想起这样一个话题:离线消息

那么,什么是离线消息呢?

这个概念一般用在客户端与服务端这种网络交互上,具体而言,是指在通信应用程序中,当接收方不在线或者不可用时,发送方发送的消息会被保存在服务器上,直到接收方再次上线或者可用时,才会被传送给接收方

那么什么是vue3的离线消息呢?

假设,现在有AB两个组件要进行消息互换,其中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-pluspostMessage接口进行重写,在这一过程中对相关的数据进行存储。当然,除了这个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
评论
请登录