随着公司项目建设,各系统出现了用户、角色、组织、权限等系统基础功能重复开发的问题,为了不重复开发、减少成本,也为了整合项目,提升用户体验,也为了之前单独开发的系统能够统一访问。所以我们使用“微前端”。
微前端是一种多个团队通过独立发布功能的方式来共同构建现代化web应用的技术手段及方法策略。
微前端借鉴了微服务的架构理念,将一个庞大的前端应用拆分为多个独立灵活的小应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用联合微一个完整的应用。微前端既可以将项目融合为一,又可以减少项目之间的耦合,提升项目扩展性,相比一整块的前端仓库,微前端架构下的前端仓库倾向于更小更灵活。
技术无关
主应用不限制接入应用的技术栈,子应用具备完全的自主权(子应用所用的技术不限制)
独立开发、独立部署
微应用仓库独立升级,前后端可独立开发,部署完成后主框架自动完成同步更新
增量升级
在面对个证负责场景时,我们通常很难对一个已经存在的系统做出全量的技术栈升级或重构,而微前端时一种非常好的实施渐进式重构的手段和策略
独立运行时
每个子应用之间状态隔离,运行时状态不共享
安装qiankun
$ yarn add qiankun # 或者 npm i qiankun -S
在主应用main.js中注册微应用
import { registerMicroApps, start } from 'qiankun'
const apps = [
{
name: 'jdptApp',
entry: '//localhost:30000', // 默认加载这个HTML解析里面的js动态的执行(子应用必须支持跨域)
// fetch //子应用是否允许跨域
container: '#vueJdpt', // 挂载的dom区域
activeRule: '/jdpt' // 激活的路径
}
]
registerMicroApps(apps) 注册微应用
start({
prefetch: false, // 取消预加载
sandbox: true //隔离样式sandbox : { experimentalStyleIsolation: true }
}) // 开启微应用
<template>
<div>
<el-menu router
mode="horizontal">
<!-- 基座中可以放自己的路由 -->
<el-menu-item index="/">Home</el-menu-item>
<!-- 引用其他子应用 -->
<el-menu-item index="/jdpt">机电平台</el-menu-item>
</el-menu>
<!-- // 显示自己的路由 -->
<router-view></router-view>
<!-- 显示子应用路由 -->
<div id="vueJdpt"></div>
</div>
</template>
let instance = null
function render() {
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount("#app") // 挂载到自己的html中,基座会拿到这个挂载后的html 将其插入进去
}
// 如果在乾坤下运行,
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
// 如果独立运行,则直接渲染
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
// 地下三个方法为自组建的协议 内部会校验此方法 必须传,单里面可以不写内容
**
1. bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
2. 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap(props) { }
/**
3. 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
render(props)
}
/**
4. 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
instance.$destroy()
instance = null
}
module.exports = {
devServer: {
port: 30000,
headers: {
'Access-Control-Allow-Origin': '*' // 允许所有跨域
}
},
configureWebpack: {
output: {
library: 'jdptApp',
libraryTarget: 'umd', 打包成 umd 库格式
}
}
}
const routes = [{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () =>
import ( /* webpackChunkName: "about" */ '../views/About.vue')
},
{
path: '/demo',
name: 'Demo',
component: () =>
import ( /* webpackChunkName: "about" */ '../views/Demo.vue')
}
]
console.log(routes, 'routes');
const router = new VueRouter({
mode: 'history',
base: window.__POWERED_BY_QIANKUN__ ? '/jdpt' : process.env.BASE_URL, // 如果在乾坤环境下运行则添加'/jdpt'前缀
routes
})
export default router
// 乾坤全局通信
import { initGlobalState } from 'qiankun'
const initState = {
// 这里写初始化数据
}
const actions = initGlobalState(initState)
actions.onGlobalStateChange((state, preState) => {
console.log(preState, '主应用变更前');
console.log(state, '主应用变更后');
})
export default actions
import actions from '@/action'
const apps = [
{
name: 'jdptApp',
entry: '//localhost:30000', // 默认加载这个HTML解析里面的js动态的执行(子应用必须支持跨域)
// fetch //子应用是否允许跨域
container: '#vueJdpt', // 挂载的dom区域
activeRule: '/jdpt', // 激活的路径
props: {
actions,
msg: '消息'
} // 向子应用传递创建的全局状态(*注意*)
}
]
<template>
<div class="home">基座自己的路由页面
<el-button @click="handle1">点击向子应用发送消息</el-button>
<p>当前显示的项目</p>
</div>
</template>
<script>
// @ is an alias to /src
import actions from '../action'
export default {
name: 'Home',
data () {
return {
mes1: {
project_id: '机电平台'
}
}
},
mounted () {
// 注册一个观察者函数
actions.onGlobalStateChange((state, preState) => {
console.log(preState, '主应用观察者变更前');
console.log(state, '主应用观察者变更后');
})
},
methods: {
handle1 () {
actions.setGlobalState(this.mes1)
this.$router.push('/jdpt')
},
}
}
</script>
function emptyAction() {
// 警告提示,当前使用的是空action
console.warn('current execute action is empty');
}
// 设置一个用于通信的 action类
class Action {
actions={
onGlobalStateChange:emptyAction,
setGlobalState:emptyAction
}
constructor(){
}
// 默认为空Action
// 设置actions
setActions(actions){
this.actions = actions
}
// 映射
onGlobalStateChange(...args:[]){
return this.actions.onGlobalStateChange(...args)
}
// 映射
setGlobalState(...args:[]){
console.log(args,'args');
return this.actions.setGlobalState(...args)
}
}
const actions = new Action()
export default actions
import actions from './action'
function render(props) {
console.log(props,'props');
if (props) {
actions.setActions(props)
}
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount("#app") // 挂载到自己的html中,基座会拿到这个挂载后的html 将其插入进去
}
export async function mount(props) {
// 子应用进入时将数据进行传递
render(props)
}
<template>
<el-button @click="handle1">点我向父应用发送数据</el-button>
</template>
<script>
import actions from "@/action"
// 在mounted中
mounted () {
actions.onGlobalStateChange(state=>{
console.log(state,'子应用检测数据');
},true) // onGlobalStateChange 第二个参数设置为true,会立即触发一次观察者函数
},
methods: {
handle1(){
actions.setGlobalState({project_id:'机电子应用'})
}
}
</script>
后续会更新项目中遇到的问题。。。