admin 管理员组

文章数量: 887032

micro

关于micro-app

micro-app之前,业内已经有一些开源的微前端框架,比较流行的有2个:single-spaqiankun

single-spa是通过监听url change事件,在路由变化时匹配到渲染的子应用并进行渲染,这个思路也是目前实现微前端的主流方式。同时single-spa要求子应用修改渲染逻辑并暴露出三个方法:bootstrap、mount、unmount,分别对应初始化、渲染和卸载,这也导致子应用需要对入口文件进行修改。因为qiankun是基于single-spa进行封装,所以这些特点也被qiankun继承下来,并且需要对webpack配置进行一些修改。

micro-app并没有沿袭single-spa的思路,而是借鉴了WebComponent的思想,通过CustomElement结合自定义的ShadowDom,将微前端封装成一个类WebComponent组件,从而实现微前端的组件化渲染。并且由于自定义ShadowDom的隔离特性,micro-app不需要像single-spaqiankun一样要求子应用修改渲染逻辑并暴露出方法,也不需要修改webpack配置,是目前市面上接入微前端成本最低的方案。

概念图

micro-app的优势

1、使用简单

我们将所有功能都封装到一个类WebComponent组件中,从而实现在基座应用中嵌入一行代码即可渲染一个微前端应用。

同时micro-app还提供了js沙箱样式隔离元素隔离预加载数据通信静态资源补全等一系列完善的功能。

2、零依赖

micro-app没有任何依赖,这赋予它小巧的体积和更高的扩展性。

3、兼容所有框架

为了保证各个业务之间独立开发、独立部署的能力,micro-app做了诸多兼容,在任何技术框架中都可以正常运行。

React搭建

本篇以React 16、17作为案例介绍react的接入方式,其它版本react的接入方式以此类推。我们默认开发者掌握了各版本react的开发技巧,如示例中useEffect,在不支持hooks的版本中转换为componentDidMount。

基座应用

我们强烈建议基座应用采用history模式,hash路由的基座应用只能加载hash路由的子应用,history模式的基座应用对这两种子应用都支持。

在以下案例中,我们默认基座的路由为history模式

1、安装依赖

npm i @micro-zoe/micro-app --save

2、在入口处引入

// entry
import microApp from '@micro-zoe/micro-app'microApp.start()

3、分配一个路由给子应用

