微服务-乾坤

乾坤:

目标是将庞大的单体前端应用,拆解成多个可独立开发、部署、运行的小型应用(微应用),并最终无缝集成在一起

  • 主应用(基座)

    • 负责注册、加载、卸载子应用。
    • 提供公共布局、登录、全局样式、全局状态。
  • 子应用(微应用)

    • 一个完整的业务模块(如:商品、订单、用户中心)。
    • 暴露固定生命周期钩子,供主应用调用

实操:

一、主应用

1、main.js 注册子应用

js 复制代码
import { registerMicroApps, start } from 'qiankun';
const props = {
  getMainData: () => store.state.globalState.mainData,
  updateMainData:(child)=>{
    store.commit('SET_GLOBAL_STATE',child)
  }
}
registerMicroApps([
{
  name: 'vue app',
  entry: '//localhost:8000',
  container: '#childContainer',
  activeRule: '/vue',
  props
},
{
  name: 'react app', // app name registered
  entry: '//localhost:9000',
  container: '#childContainer',
  activeRule: '/react',
  props
}
]);
start({
sandbox: {
  strictStyleIsolation: true, // 严格样式隔离(推荐),主子应用样式隔离
  // experimentalStyleIsolation: true // 可选:追求兼容(弹窗、UI 库正常)
}
})

2、App.vue (任意组件,存放子应用容器)

js 复制代码
<template>
  <div id="app">
    <nav>
      <router-link to="/">Home</router-link> |
      <router-link to="/vue">跳转子应用vue</router-link> |
      <router-link to="/react">跳转子应用react</router-link> |
    </nav>
    <div>
      <h1>主应用data</h1>
      <h2 style="color:red">name:{{ name}}</h2>
    </div>
    <router-view/>
    <hr>
    <div id="childContainer"></div> // 存放子应用容器
  </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
  computed: {
    ...mapState(['globalState']),
    name(){
      return this.globalState?.mainData?.userInfo?.name||''
    }
  }
}
</script>
<style lang="scss">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

nav {
  padding: 30px;
}
.childContainer{
  display: flex;
  justify-content: center;
}
</style>

二、子应用-vue

1、main.js

js 复制代码
// 定义变量存储 Vue 实例
let instance = null

// 渲染函数
function render(props = {}) {
  const { container } = props

  instance = new Vue({
    router,
    store,
    render: h => h(App)
  // 乾坤会把容器传给你,避免挂载到主应用根节点(不污染主应用节点)
  }).$mount(container ? container.querySelector('#app') : '#app')
}

// 独立运行时(非微应用环境)直接渲染
if (!window.__POWERED_BY_QIANKUN__) {
  render()
}

export async function bootstrap() {
  // console.log('[vue] 微应用初始化')
}
export async function mount(props) { // props主应用传递的公共数据
  store.commit('SET_GLOBAL_STATE',{
    ...props,
    mainData:props?.getMainData()||{}
  })
  render(props)
}

export async function unmount() {
  console.log('[vue2] 微应用卸载')
  instance.$destroy() // 销毁实例
  instance.$el.innerHTML = '' // 清空 DOM
  instance = null
}

说明:为什么主应用传递给子应用时,子应用能拿到 container.querySelector('#app') ,主应用时如何能识别到的?

