新星微前端MicroApp的基础教程

时间:2024-10-09 07:48:31

目录

什么是微前端?

使用场景

microApp介绍

概念图

micorApp的优势

microApp项目的应用

基座

基座路由

子应用

react项目中路由位置进行使用

跨域的问题

react项目中跨域

vue项目中跨域

micorApp基础介绍

micorApp传值(重要)

基座—>子应用发送数据

子应用获取—>基座数据

子应用—>基座发送数据

基座获取—>子应用数据

全局数据通信

  接收数据

JS沙箱

注意:

解决方式:

样式隔离

所有应用中禁用样式

在某个应用里禁止

在某个文件里禁用

在某一行禁用

元素隔离

预加载

使用方式

插件系统

官方定义

个人理解

适用场景

使用方式

例子

子午线埋点插件


什么是微前端?

微前端是一种类似于微服务的架构,是一种由独立交付的多个前端应用组成整体的架构风格,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的应用,而在用户看来仍然是内聚的单个产品。

简单来说:就是一个项目里可以内嵌多个子项目,子项目可以单独开发、部署并且技术框架无关。

使用场景

  • 大规模企业级 Web 应用开发;

  • 跨团队及企业级应用协作开发;

  • 俗称屎山的项目,就是用的技术比较老然后还堆叠了很多业务要重构需要花很大成本;

microApp介绍

看官方文档发现上来microApp就表明来意,`microApp`并没有延续single-spa的思路

micro-app借鉴了WebComponent的思想,通过CustomElement结合自定义的ShadowDom,将微前端封装成一个类WebComponent组件,从而实现微前端的组件化渲染。并且由于自定义ShadowDom的隔离特性,是目前市面上接入微前端成本最低的方案。

概念图

它在 基座应用子应用 之间充当桥梁胶水的作用。

micorApp的优势

  • 使用简单。 将功能封装到 WebComponent 中

  • 零依赖。 无依赖、更高的扩展性

  • 兼容所有框架 技术栈无关

microApp项目的应用

基座

通过micro-app标签可以渲染出一个子应用,它又三个参数

  • name:名称(必填)

  • url:子应用页面地址(必填)

  • baseurl:baseurl是基座应用分配给子应用的路由前缀(可选)

    注意:

    • 1、如果基座是history路由,子应用是hash路由,不需要设置基础路由baseroute

    • 2、如果子应用只有一个页面,没有使用react-routervue-router之类,也不需要设置基础路由baseroute

  1. <div>
  2. <h1>React项目</h1>
  3. {/* 端口号通过.env进行设置 */}
  4. {/* name必填、url必填、baseroute选填 */}
  5. <micro-app
  6. name="app1"
  7. url="http://localhost:3001/"
  8. baseroute="/app1"
  9. ></micro-app>
  10. </div>

基座路由

  • 1、基座是hash路由,子应用也必须是hash路由

  • 2、基座是history路由,子应用可以是hash或history路由

    注意:官方推荐使用history的路由

  1. <Routes>
  2.       {/*'/'的时候我就渲染AppLayout组件 */}
  3.       <Route
  4.         path="/*"
  5.         element={
  6.           <AppLayout>
  7.             {/* 如果AppLayout组件的路径事/app1/的话就加载AppOne */}
  8.             <Routes>
  9.               <Route path="/app1/*" element={<AppOne />} />
  10.               <Route path="/app2/*" element={<AppTwo />} />
  11.             </Routes>
  12.           </AppLayout>
  13.         }
  14.       />
  15. </Routes>

子应用

通常基座应用和子应用各有一套路由系统,为了防止冲突,基座需要分配一个路由给子应用,称之为基础路由,子应用可以在这个路由下渲染,但不能超出这个路由的范围,这就是基础路由的作用。

如下: window.MICRO_APP_BASE_URL是由基座应用下发的路由前缀,在非微前端环境下,这个值为undefined

使用:

