一起养成写作习惯!这是我参与「掘金日新计划 · 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",
},
三、效果演示
四、补充
1、若遇到微应用向主应用发起通信的场景需求,可根据以下方法实现
import { initGlobalState } from "qiankun";
const actions = initGlobalState();
actions.setGlobalState('微应用向主应用传递信息');
总结
-
上述实践中所基于路由配置,也可根据实际需要采用手动加载的方式(具体可上qiankun官网查询)
-
通过qiankun使我们能够通过简单的API去完成庞大应用的改造,使微应用的接入像iframe一样简单
-
好了,本篇的所有内容就介绍到这里了,希望大家能够在未来的日子里继续加油!