当子应用被主应用加载时,qiankun 会自动做这一步

  1. 去请求子应用的 index.html
  2. 解析子应用 HTML,隔离后挂载 到主应用容器(包括里面的 <div id="app"></div>
  3. 子应用的根节点 #app 渲染到主应用的 #childContainer 容器中

2、vue.config.js(子应用能被主应用识别加载)

js 复制代码
const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package.json')

module.exports = defineConfig({
  // 微应用唯一名称(主应用注册时要一致)
  configureWebpack: {
    output: {
      library: `${name}-[name]`, // 主应用上name呼应
      libraryTarget: 'umd', // 把微应用打包成 umd 格式,让子应用变成"可被主应用加载的格式"
      chunkLoadingGlobal: `webpackJsonp_${name}`,
    },
  },
  transpileDependencies: true,
  devServer: {
    port: 8000, // 自己定义微应用端口
    headers: {
      'Access-Control-Allow-Origin': '*', // 允许跨域(乾坤必须)
    },
  },
})

3、router/index.js

js 复制代码
const router = new VueRouter({
  mode: 'history',
  base: window.__POWERED_BY_QIANKUN__ ? '/vue' : process.env.BASE_URL, // vue 是主应用配置的 activeRule
  routes
})

说明:为什么vue不需要改publicPath?

  • Vue CLI 项目默认 publicPath: '/'

  • 被 qiankun 加载时,会自动修正子应用静态资源路径

  • 子应用部署到非根目录时必须改,不是永远不用改

三、子应用-react

1、index.js

js 复制代码
let instance = null

function render(props = {}) {
  const { container } = props
  const domContainer = container
    ? container.querySelector('#root')
    : document.getElementById('root')

  instance = ReactDOM.createRoot(domContainer)

  instance.render(
    <React.StrictMode>
      {/* 必须包 Provider */}
      <Provider store={store}>
        <RouterProvider router={router} />
      </Provider>
    </React.StrictMode>
  )
}
// 独立运行
if (!window.__POWERED_BY_QIANKUN__) {
  render()
}

export async function bootstrap() {
  console.log('[react] 微应用初始化')
}

export async function mount(props) {
  store.dispatch({
    type: "SET_GLOBAL_STATE",
    payload: {
      ...props,
      mainData: props?.getMainData?.() || {}
    }
  })
  render(props)
}

export async function unmount() {
  if (instance) {
    instance.unmount()
    instance = null
  }
}
reportWebVitals();

2、craco.config.js

react脚手架默认是这样的:

  • 所有 webpack、babel、eslint 配置全部藏在 node_modules 里
  • 你看不到、改不了
  • 你的项目很干净,只有 src、public

官方eject方法:

  • 不可逆:一旦执行,再也回不去

    • 把所有隐藏的配置文件,一次性全部复制到你的项目里
    • 这个命令会被删掉,再也不能执行第二次,也不能撤销!
  • 暴露几百个配置文件,你必须自己维护所有依赖和更新

    • 暴露几百个配置文件,必须自己维护依赖
  • 失去 CRA 后续升级能力

    • CRA 官方会不断更新,但eject就没有了(比如:优化打包速度、修复安全漏洞、升级 webpack、升级 babel、升级 eslint、加新特性等)

craco不用 eject,也能改 webpack 配置:

js 复制代码
const { name } = require('./package.json')

module.exports = {
  webpack: {
    configure: (config) => {
      config.output.library = `${name}-[name]`
      config.output.libraryTarget = 'umd'
      config.output.chunkLoadingGlobal = `webpackJsonp_${name}`
      config.output.publicPath = process.env.NODE_ENV === 'development'
    ? 'http://localhost:9000/' 
    : '/'; // 方便引入静态资源不会404
    return config
    }
  },
  devServer: (config) => {
    config.headers = {
      'Access-Control-Allow-Origin': '*'
    }
    return config
  }
}

3、router/index

js 复制代码
import { createBrowserRouter } from 'react-router-dom'
import App from "../App.js"

// 👇 核心:微应用必须加这个 base
const base = window.__POWERED_BY_QIANKUN__ ? '/react' : '/'

const router = createBrowserRouter([
  {
    path: '/',
    element:<App />
  }
], {
  basename: base  // 👈 这里注入 base
})

export default router

四、主、子通信

1、vuex+props

  • 将公共数据、更新公共数据方法存储到vuex

  • 通过注册应用registerMicroApps中props传递给子数据

    js 复制代码
    const props = {
      getMainData: () => store.state.globalState.mainData,
      updateMainData:(child)=>{
        store.commit('SET_GLOBAL_STATE',child)
      }
    }
     registerMicroApps([
        {
          name: 'vue app',
          entry: '//localhost:8000',
          container: '#childContainer',
          activeRule: '/vue',
          props
        }
      ]);
  • 子应用通过周期函数mount获取props再另行存储

2、initGlobalState、setGlobalState、onGlobalStateChange

  • initGlobalState(数据初始化)
  • setGlobalState(更新数据)
  • onGlobalStateChange(监听数据变化)
js 复制代码
// qiankun/index.js
import { initGlobalState } from 'qiankun';

const initialState = {
  userInfo: {},
  token:''
}

// 生成 actions
const actions = initGlobalState(initialState)

// 监听全局变化(可选)
actions.onGlobalStateChange((state) => {
  console.log('主应用全局状态变化:', state)
})
export { actions }    
js 复制代码
// main.js
import "./qiankun"
js 复制代码
// 组件内使用
import { actions } from '@/qiankun/index.js'

onChangeGlobal(){
  actions.setGlobalState({token:`token_update_----`})
}
js 复制代码
// 子应用中使用
// 子应用通过props接收,方法都在props上可以直接调用
props.setGlobalState({token:'00000000000000000000'})

五、子、子通信

需要主应用做中转

  • initGlobalState主应用
  • 子应用A:setGlobalState
  • 子应用B:onGlobalStateChange监听获取

总结:

主应用、子应用相连:

1、主应用做什么

  • 注册子应用(registerMicroApps

  • 启动 qiankun(start

  • 提供子应用挂载容器(<div id="container"></div>

  • 通过 activeRule 路由规则匹配子应用

2、子应用做什么

  • 子应用在主应用提供的容器内进行渲染

  • 导出生命周期函数bootstrap/mount/unmount

  • 配置 webpack 打包为 umd 格式(让主应用能识别)

    • library
    • libraryTarget: 'umd'
    • chunkLoadingGlobal
  • 配置跨域devServer.headers

  • 配置路由 base (与主应用 activeRule 对应)

  • 配置 publicPath(防止静态资源 404)

    • React 必须配
    • Vue 可配可不配(建议配)
相关推荐
Wect2 小时前
深度解析浏览器本地存储:原理、方案与实战指南
前端·面试·浏览器
前端那点事2 小时前
Vue自定义指令全解析(Vue2+Vue3适配)| 底层DOM操作必备
前端
|晴 天|2 小时前
实现草稿自动保存功能:5秒无操作自动保存
前端·vue.js·typescript
Cisyam^2 小时前
Bright Data Web Scraping 指南:用 MCP + Dify 自动采集 TikTok 与 LinkedIn数据
大数据·前端·人工智能
XGeFei3 小时前
【表单处理】——如何防止CSRF(跨站请求伪造)攻击的?
前端·网络·csrf
还不秃顶的计科生3 小时前
多模态模型下载
java·linux·前端
GISer_Jing3 小时前
笑不活了!蒸馏Skill竟能复刻前任、挽留同事?三大热门项目+完整地址汇总
前端·人工智能
Bigger4 小时前
🚀 mini-cc:打造你的专属轻量级 AI 编程智能体
前端·node.js·claude
小江的记录本4 小时前
【网络安全】《网络安全三大加密算法结构化知识体系》
java·前端·后端·python·安全·spring·web安全