react项目中路由位置进行使用

  1. (
  2. <>
  3. <BrowserRouter basename={window.__MICRO_APP_BASE_ROUTE__ || "/"}>
  4. <App />
  5. </BrowserRouter>
  6. </>
  7. );
  8. vue项目中路由位置进行使用
  9. const router = createRouter({
  10. routes,
  11. history: createWebHistory(
  12. window.__MICRO_APP_BASE_ROUTE__ || .BASE_URL
  13. ),
  14. });
  15. export default router;
  16. 或者
  17. const router = new VueRouter({
  18. mode: 'history',
  19. // __MICRO_APP_BASE_ROUTE__ 为micro-app传入的基础路由
  20. base: window.__MICRO_APP_BASE_ROUTE__ || .BASE_URL,
  21. routes,
  22. })

跨域的问题

react项目中跨域

  1. = {
  2.   devServer: (_) => {
  3.       const config = _;
  4.       = {
  5.           "Access-Control-Allow-Origin": "*",
  6.       };
  7.       return config;
  8.   },
  9. };

vue项目中跨域

  1. module.exports = {
  2.   devServer: {
  3.       port: 3002,
  4.       headers: {
  5.           "Access-Control-Allow-Origin": "*",
  6.       },
  7.   },
  8. };

micorApp基础介绍

micorApp传值(重要)

每个应用的路由实例都是不同的,应用的路由实例只能控制自身,无法影响其它应用,包括基座应用无法通过控制自身路由影响到子应。

常见的问题如:开发者想通过基座应用的侧边栏跳转,从而控制子应用的页面,这其实是做不到的,只有子应用的路由实例可以控制自身的页面

基座—>子应用发送数据

  1. 方式1: 通过data属性发送数据
  2. <micro-app
  3. name='my-app'
  4. url='xx'
  5. data={data} // data只接受对象类型,采用严格对比(===),当传入新的data对象时会重新发送
  6. />
  7. 方式2: 手动发送数据
  8. ('my-app', {type: '新的数据'})

子应用获取—>基座数据

  1. 方式2:监听基座传来的数据
  2. 监听数据
  3. ?.addDataListener(dataListener:function类型)
  4. // 解绑指定函数
  5. ?.removeDataListener(dataListener)
  6. // 清空当前子应用的所有绑定函数(全局数据函数除外)
  7. ?.clearDataListener()
  8. 方式2:主动获取数据
  9. ?.getData() // 返回data数据
  10. 应用
  11. if (window.__MICRO_APP_BASE_ROUTE__) {
  12.   // @ts-ignore
  13.   ((data) => {
  14.     // 当基座下发跳转指令时进行跳转
  15.     if (data.path) {
  16.       navigate(data.path);
  17.     }
  18.   });
  19. }

子应用—>基座发送数据

?.dispatch({type: '子应用发送的数据'})

基座获取—>子应用数据

  1. 方式1: 监听自定义事件
  2. <micro-app
  3. name='my-app'
  4. url='xx'
  5. data={data}
  6. onDataChange={(e) => (e.detail.data)}
  7. />
  8. 方式2: 手动绑定监听函数
  9. (appName: string, dataListener: Function)
  10. // 解绑监听my-app子应用的函数
  11. (appName: string, dataListener: Function)
  12. // 清空所有监听appName子应用的函数
  13. (appName: string)
  14. 方式3:主动获取数据
  15. (appName) // 返回子应用发送的data数据

数据在字段中,子应用每次发送数据都会重新触发事件  onDataChange函数在子应用卸载时会自动解绑,不需要手动处理

全局数据通信

  1. 发送数据
  2. 基座
  3. microApp.setGlobalData({type: '全局数据'})
  4. 子应用
  5. ?.setGlobalData({type: '全局数据'})

  接收数据

  1. (dataListener: Function, autoTrigger?: boolean)
  2. // 解绑指定函数
  3. (dataListener)
  4. // 清空基座应用绑定的全局数据函数
  5. ()

JS沙箱

使用Proxy拦截了用户全局操作的行为,防止对window的访问和修改,避免全局变量污染。micro-app中的每个子应用都运行在沙箱环境,以获取相对纯净的运行空间。

官方建议:沙箱是默认开启的,正常情况下不建议关闭,以避免出现不可预知的问题。

