一、项目背景
商羚项目B端前端有13个业务模块,项目周期紧。为保证项目进度和质量,各模块作为单独SPA应用并行研发。需要一套能快速集成各模块的技术方案。
二、什么是微前端
微前端并不是单单技术上的创新,它在团队协同、用户体验上都给我们带来非常大的提升,它借鉴了后端微服务的概念,在技术架构上它能够做到各个系统模块的拆分,既能让模块之间低耦合,又能让所有的模块聚合到同一个页面上运行。那微前端能给我们带来什么呢?
一致的开发体验: 开发者在微前端下开发子应用,与平时开发其它 Web 应用没有太大区别,无需因为是微前端而做过多的特殊处理;
与技术栈无关: 微前端的接入不限制技术栈,无论是 Vue、React 还是 Angular 甚至是古老的 jQuery 都是可以完美融合;
独立开发、独立部署: 子应用独立仓库、独立开发、独立部署,同时主应用会将各个子应用融合在一起;
简单接入: 子应用只需要简单改造就能够接入,就像古老而又可靠的 iFrame 接入方式一样;
中心化路由: 主应用统一注册子应用,统一管理各个子应用的路由;
三、技术选型对比
业务模块集成过程中需要解决的主要问题如下:
- 跨域数据请求
- 跨域静态资源集成
- 样式隔离
- JS隔离
- 模块间跨域交互
方案对比:
相对比而言
iframe技术难度低,可靠性高,但体验是硬伤。
公共组件(NPM)方案表现平平,但是公共npm的升级成本太高。
乾坤微前端对JS做了绝对隔离,对CSS做了相对隔离。同时乾坤集成后所有模块UI交互同域,体验和同一应用相同。
四、微前端的原理-加载流程
- 浏览器访问主应用: 此时主应用会被下载到浏览器,并且开始运行主应用;
- 主应用注册子应用: 设置子程序相关配置:子应用名称、子应用入口、子应用加载到哪个 DOM、子应用激活路径等等;
- 启动主应用;
- 加载子应用: 浏览器会根据子应用入口下载子应用 HTML 模版;
- 解析子应用: 框架开始解析子应用 HTML 模版,DOM、JavaScript 资源和 CSS 资源;
- 加载子应用资源: 浏览器开始下载子应用 JavaScript 和 CSS 资源;
- 创建沙箱环境: 为了将多个应用隔离开来,互不干扰,主应用为子应用创建沙箱环境;
- 挂载子应用: 子应用开始运行;
- 预加载其它子应用资源: 由于微前端采用了预加载技术,在网络空闲的时候会加载其它的子应用资源,这样当其它子应用在唤起时资源已经准备好,立马可以运行起来。
五、微前端的原理-样式隔离
乾坤微前端样式隔离分为三种方案,动态样式表、影子DOM、Runtime css transformer。
动态样式表:
如图,当咋们从子应用A切换到子应用B时,将在主应用环境卸载子应用A的样式,重新挂载子应用B的样式。这样可以做到子应用之间样式绝对隔离,但是无法从根本上解决主应用样式和子应用样式的冲突问题。
影子DOM:
影子DOM可以将结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,可使代码更加干净、整洁。
详细可以参加:https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM
- Shadow host:一个常规 DOM节点,Shadow DOM 会被附加到这个节点上。
- Shadow tree:Shadow DOM内部的DOM树。
- Shadow boundary:Shadow DOM结束的地方,也是常规 DOM开始的地方。
- Shadow root: Shadow tree的根节点。
用一个实际案例代码来帮助理解:
影子DOM实现了样式的绝对隔离,但是目前IE11并不支持影子DOM,所以成为降级方案。
Runtime css transformer:
在乾坤的样式隔离方案上有人提出,乾坤可以在运行时将子应用的样式加一个前缀的转换。如图:
但此方案并未被乾坤纳入正式版本中,其主要原因有三点
- 转换后会加深对样式的寻址,影响性能。
- 影响到子程序JS中的样式查询选择器。
- 对于动画的样式变更后可能会破坏结构。
基于Runtime css transformer的思路,咋们提出了一个Build css transformer,利用工程化工具将样式在编译阶段打包成按照package.json里面的name属性进行区分的目标样式,实现真正的样式隔离。
六、微前端的原理-JS沙箱
代理沙箱(ProxySandbox):
通过 ES6 的 Proxy 特性,对 window(全局环境) 进行拦截,它可以劫持到子应用对 window 上的操作,例如:增加属性、修改属性、删除等操作,然后将这些变化记录到一个公共存储中(updateValueMap),当子应用卸载时我们把记录好的内容从 window 上卸载掉即可。这样一来,我们就可以在多个应用之间随意切换,并且保持应用间环境是隔离的。
快照沙箱(SnapshotSandbox):
当浏览器不支持 Proxy 时,会启用快照沙箱,快照沙箱其实就是通过对比的方式,将当前环境和原有环境进行对比,然后全量的恢复到原有环境,这种方式的缺点在于无法支持多个实例。
七、微前端的原理-路由
其主要原理是利用主程序传递给子程序的主框架路由来作为子程序路由的basePath,以此来达到修改子程序路由的目的。
- 子程序在生命周期里获取主程序传递的路由参数
- 子程序渲染时获取路径配置
- getRouter方法根据主程序的路由参数和乾坤全局变量进行封装
八、集成使用
主程序配置
- 安装依赖:npm i qiankun
- 通过registerMicroApps注册子程序
- 通过start方法启动微前端主框架
- 通过window.history.pushState({}, ‘’, path)来跳转路由
以下是子程序在主程序中的配置,分别包括模块名、入口地址和主应用路由地址。
子程序配置
- 全局常量配置
基于vue+ts的前端应用,需要在shims-tsx.d.ts中定义Window接口,将需要用到乾坤的两个常量定义到里面,以便在项目中支持乾坤标识判断。
- publicPath配置
去掉vue.config.js里面的publicPath配置项,将配置项留给入口文件main.ts配置。
此配置的作用是解决主程序在引入子程序的其他静态资源时,在所有path前面加上主程序的activeRule属性,以便能fetch到activeRule下子程序的依赖静态资源。
- umd打包配置
在vue.config.js里面增加应用模块的umd打包配置。
name使用package.json里面的子应用名称即package.json的name属性(需要在整个项目下全局唯一)。此输出以便主程序在fetch到子程序的JS后做全局隔离。
- 子程序生命周期函数配置
此处配置相对固定,区分在于如果是乾坤微前端主程序嵌入,则会调用mount方法,将主程序的props参数传递到render方法进行渲染,否则则直接无参数进行渲染。
- 路由配置
render渲染函数逻辑唯一区别是根据props参数获取不同的路由配置。
如果是乾坤嵌入,则将路由的基础路径换为主程序的参数里面的baseRoute即activeRule,否则即为默认的BASE_ROUTE即 /
。
- 子程序字体处理
子程序按照下图配置可以将字体配置到本地支持主程序通过fetch加载到字体静态文件。
九、兼容性处理
乾坤对chrome、firefox、edge等主流浏览器的新版本都做好了兼容处理,不过对于IE11的兼容性需要手动配置。原因是IE11对promise、symbol、startsWith、fetch等暂不支持。
方案一
先执行命令:
npm i custom-event-polyfill
npm i whatwg-fetch
然后在main.ts最前面加上以下代码即可:
方案二
利用babel,将IE11依赖的第三方JS独立打包到单独的文件中,步骤如下:
先执行命令:
npm i @babel/preset-env
npm i custom-event-polyfill
npm i whatwg-fetch
修改babel.config.js配置文件加入:
这样就会将兼容性JS打包到polyfill.js中,然后html可以判断是否IE来引入。
谢谢您抽时间阅读。