likes
comments
collection
share

浅聊微前端

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

写在前面

微前端技术已经很成熟了,现在市面上最火的框架就是乾坤了,今天就来展示下简单的应用,以及为什么我们不使用原生的标签iframe来代替其他系统的嵌入。

什么是微前端

术语: 微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

简单讲:对于前端而言,就像是一个大型的项目允许加入多种框架一起开发,各框架之间相互独立,又可以进行联系。

常见微前端解决方案

  1. 基于iframe完全隔离的方案
  2. 基于single-spa路由劫持方案
  3. 阿里 qiankun
  4. 京东micro-app方案

为什么不推荐iframe

主要原因:通信问题无法解决

  1. 页面刷新状态丢失

当我们嵌入iframe时,点击按钮刷新页面,会导致浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。

  1. 样式的局限性

iframe的样式修改起来复杂就不说了,而且他的样式展示是比较局限的,不是相对于整个浏览器,而是文档的一个弹框。

  1. 数据的通信

不同框架之间或应用之间或多或少会存在通信的问题,最常见的就是登陆问题,iframe对于跨域的通信是比较难搞的,这也是放弃iframe的最要原因

qiankun安装与使用

这里只是介绍简单的安装与使用,因为我们公司的项目并没有用到乾坤所以只是大概的研究,如果有问题的小伙伴可以参考官网:qiankun.umijs.org/zh/guide

以vue为例进行安装与使用

首先创建应用文件

在D盘下创建文件夹qiankun,在文件夹乾坤下创建三个项目,主应用parent,子应用child1 child2

vue cerate qiankun-test

vue create child-one

vue create child-two

安装依赖,保证每个项目都能正常运行

修改child-one和child-two的config文件,方便后续被主应用parent访问

child1:

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
    lintOnSave: false,
    devServer: {
        port: "6001",
        headers: {
            "Access-Control-Allow-Origin": "*", //所有人都可以访问我的服务器
        },
    },
    configureWebpack: {
        output: {
            // library: `${name}-[name]`,
            library: `vueChildOne`,
            libraryTarget: "umd", // 把微应用打包成 umd 库格式
            // jsonpFunction: `webpackJsonp_${name}`,
        },
    },
})

child-two:

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
    lintOnSave: false,
    devServer: {
        port: "6002",
        headers: {
            "Access-Control-Allow-Origin": "*", //所有人都可以访问我的服务器
        },
    },
    configureWebpack: {
        output: {
            // library: `${name}-[name]`,
            library: `vueChildTwo`,
            libraryTarget: "umd", // 把微应用打包成 umd 库格式
            // jsonpFunction: `webpackJsonp_${name}`,
        },
    },
})

为了避免后续的语法检查问题,我们这里分别修改child-one 和child-twod的配置文件package.json,在eslitConfig中添加如下代码:

"globals": {
  "__webpack_public_path__": true
}

主应用安装qiankun

浅聊微前端

为了方便展示,我们在主应用qiankun-test中创建文件夹views,在views中创建两个组件childOne 和childtwo分别用来做为应用child-one和child-rwo的容器。

浅聊微前端

添加路由

src下创建文件router文件夹添加router.js

import { createRouter, createWebHistory } from "vue-router";

// 2. 配置路由
const routes = [
    {
        path: '/child-one',
        component: () => import('@/views/childOne/index.vue'),
    },
    {
        path: '/child-two',
        component: () => import('@/views/childTwo/index.vue'),
    },
];
const router = createRouter({
    history: createWebHistory(),
    routes,
});


export default router

配置mian.js

import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'

import { registerMicroApps } from 'qiankun';

registerMicroApps([
    {
        name: 'vueChildOne',
        entry: '//localhost:6001',
        props: { age: 10 }, //给子应用传数据
        container: '#child-one-content',
        activeRule: '/child-one',
    },
    {
        name: 'vueChildTwo',
        entry: '//localhost:6002',
        container: '#child-two-content',
        activeRule: '/child-two',
    }
]);


createApp(App).use(router).mount('#app-base')

container: '#child-two-content',是用来确定应用的容器节点,activeRule主要是用来做对应的路哟跳转,这里不能随意修改,要与router.js中配置的路由保持一致。

APP.vue

<template>
  <div>
    <div class="bens">
      <button class="btns" @click="$router.push('/child-one')">childOne</button>
      <button class="btns" @click="$router.push('/child-two')">childTwo</button>
    </div>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "App",
  components: {},
};
</script>

child-one:

