微前端大炮打蚊子?看看如何轻量化跨平台开发(附源码)

前言

你是否遇到过一下几种情况

  • 某些和项目契合度不高的页面,因为业务需要,也被放到项目中去开发,造成体积臃肿
  • 跨团队协作开发的时候,对方要塞几个页面到你的项目里
  • 高复用页面,很多项目都有类似的几个页面,但是要维护好几份代码
  • 大炮打蚊子,接入微前端,其实只有简单的几个页面

浅析思考

微前端的加载逻辑是什么?其实他就是通过一个入口去请求资源,也就是我们配置的entry。然后主应用提供一个容器,把子应用的代码运行在这个容器下。

微前端其实去加载的就是一个html文件,然后通过html文件去加载对应的cssjs文件。

那如果我们自己去手撸一个加载器呢?我们只需要通过script去加载一个bundle文件,是否就能实现页面级的微前端呢?

写在前面

目前我使用的是Vue3框架,目前跨Vue2,和Vue3都是可以实现的。对于React的用户,可能方式需要再调整一下,但是整体思路应该都是相通的。

加载器代码

html 复制代码
<template>
  <component
     v-if="mode"
    :is="mode"
    :extraData="extraData"
    v-bind="{ ...attrs, ...$attrs }"
  ></component>
  <div v-if="isError">
    <b>
      {{ pluginData.libraryName }}
    </b>
    加载失败!!尝试点击
    <el-button type="text" @click="openUrl(pluginData.url)">
    刷新
    </el-button>
    查看是否为浏览器权限问题!
  </div>
</template>

<script>
  import { markRaw, defineComponent } from 'vue';
  export default defineComponent({
    name: 'RemotePlugin',
    emits: ['loaded'],
    props: {
      // 插件需要的数据
      pluginData: {
        type: Object,
        default: () => {},
      },
      // 额外需要传输的数据
      extraData: {
        type: Object,
        default: () => {},
      },
    },
    data() {
      return {
        mode: '',
        attrs: {},
        isError: false,
      };
    },
    mounted() {
      this.load();
    },
    methods: {
      asyncScript(url, name) {
        // 动态script
        return new Promise((resolve, reject) => {
          const script = document.createElement('script');
          const target = document.head;
          script.type = 'text/javascript';
          script.src = url;
          script.id = name;
          script.onload = function () {
            resolve(window);
          };
          script.onerror = () => {
            this.isError = true;
            console.error('加载失败');
            window[name] = null;
            reject();
          };
          target.parentNode?.appendChild(script);
        });
      },
      load() {
        this.loadScript(this.currentLoadConfig);
      },
      /**
       * 加载js
       * @param url js文件地址
       * @param libraryName 包名
       * @return {Promise<void>}
       */
      loadScript(config) {
        const { url, libraryName } = this.pluginData;
        let cp = window[libraryName];
        this.asyncScript(url, libraryName).then(() => {
          cp = window[libraryName];
          if (cp.AsyncPluginComp) cp = cp.AsyncPluginComp;
          cp.inheritAttrs = true;
          this.mode = markRaw(cp);
          this.attrs = {
            [cp.__scopeId]: '',
          };
          this.$emit('loaded');
        });
      },
     
    },
  });
</script>

通信问题

可能有的朋友会问了,微前端通过setGlobalState可以进行父子应用通信,我们这种方式怎么通信呢?

我们是通过组件加载的方式,去加载的子页面,所以天生就支持原生的父子组件通信方式。可以通过propsemits,比微前端更便捷。

如何使用

html 复制代码
<template>
  <remote-plugin :pluginData="pluginData" :extraData="extraData"></remote-plugin>
</template>

<script setup>
  import RemotePlugin from '@/components/RemotePlugin.vue';
  const extraData = {
    foo:'bar'
  };
  const libraryName = 'libraryName';
  const pluginData = {
    url: `http://localhost:8888/demo.js`,
    libraryName,
  };
</script>

关于主实例

既然我们上面说了,子页面是通过组件的方式去加载的。那么对于Vue的实例,页面在打包时,就不能使用自己的Vue实例,而是需要通过globals加载主应用对外暴露的Vue实例。 所以子页面项目在打包的时候需要配置下

js 复制代码
 rollupOptions: {
    external: ['vue', 'vue-router'],
    output: {
        globals: {
            vue: 'globalVue',
            'vue-router': 'globalVueRouter',
        }
    }
}

这样子页面在使用import {ref} from 'vue'的时候,指向的就是主应用的Vue实例对象。