注意:

子应用在沙箱环境下如何获取真实的window

  1. 1、new Function("return window")() 或 Function("return window")()
  2. 2、(0, eval)('window')
  3. 3

子应用抛出错误:xxx未定义

  1. xxx is not defined
  2. nxxx is not a function
  3. Cannot read properties of undefined

这种原因事在js中var name或者function事*变量并会泄露为全局变量,可以通过或者name方式去访问;但是在微前端中*变量不会泄露为全局变量,所以会报错;

解决方式:

将 var name 或 function name () {} 修改为 = xx或者使用插件系统的方式的方式

样式隔离

MicroApp的样式隔离是默认开启的,开启后会以<micro-app>标签作为样式作用域,利用标签的name属性为每个样式添加前缀,将子应用的样式影响禁锢在当前标签区域。

  1. .test {
  2. color: red;
  3. }
  4. /* 转换为 */
  5. micro-app[name=xxx] .test {
  6. color: red;
  7. }

官方建议:但基座应用的样式依然会对子应用产生影响,如果发生样式污染,推荐通过约定前缀或CSS Modules方式解决。

所有应用中禁用样式

这主要通过start方法进行全局配置,设置后所有应用的样式隔离都会停止。

  1. import microApp from '@micro-zoe/micro-app'
  2. microApp.start({
  3. disableScopecss: true, // 默认值false
  4. })

如果希望在某个子应用中不受全局配置控制,可以设置disableScopecss='false'

<micro-app name='xx' url='xx' disableScopecss='false'></micro-app>

在某个应用里禁止

设置后,当前应用的所有css都不会进行样式隔离。

<micro-app name='xx' url='xx' disableScopecss 或 disable-scopecss></micro-app>

在某个文件里禁用

可以在你的css文件中使用以下格式的注释来禁用样式隔离:

  1. /* ! scopecss-disable */
  2. .test1 {
  3. color: red;
  4. }
  5. /* ! scopecss-enable */

如果想在整个文件范围内禁用样式隔离,将 /* ! scopecss-disable */ 注释放在文件顶部:

  1. /* ! scopecss-disable */
  2. ...

在某一行禁用

在文件中使用以下格式的注释在某一特定的行上禁用样式隔离:

  1. /* ! scopecss-disable-next-line */
  2. .test1 {
  3. color: red;
  4. }
  5. .test2 {
  6. /* ! scopecss-disable-next-line */
  7. background: url(/test.png);
  8. }

元素隔离

大栗子:基座应用和子应用都有一个元素`<div id='root'></div>`,此时子应用通过`('#root')`获取到的是自己内部的`#root`元素,而不是基座应用的。

元素隔离的概念来自ShadowDom,即ShadowDom中的元素可以和外部的元素重复但不会冲突,micro-app模拟实现了类似ShadowDom的功能,元素不会逃离<micro-app>元素边界,子应用只能对自身的元素进行增、删、改、查的操作。

预加载

预加载是指在应用尚未渲染时提前加载资源并缓存,从而提升首屏渲染速度。

预加载并不是同步执行的,它会在浏览器空闲时间,依照开发者传入的顺序,依次加载每个应用的静态资源,以确保不会影响基座应用的性能。