// router.js
import { BrowserRouter, Switch, Route } from 'react-router-dom'
import MyPage from './my-page'export default function AppRoute () {return (<BrowserRouter><Switch>// 👇 非严格匹配,/my-page/* 都指向 MyPage 页面<Route path='/my-page'><MyPage /></Route></Switch></BrowserRouter>)
}

4、在页面中嵌入子应用

export function MyPage () {return (<div><h1>子应用</h1><micro-appname='app1' // name(必传):应用名称url='http://localhost:3000/' // url(必传):应用地址,会被自动补全为http://localhost:3000/index.htmlbaseroute='/my-page' // baseroute(可选):基座应用分配给子应用的基础路由,就是上面的 `/my-page`></micro-app></div>)
}

子应用

1、设置跨域支持

使用create-react-app脚手架创建的项目,在 config/webpackDevServer.config.js 文件中添加headers

其它项目在webpack-dev-server中添加headers

headers: {'Access-Control-Allow-Origin': '*',
}

2、设置基础路由(如果基座是history路由,子应用是hash路由,这一步可以省略)

// router.js
import { BrowserRouter, Switch, Route } from 'react-router-dom'export default function AppRoute () {return (// 👇 设置基础路由,如果没有设置baseroute属性,则window.__MICRO_APP_BASE_ROUTE__为空字符串<BrowserRouter basename={window.__MICRO_APP_BASE_ROUTE__ || '/'}>...</BrowserRouter>)
}

3、设置 publicPath

这一步借助了webpack的功能,避免子应用的静态资源使用相对地址时加载失败的情况,详情参考webpack文档 publicPath

如果子应用不是webpack构建的,这一步可以省略。

步骤1: 在子应用src目录下创建名称为public-path.js的文件,并添加如下内容

// __MICRO_APP_ENVIRONMENT__和__MICRO_APP_PUBLIC_PATH__是由micro-app注入的全局变量
if (window.__MICRO_APP_ENVIRONMENT__) {// eslint-disable-next-line__webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__
}

步骤2: 在子应用入口文件的最顶部引入public-path.js

// entry
import './public-path'

4、监听卸载

子应用被卸载时会接受到一个名为unmount的事件,在此可以进行卸载相关操作。

window.addEventListener('unmount', function () {ReactDOM.unmountComponentAtNode(document.getElementById('root'))
})

实战案例

以上介绍了react如何接入微前端,但在实际使用中会涉及更多功能,如数据通信、路由跳转、打包部署,为此我们提供了一套案例,用于展示react作为基座嵌入(或作为子应用被嵌入) react、vue、angular、vite、nextjs、nuxtjs等框架,在案例中我们使用尽可能少的代码实现尽可能多的功能。

案例地址:

常见问题

1、create-react-app创建的子应用,被嵌入微前端后sockjs-node报错

报错信息: WebSocket connection to 'ws://localhost:3000/sockjs-node' failed

原因: 子应用的sockjs-node会根据当前页面的端口号进行通信,嵌入微前端后,端口号为基座的,而非子应用的,导致报错。 虽然这个问题不影响应用的正常运行,但还是要进行处理。

解决方式: 使用插件系统补全子应用sockjs-node的端口号。

microApp.start({plugins: {modules: {子应用名称: [{loader(code) {if (process.env.NODE_ENV === 'development' && code.indexOf('sockjs-node') > -1) {code = code.replace('window.location.port', 子应用端口)}return code}}],}}
})

Vue搭建

本篇以Vue 2、3作为案例介绍vue的接入方式,其它版本vue的接入方式以此类推,我们默认开发者掌握了各版本vue的开发技巧,比如示例中vue2的代码如何转换为vue1。

基座应用

我们强烈建议基座应用采用history模式,hash路由的基座应用只能加载hash路由的子应用,history模式的基座应用对这两种子应用都支持。

在以下案例中,我们默认基座的路由为history模式

1、安装依赖

npm i @micro-zoe/micro-app --save

2、在入口处引入

// entry
import microApp from '@micro-zoe/micro-app'microApp.start()

3、分配一个路由给子应用

Vue2版本

// router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import MyPage from './my-page.vue'Vue.use(VueRouter)const routes = [{// 👇 非严格匹配,/my-page/* 都指向 MyPage 页面path: '/my-page/*',name: 'my-page',component: MyPage,},
]export default routes

Vue3版本

// router.js
import { createRouter, createWebHistory } from 'vue-router'
import MyPage from './my-page.vue'const routes = [{// 👇 非严格匹配,/my-page/* 都指向 MyPage 页面path: '/my-page/:page*',name: 'my-page',component: MyPage,},
]const router = createRouter({history: createWebHistory(process.env.BASE_URL),routes
})export default router

4、在页面中嵌入子应用

<!-- my-page.vue -->
<template><div><h1>子应用</h1><!-- name(必传):应用名称url(必传):应用地址,会被自动补全为http://localhost:3000/index.htmlbaseroute(可选):基座应用分配给子应用的基础路由,就是上面的 `/my-page`--><micro-app name='app1' url='http://localhost:3000/' baseroute='/my-page'></micro-app></div>
</template>

子应用

1、设置跨域支持

vue.config.js中添加配置

devServer: {headers: {'Access-Control-Allow-Origin': '*',}
}

2、设置基础路由(如果基座是history路由,子应用是hash路由,这一步可以省略)

Vue2版本

// main.js
import VueRouter from 'vue-router'
import routes from './router'const router = new VueRouter({mode: 'history',// 👇 __MICRO_APP_BASE_ROUTE__ 为micro-app传入的基础路由base: window.__MICRO_APP_BASE_ROUTE__ || process.env.BASE_URL,routes,
})

Vue3版本

// main.js
import { createRouter, createWebHistory } from 'vue-router'
import routes from './router'const router = createRouter({// 👇 __MICRO_APP_BASE_ROUTE__ 为micro-app传入的基础路由history: createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || process.env.BASE_URL),routes,
})

3、设置 publicPath

这一步借助了webpack的功能,避免子应用的静态资源使用相对地址时加载失败的情况,详情参考webpack文档 publicPath

如果子应用不是webpack构建的,这一步可以省略。

步骤1: 在子应用src目录下创建名称为public-path.js的文件,并添加如下内容

// __MICRO_APP_ENVIRONMENT__和__MICRO_APP_PUBLIC_PATH__是由micro-app注入的全局变量
if (window.__MICRO_APP_ENVIRONMENT__) {// eslint-disable-next-line__webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__
}

步骤2: 在子应用入口文件的最顶部引入public-path.js

// entry
import './public-path'

4、监听卸载

子应用被卸载时会接受到一个名为unmount的事件,在此可以进行卸载相关操作。

Vue2版本

// main.js
const app = new Vue(...)// 监听卸载操作
window.addEventListener('unmount', function () {app.$destroy()
})

Vue3版本

// main.js
const app = createApp(App)
app.mount('#app')// 监听卸载操作
window.addEventListener('unmount', function () {app.unmount()
})

实战案例

以上介绍了vue如何接入微前端,但在实际使用中会涉及更多功能,如数据通信、路由跳转、打包部署,为此我们提供了一套案例,用于展示vue作为基座嵌入(或作为子应用被嵌入) react、vue、angular、vite、nextjs、nuxtjs等框架,在案例中我们使用尽可能少的代码实现尽可能多的功能。

案例地址:

常见问题

1、基座应用中抛出警告,micro-app未定义

报错信息:

vue2: [Vue warn]: Unknown custom element: <micro-app>
vue3: [Vue warn]: Failed to resolve component: micro-app

参考issue: vue-next@1414

解决方式: 在基座应用中添加如下配置

Vue2版本

在入口文件main.js中设置ignoredElements,详情查看:

// main.js
import Vue from 'vue'Vue.config.ignoredElements = ['micro-app',
]

Vue3版本

在vue.config.js中添加chainWebpack配置,如下:

// vue.config.js
module.exports = {chainWebpack: config => {config.module.rule('vue').use('vue-loader').tap(options => {options.compilerOptions = {...(options.compilerOptions || {}),isCustomElement: (tag) => /^micro-app/.test(tag),};return options})}
}

Vite + Vue3版本

在vite.config.js中通过vue插件设置isCustomElement,如下:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'export default defineConfig({plugins: [vue({template: {compilerOptions: {isCustomElement: tag => /^micro-app/.test(tag)}}})],
})

2、当基座和子应用都是vue-router4,点击浏览器返回按钮页面丢失

原因: vue-router4没有对路由堆栈state做唯一性标记,导致基座和子应用相互影响,vue-router3及其它框架路由没有类似问题。

测试版本: vue-router@4.0.12

相关issue: 155

解决方式: 在子应用中添加如下设置

if (window.__MICRO_APP_ENVIRONMENT__) {// 如果__MICRO_APP_BASE_ROUTE__为 `/基座应用基础路由/子应用基础路由/`,则应去掉`/基座应用基础路由`// 如果对这句话不理解,可以参考案例: realBaseRoute = window.__MICRO_APP_BASE_ROUTE__router.beforeEach(() => {if (typeof window.history.state?.current === 'string') {window.history.state.current = window.history.state.current.replace(new RegExp(realBaseRoute, 'g'), '')}})router.afterEach(() => {if (typeof window.history.state === 'object') {window.history.state.current = realBaseRoute +  (window.history.state.current || '')}})
}

3、vue-router在hash模式无法通过base设置基础路由

**解决方式:**创建一个空的路由页面,将其它路由作为它的children,具体设置如下:

import RootApp from './root-app.vue'const routes = [{path: window.__MICRO_APP_BASE_ROUTE__ || '/',component: RootApp,children: [// 其他的路由都写到这里],},
]

root-app.vue内容如下:

<template><router-view />
</template>

本文标签: micro