这样,只要在主应用注册过的公共组件,插件,在子页面就可以丝滑的使用,没有任何负担。

对了,子页面使用globals去加载,其实就是去window.globalVue去找,这个时候,主应用也需要适配下,通过window对外暴露

js 复制代码
import * as Vue from 'vue';
import * as VueRouter from 'vue-router';

window.globalVue = Vue;
window.globalVueRouter = VueRouter;

关于持久化和路由的支持

关于持久化,无论你是使用vueX还是Pinia都可以做到支持。目前我使用的是Pinia可以通过props传入,然后在子页面使用。比如:

html 复制代码
主应用代码
<template>
  <remote-plugin :pluginData="pluginData" :extraData="extraData"></remote-plugin>
</template>
<script setup>
  import RemotePlugin from '@/components/RemotePlugin.vue';
  import {
    usePublicStore,
    useInstanceStore,
    useAppStore,
  } from '@/store';
  const extraData = {
    storeData: {
        usePublicStore,
        useInstanceStore,
        useAppStore
    }
  };
  const libraryName = 'libraryName';
  const pluginData = {
    url: `http://localhost:8888/demo.js`,
    libraryName,
  };
</script>
js 复制代码
子页面代码

import { defineProps,provide } from 'vue'
const props = defineProps({
  extraData: {
    required: true,
    type: Object,
    default:() => {}
  }
})
const { usePublicStore,useInstanceStore,useAppStore } = props.storeData.storeData
const publicStore = usePublicStore()
const instanceStore = useInstanceStore()
const appStore = useAppStore()
//向下注入 方便使用
provide('publicStore',publicStore)
provide('instanceStore',instanceStore)
provide('appStore',appStore)

关于路由,如果你的子页面下面还区分路由,那么在使用的时候需要把主应用对应页面的路由传下去,然后子页面基于主应用传入的路由,使用addRoute进行注册。比如:

js 复制代码
主应用代码

<template>
  <remote-plugin :pluginData="pluginData" :extraData="extraData"></remote-plugin>
</template>
<script setup>
  import RemotePlugin from '@/components/RemotePlugin.vue';
  const extraData = {
    parentRouteName:'aaa',
    parentRoutePath: '/aaa',
  };
  const libraryName = 'libraryName';
  const pluginData = {
    url: `http://localhost:8888/demo.js`,
    libraryName,
  };
</script>
js 复制代码
子页面代码

import { defineProps } from 'vue'
import {useRouter,useRoute} from 'vue-router'
const props = defineProps({
  extraData: {
    type: Object
  }
})
const { parentRouteName, parentRoutePath } = props.extraData
const router = useRouter()
const route = useRoute()
const routes = [
    {
        path:'first',
        name: 'first',
        component:() => import('./first.vue')
    },
    {
        path:'second',
        name:'second',
        component:() => import('./second.vue')
    }
]

routes.forEach(child => {
    router.addRoute(parentRouteName, child)
})

关于Vue2和Vue3兼容问题

本人亲测,不管是Vue2打包出来的bundle在Vue3项目下,还是Vue3项目打包出来的bundle放在Vue2下运行,都是可以实现的。

如果是React项目想在Vue下运行,也不是不可以,只不过麻烦的点在于不能去使用组件注册的方式了,而是需要有一个根节点,去使用mount的方式加载,子页面暴露出去的也不再是一个页面,而是需要暴露出去一个React实例。

关于qiankun兼容性问题

如果是在qiankun下使用,需要使用window.proxy去访问Vue实例对象

相关推荐
一只会跑会跳会发疯的猴子6 分钟前
ajax访问阿里云天气接口,获取7天天气
前端·ajax·okhttp
利刃大大8 分钟前
【在线五子棋对战】五、前端扫盲:html && css && javascript && ajax && jquery && websocket
前端·javascript·html
石小石Orz14 分钟前
被谷歌插件劝退,我半小时学会了油猴脚本开发
前端
阁下何不同风起?34 分钟前
前端导出PDF(适配ios Safari浏览器)
前端·pdf
zhangxingchao41 分钟前
Flutter网络编程与数据存储技术
前端
啪叽43 分钟前
探索鲜为人知的浏览器API:document.currentScript的实用案例
前端·javascript·dom
DuxWeb1 小时前
为什么 React 如此简单:5分钟理解核心概念,快速上手开发
前端·react.js
陈随易1 小时前
VSCode v1.101发布,MCP极大增强关联万物,基于VSCode的操作系统雏形已初见端倪
前端·后端·程序员
工呈士1 小时前
Vite 及生态环境:新时代的构建工具
前端·面试