(Array<app>|Function=>Array<app>

preFetch接受app数组或一个返回app数组的函数,app的值如下:

  1. app: {
  2. name: string, // 应用名称,必传
  3. url: string, // 应用地址,必传
  4. disableScopecss?: boolean // 是否关闭样式隔离,非必传
  5. disableSandbox?: boolean // 是否关闭沙盒,非必传
  6. }

使用方式

  1. import microApp from '@micro-zoe/micro-app'
  2. // 方式一
  3. ([
  4. { name: 'my-app', url: 'xxx' }
  5. ])
  6. // 方式二
  7. (() => [
  8. { name: 'my-app', url: 'xxx' }
  9. ])
  10. // 方式三
  11. microApp.start({
  12.  preFetchApps: [
  13.   { name: 'my-app', url: 'xxx' }
  14. ],
  15. })

插件系统

官方定义

微前端的使用场景非常复杂,没有完美的沙箱方案,所以我们提供了一套插件系统,它赋予开发者灵活处理静态资源的能力,对有问题的资源文件进行修改。插件系统的主要作用就是对js进行修改,每一个js文件都会经过插件系统,我们可以对这些js进行拦截和处理,它通常用于修复js中的错误或向子应用注入一些全局变量。

个人理解

由于沙箱系统的并不是特别完美,而且还必须需要,所以出现了一套插件系统!主要的作用就是操作js,每个js文件都会经过js进行处理和拦截,可以用来修复js的错误!

适用场景

通常我们无法控制js的表现,比如在沙箱中:

顶层的变量是无法泄漏为全局变量的(如 var xx = , function xxx 定义变量,无法通过 访问),导致js报错,此时开发者可以通过插件对js进行修改处理。

使用方式

  1. import microApp from '@micro-zoe/micro-app'
  2. //基座
  3. microApp.start({
  4. plugins: {
  5.   // 全局插件,作用于所有子应用的js文件
  6.   global?: Array<{
  7.     // 可选,强隔离的全局变量(默认情况下子应用无法找到的全局变量会兜底到基座应用中,scopeProperties可以禁止这种情况)
  8.     scopeProperties?: string[],
  9.     // 可选,可以逃逸到外部的全局变量(escapeProperties中的变量会同时赋值到子应用和外部真实的window上)
  10.     escapeProperties?: string[],
  11.     // 可选,传递给loader的配置项
  12.     options?: any,
  13.     // 必填,js处理函数,必须返回code
  14.     loader?: (code: string, url: string, options: any) => code
  15.   }>
  16.  
  17.   // 子应用插件
  18.   modules?: {
  19.     // appName为应用的名称,这些插件只会作用于指定的应用
  20.     [appName: string]: Array<{
  21.       // 可选,强隔离的全局变量(默认情况下子应用无法找到的全局变量会兜底到基座应用中,scopeProperties可以禁止这种情况)
  22.       scopeProperties?: string[],
  23.       // 可选,可以逃逸到外部的全局变量(escapeProperties中的变量会同时赋值到子应用和外部真实的window上)
  24.       escapeProperties?: string[],
  25.       // 可选,传递给loader的配置项
  26.       options?: any,
  27.       // 必填,js处理函数,必须返回code
  28.       loader?: (code: string, url: string, options: any) => code
  29.     }>
  30.   }
  31. }
  32. })

例子

操作顶层的变量是无法泄漏为全局变量的eg:var name ===>

  1. import microApp from '@micro-zoe/micro-app'
  2. microApp.start({
  3. plugins: {
  4.   modules: {
  5.     'appName1': [{
  6.       loader(code, url, options) {
  7.         if (url === '') {
  8.           code = code.replace('var abc =', ' =')
  9.         }
  10.         return code
  11.       }
  12.     }],
  13.     'appName2': [{
  14.       scopeProperties: ['key', 'key', ...], // 可选
  15.       escapeProperties: ['key', 'key', ...], // 可选
  16.       options: 配置项, // 可选
  17.       loader(code, url, options) { // 必填
  18.         ('只适用于appName2的插件')
  19.         return code
  20.       }
  21.     }]
  22.   }
  23. }
  24. })

子午线埋点插件

子午线埋点文件中使用function定义将函数泄漏为全局变量,这在沙箱中是不允许的,所以我们需要将其修改为 = funnction xx 的形式进行适配。

  1. # 安装子午线埋点插件
  2. npm i @micro-zoe/plugin-painful-joya -S
  1. import microApp from '@micro-zoe/micro-app'
  2. import painfulJoya from '@micro-zoe/plugin-painful-joya'
  3. // 设置为全局插件,作用于所有子应用
  4. microApp.start({
  5. plugins: {
  6. global: [painfulJoya],
  7. }
  8. })
  9. // 或者设置为某个子应用的插件,只作用于当前子应用
  10. microApp.start({
  11. plugins: {
  12. modules: {
  13. 'appName': [painfulJoya],
  14. }
  15. }
  16. })