<template>
  <h2>我是 主应用 one</h2>
  <div id="child-one-content"></div>
</template>

<script>
import { start } from "qiankun";
export default {
  name: "childOne",
  components: {},
  mounted() {
    if (!window.qiankunStarted) {
      window.qiankunStarted = true;
      start();
    }
  },
};
</script>

子应用配置

我们以child-one为例进行举例

首先是在src下创建一个public-path.js文件,内容如下:

if (window.__POWERED_BY_QIANKUN__) {
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}

文件作用:

解决因为qiankun造成的路径报错问题,后续会在在main.js中引入。

main.js中的配置如下:

import { createApp } from 'vue'
import App from './App.vue'

import './public-path'
import router from "@/router";

// createApp(App).mount('#app')

let instance = null;
function render(props = {}) {
    if (instance) return;
    const { container } = props;
    console.log(container);
    instance = createApp(App).use(router).mount(container ? container.querySelector("#app-child-one") : "#app-child-one");
}

// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
    render();
}

export async function bootstrap() {
    console.log("[vue] vue app bootstraped");
}
export async function mount(props) {
    console.log("[vue] props from main framework", props);
    render(props);
}
export async function unmount() {
    //可选链操作符
    instance.$destroy?.();
    instance = null;
}

app.vue

<template>
  <img alt="Vue logo" src="./assets/logo.png">
<!--  <router-view></router-view>-->
</template>

<script>

export default {
  name: 'App',
  mounted() {
    console.log(23232)
  }
}
</script>

这样我们的基本页面就写好了,看下基本的效果:

点击childOne:

浅聊微前端

子应用添加路由配置

避坑: 我们在安装路由插件vue-router时,一定要保证各应用的插件是同一个版本的,不然后期使用的时候会出现冲突。

以child-one为例,我们在src下创建新的路由,router.js

import { createRouter, createWebHashHistory } from "vue-router";

// 2. 配置路由
const routes = [
    {
        path: '/',
        component: () => import('@/views/home/index.vue'),
    },
    {
        path: '/world',
        component: () => import('@/views/World/World'),
    },

];
// 1.返回一个 router 实列,为函数,里面有配置项(对象) history
const router = createRouter({
    history: createWebHashHistory('/child-one'),
    routes,
});

export default router

这里我使用的router版本是

"core-js": "^3.8.3",
"vue": "^3.2.13",
"vue-router": "^4.1.6"

在main.js中使用方式如下:

import router from "@/router";
function render(props = {}) {
    if (instance) return;
    const { container } = props;
    console.log(container);
    instance = createApp(App).use(router).mount(container ? container.querySelector("#app-child-one") : "#app-child-one");
}

这里大家可以随意的配置路由,路由出口放在app.vue就好了

app.vue

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <router-view></router-view>
</template>

<script>

export default {
  name: 'App',
  mounted() {
    console.log(23232)
  }
}
</script>

应用one和应用two之间的通信

vue中上下属之间有一个通信方式是props,这里也是props

浅聊微前端

在加载的时候可以通过打印查看传的参数:

export async function mount(props) {
    console.log("[vue] props from main framework", props);
    render(props);
}

浅聊微前端

在two中通过缓存的方式获取到one中的数据:

one中:

export async function mount(props) {
    console.log("[vue] props from main framework", props);
    render(props);
    sessionStorage.setItem('childone',props.age)
}

two中

export async function mount(props) {
    console.log("[vue] props from main framework", props);
    render(props);
    const val = sessionStorage.getItem('childone')
    console.log(val)
}

打印结果如下:

浅聊微前端

乾坤的生命周期函数

// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
    render();
}
//只有项目在第一次加载时才会被调用
export async function bootstrap() {
    console.log('[vue] vue app bootstraped');
}
//项目加载前都会被计用
export async function mount(props) {
    /*
    挂载全局,在组件中可使用this.$actions.setGlobalState(state)修改状态,
    触发子应用及主应用的监听
    */
    Vue.prototype.$actions = props.actions
    props.onGlobalStateChange((state, prev) => {
        //state变更后的状态,prev变更前的状态  使用 actions.setGlobalState(state);改变状态,触发状态管理机的监听
        console.log('父应用检测到变化')
    })
    console.log('[vue] props from main framework', props);
    render(props);
}
//应用在销毁时调用
export async function unmount() {
    instance.$destroy();
    instance.$el.innerHTML = '';
    instance = null;
    router = null;
}

代码地址:github.com/wyk-6987/qi…

转载自:https://juejin.cn/post/7231081485319422010
评论
请登录