浅聊微前端
写在前面
微前端技术已经很成熟了,现在市面上最火的框架就是乾坤了,今天就来展示下简单的应用,以及为什么我们不使用原生的标签iframe来代替其他系统的嵌入。
什么是微前端
术语: 微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。
简单讲:对于前端而言,就像是一个大型的项目允许加入多种框架一起开发,各框架之间相互独立,又可以进行联系。
常见微前端解决方案
- 基于iframe完全隔离的方案
- 基于single-spa路由劫持方案
- 阿里 qiankun
- 京东micro-app方案
为什么不推荐iframe
主要原因:通信问题无法解决
- 页面刷新状态丢失:
当我们嵌入iframe时,点击按钮刷新页面,会导致浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
- 样式的局限性 :
iframe的样式修改起来复杂就不说了,而且他的样式展示是比较局限的,不是相对于整个浏览器,而是文档的一个弹框。
- 数据的通信 :
不同框架之间或应用之间或多或少会存在通信的问题,最常见的就是登陆问题,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;
}
转载自:https://juejin.cn/post/7231081485319422010