微前端 qiankun 实践与总结

373 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

背景

一、是什么

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

  • 白话:将一个庞大的前端应用拆分成为多个独立的小型应用,每个小型应用可以进行独立开发、运行和部署,最后将其合并为一个完整的应用。既减少了耦合性,又提高了拓展性

二、特性

  • 技术栈无关:主应用不限制接入应用的技术栈,微应用可根据项目需要自主选择技术栈
  • 独立开发/部署:各个团队之间的仓库独立,单独部署,互不依赖
  • 增量升级:当一个应用庞大之后,技术升级或重构相当麻烦,而微应用具备渐进式升级的特性
  • 独立运行时:微应用之间运行时互不依赖,有独立的状态管理

实践

一、主应用

  • 这里选取的是Vue3作为主应用
  • 关键步骤为安装和在主应用中注册微应用

1、创建 vue-cli 脚手架,生成主应用程序

vue create main

2、安装 qiankun,在主应用程序安装即可

$ yarn add qiankun # 或者 npm i qiankun -S

3、在 main.js 文件中,在主应用程序中注册微应用并启动,以及相关注册规则

  • name:微应用名称(必选)
  • entry:微应用的入口(必选)
  • container:微应用的容器节点的选择器(必选)
  • activeRule:微应用的激活规则(必选)
  • msg:主应用需要传递给微应用的数据(可选)
import { registerMicroApps, start } from 'qiankun'

registerMicroApps([
  {
    name: 'vueApp',
    entry: '//localhost:3000',
    container: '#container1',
    activeRule: '/vue',
    props: {
      msg: '主应用传递Vue微应用',
    },
  },
  {
    name: 'reactApp',
    entry: '//localhost:2000',
    container: '#container2',
    activeRule: '/react',
    props: {
      msg: '主应用传递React微应用',
    },
  },
])
// 启动 qiankun
start()

4、在router文件夹中,修改对应路由(这里是演示,可按项目需求更改)

import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'index',
    component: () => import('@/views/main'),
  },
  {
    path: '/vue',
    name: 'vue',
    component: () => import('@/views/vue'),
  },
  {
    path: '/react',
    name: 'react',
    component: () => import('@/views/react'),
  },
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
})

export default router

二、微应用

  • 这里分别选择Vue3和React作为微应用
  • 关键步骤为导出相应生命周期钩子和配置微应用的打包工具

Vue微应用

1、创建 vue-cli 脚手架,生成微小应用程序

vue create child1

2、在 src 目录新增 public-path.js

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

3、在 main.js 文件中,添加相应生命周期钩子

  • 注意:为避免与主应用或微应用根id的重名,请务必对querySelector('#vue')中的id作出相应修改
import './public-path'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

import actions from './actions'

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

const render = (props = {}) => {
  const { container } = props

  const app = createApp(App)

  app.use(router).mount(container ? container.querySelector('#vue') : '#vue') // 微应用的根id
}

if (!window.__POWERED_BY_QIANKUN__) {
  render()
}

export async function bootstrap() {
  console.log('[vue3] vue app bootstraped')
}

export async function mount(props) {
  console.log('[vue3] props from main framework', props)
  actions.setActions(props)
  render(props)
}

export async function unmount(props) {
  console.log('vue3 app unmount', props)
  if (window._POINT_STORE_APP_INSTANCE) {
    window._POINT_STORE_APP_INSTANCE.unmount()
  }
}

4、在 src 目录新增 action.js

function emptyAction() {
  //设置一个actions实例
  // 提示当前使用的是空 Action
  console.warn('Current execute action is empty!')
}

class Actions {
  // 默认值为空 Action
  actions = {
    onGlobalStateChange: emptyAction,
    setGlobalState: emptyAction,
  }

  /**
   * 设置 actions
   */
  setActions(actions) {
    this.actions = actions
  }

  /**
   * 映射
   */
  onGlobalStateChange(...args) {
    return this.actions.onGlobalStateChange(...args)
  }

  /**
   * 映射
   */
  setGlobalState(...args) {
    return this.actions.setGlobalState(...args)
  }
}

const actions = new Actions()
export default actions

5、打包配置修改 vue.config.js

const { name } = require('./package');
module.exports = {
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  configureWebpack: {
    output: {
      library: `${name}-[name]`,
      libraryTarget: 'umd', // 把微应用打包成 umd 库格式
      jsonpFunction: `webpackJsonp_${name}`, // 如报错将jsonpFunction修改为chunkLoadingGlobal
    },
  },
};

React微应用

1、创建 react-cli 脚手架

create-react-app child2

2、在 src 目录新增 public-path.js,同上

3、在 main.js 文件中,同上

import './public-path'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'

import actions from './actions'

// ReactDOM.render(
//   <React.StrictMode>
//     <App />
//   </React.StrictMode>,
//   document.getElementById('root')
// );

function render(props) {
  const { container } = props

  ReactDOM.render(
    <App />,
    container
      ? container.querySelector('#react')
      : document.querySelector('#react') // 微应用的根id
  )
}

if (!window.__POWERED_BY_QIANKUN__) {
  render({})
}

export async function bootstrap() {
  console.log('[react16] react app bootstraped')
}

export async function mount(props) {
  console.log('[react16] props from main framework', props)
  actions.setActions(props)
  render(props)
}

export async function unmount(props) {
  console.log('react16 app unmount', props)
  const { container } = props
  ReactDOM.unmountComponentAtNode(
    container
      ? container.querySelector('#react')
      : document.querySelector('#react')
  )
}

4、在 src 目录新增 action.js,同上

5、安装插件 @rescripts/cli,根目录新增 .rescriptsrc.js,修改 package.json

npm i -D @rescripts/cli
const { name } = require('./package')

module.exports = {
  devServer: (config) => {
    config.headers = {
      'Access-Control-Allow-Origin': '*',
    }
    return config
  },

  webpack: (config) => {
    config.output.library = `${name}-[name]`
    config.output.libraryTarget = 'umd'
    config.output.chunkLoadingGlobal = `webpackJsonp_${name}`
    config.output.globalObject = 'window'
    return config
  },
}

  "scripts": {
    "start": "rescripts start",
    "build": "rescripts build",
    "test": "rescripts test",
  },

三、效果演示

动画.gif

四、补充

1、若遇到微应用向主应用发起通信的场景需求,可根据以下方法实现

import { initGlobalState } from "qiankun";

const actions = initGlobalState();

actions.setGlobalState('微应用向主应用传递信息');

总结

  • 上述实践中所基于路由配置,也可根据实际需要采用手动加载的方式(具体可上qiankun官网查询)

  • 通过qiankun使我们能够通过简单的API去完成庞大应用的改造,使微应用的接入像iframe一样简单

  • 好了,本篇的所有内容就介绍到这里了,希望大家能够在未来的日子里继续加油!