手动实现微前端框架的思路

此文是本人学习慕课网微前端课程《从零打造微前端框架:实战“汽车资讯平台”项目》的笔记,主要记录了自研实现微前端框架的思路。

  • 一、构师思维塑造
    • 1.1.软件设计原则
    • 1.2.架构种类
    • 1.3.微前端实现方式
    • 1.4.概念上架构质量的衡量
    • 1.5.日常开发过程中的架构质量
    • 1.6.架构的前期准备
    • 1.7.技术债务的产生
    • 1.8.技术填补
    • 1.9.预防系统崩溃
    • 1.10.重构流程
  • 二、打造微前端框架
    • 2.1.微前端实现方式对比
    • 2.2.确定技术栈
    • 2.3.分析框架
    • 2.4.梳理好思路,绘制项目架构图
    • 2.5.自动启动所有子应用的脚本
    • 2.6.构建服务
    • 2.7.子应用接入微前端vue2
    • 2.8.主应用开发
    • 2.9.css样式隔离
    • 2.10.父子通信
    • 2.11.提高加载性能
  • 三、发布框架与自动部署应用
    • 3.1.通过npm发布框架
    • 3.2.应用部署-创建自动部署平台

一、构师思维塑造

1.1.软件设计原则

1.单一职责原则:永远不应该有多于一个原因来改变某个类
2.开放封闭原则:软件实体扩展应该是开放的,但是对于修改应该是封闭的
3.里氏替换原则:父类一定是能够被子类替换
4.最少知识原则:只与你最直接的对象交流(高内聚,低耦合,做系统设计时,尽量减少依赖关系)
5.接口隔离原则:一个类与另一个类之间的依赖性,应该依赖于尽可能小的接口(不要对外暴露没有实际意义的接口,用户不应该依赖它不需要的接口)
6.依赖倒置原则:高层模块不应该依赖底层模块,它们应该依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象(应该面向接口编程,不应该面向实现类编程)

满足上边六大原则 设计出稳定的软件架构

1.2.架构种类

1.系统级架构,微前端可以算是系统级架构,也可以算是应用级架构
2.应用级架构:脚手架,模式库(ui库),设计系统
3.模块级架构:迭代
4.代码级架构:规范与原则

微前端是一个系统内应用间的架构方案
在多个应用之间,微前端则是一种系统间等架构方案

1.3.微前端实现方式

单实例:同一时刻,只有一个子应用被展示,子应用具备一个完整的应用生命周期
多实例:同一时刻,可以展示多个子应用,通常基于url的变化来做子应用的切换,通常使用web components方案来做子应用封装,子应用更像是一个业务组件而不是应用。

系统的稳定性:当一个实际的系统处于一个平衡的状态时,如果受到外来作用的影响时,系统经过一个过渡过程仍然能够回到原来的平衡状态,我们称这个系统就是稳定,否则称系统不稳定
1.架构设计的基石 2.可以更好的实现自我修复

系统的健壮性:计算机软件在输入错误,磁盘故障,网络过载或有意攻击情况下,能否不死机,不崩溃,就是该软件健壮性的具体表现
健壮性也可以称之为鲁棒性
健壮性的度量标准:
1.一个软件可以从错误的输入推断出正确合理的输入
2.一个软件可以正确的运行在不同环境下
3.一个软件能够检测自己内部的设计或者编码错误,并得到正确的结果

1.4.概念上架构质量的衡量

1.拓展性
2.维护性
3.可管理
4.高可用(故障修复,容灾,降级,熔断)

1.5.日常开发过程中的架构质量

1.理解难度
2.接入依赖的成本
3.崩溃率和错误率的指标
4.开发效率
5.错误上报和信息收集等功能

1.6.架构的前期准备

架构师分类:
系统架构师:从系统的维度,负责整体系统的架构设计,纯技术架构
应用架构师:偏向业务的系统,关注理解业务
业务架构师:从业务流程的维度,关注某一个行业,所做的事情可能会脱离具体的代码开发,偏向于数据分析

1.7.技术债务的产生

1.开发过程中因为时间紧迫而导致的实现不合理
2.开发过程中暂时没有想到更好的办法而去妥协进行的实现
3.架构设计前期没有考虑到的细节
4.不合理的交互设计,导致技术实现复杂
5.一些旧的功能在做的时候并没有详细的文档,并没有预留拓展接口,导致拓展困难,上线后问题剧增

技术债务累积过多产生的可能后果:
1.修复变重构,严重影响产出,
2.对旧功能需要不断进行兼容处理,严重影响开发速度

1.8.技术填补

1.项目拆分,代码解耦
2.必须有日志模块,操作日志,错误日志,业务日志等等
3.技术培训和传帮带能力
4.做好代码规范,编写好技术文档
5.不同工程师相互code review
6.用于发现系统中的技术债务

等产品上线后,开发就没有那么紧啦,这个时间大家可以找个时间处理技术债务,一遍建立感情,一遍品味原来的代码

1.9.预防系统崩溃

系统崩溃属于严重的架构设计事故
崩溃预防:
1.抓取用户行为,获取用户操作链条
2.解决村存量的技术债务问题
3.减少新增问题
4.对脏数据进行兜底和检验
5.单元测试
6.崩溃预警
7.自动化测试
8.更广的灰度触达
9.性能优化体系

当技术债务累积到一定的程度时,小软件可以考虑直接重构,快刀斩乱麻
而随便软件系统越来越复杂,微前端架构则成为架构优化(微重构)的解决方案之一

1.10.重构流程

1.确定问题点,确定重构功能和范围
2.旧架构设计和逻辑梳理
3.稳定性保证
4.性能保证
5.需求过程中的冲突问题

二、初识微前端

2.1.微前端实现方式对比

1.iframe
优势:技术成熟,支持页面嵌入,天然支持运行沙箱隔离,独立运行
劣势:页面之间的域名可以是不同的;需要设计相应的应用通讯机制,如何监听传参格式等内容;应用加载,渲染,缓存都是交给了浏览器处理,可控性低
2.web component
优势:支持自定义元素;支持shadow dom,并可通过关联进行控制;支持模板template和插槽slot,引入自定义组件内容
劣势:接入微前端需要重写当前项目;生态系统不完善,容易出兼容性问题;整体架构设计复杂,组件之间通讯也控制繁琐
3.自研框架
优势:高度定制化,满足所兼容的一切场景;独立的通信机制和沙箱运行环境,可解决应用之间相互影响的问题;支持不同技术栈子应用,可无缝实现页面无刷新渲染
劣势:技术实现难度较高;需要设计一套定制的通信机制;首次加载会出现资源过大的情况

最终实现选择-自研框架
1.路由分发式
2.主应用控制路由匹配和子应用加载,共享依赖加载
3.子应用做功能,并接入主应用实现主子控制和联动

2.2.确定技术栈

主应用:选定vue3技术栈
子应用:
vue2:实现新能源应用
vue3:首页,选车
react15:资讯,视频,视频详情
react16:新车,排行,登录
后端服务和发布应用
服务端接口:koa实现
发布应用:express
使用原生nodejs去手写完成微前端框架

2.3.分析框架

主应用:1.注册子应用 2.加载和渲染子应用 3.路由匹配(activeWhen,rules 由框架判断) 4.获取数据(公共以来,通过数据做鉴权处理) 5.通信机制(父子通信,子父的通信)
子应用:1.渲染自身功能 2.监听通信(主应用传递过来的数据,根据传递过来的数据做一些对应的更新操作)
微前端框架:1.实现子应用的注册 2.开始内容(应用加载完成,开启微前端框架) 3.匹配对应的子应用,加载对应子应用的内容,完成所有依赖项的执行 4.将子应用渲染在固定的容器内 5.公共的事件管理 6.异常的捕获和报错 6.全局的状态管理 7.沙箱隔离,保证子应用运行期间不被其他子应用干扰 7.通信机制
服务端的功能: 1.提供数据服务
发布平台:1.主子应用的打包和发布

2.4.梳理好思路,绘制项目架构图

工具推荐:processon


image.png

2.5.自动启动所有子应用的脚本

在根目录的package.json配置start命令
4-7的内容:在微前端的根目录下配置一个脚本去同时运行一个指令,同时打开所有的应用

2.6.构建服务

构建一个后端服务:
npm install koa-generator -g
koa -V
koa2 service

快速生成一个koa2项目
bin里的www是启动文件
public 静态资源文件

安装一个插件帮助修改node的时候自动重启node服务
npm install supervisor --save-dev
安装后start启动服务的命令中用supervisor 代替node

koa解决跨域问题:
npm install koa2-cors --save-dev
app.js进行引入并使用const cors = require('koa2-cors')
将服务设置为可跨域的状态

get请求中ctx.request.query可以获取到前端接口路径携带的参数
post请求 ctx.reuqest.body

2.7.子应用接入微前端vue2

首先需要做的:主应用获得子应用的生命周期,结构,方法,文件等,这样主才能控制子的渲染和加载

Vue2子应用接入微前端
子应用vue.config.js配置关键点devServer里的contentBase和headers允许跨域和output

Vue2子应用本身创建实例new Vue(),在微前端框架中,这个实例的执行与销毁,都应该交给主应用去执行

子应用需要改造的文件 1.vue.config.js 2.main.js

react15接入微前端
修改webpack.config.js里的output和devServer
改造入口文件index.js

webpack正常打包会将所有代码打包成一个立即执行函数

(function(){
})()

配置了output的library之后就会打包成library的属性值比如demo

const demo = (function(){})()

就可以通国window.demo拿到当前自应用的所有内容

2.8.主应用开发

中央控制器-主应用 main项目,使用的vue3技术栈
主应用在页面设置一个子应用的容器

在主应用进行子应用注册
设置一个navList记录子应用的注册信息store文件里的leftNav.js
添加注册子应用的方法utils文件夹里的startMicroApp.js

路由拦截,监听路由切换,重写路由切换事件,另外别忘了监听浏览器的前进/后退按钮window.onpopstate

微前端框架-获取首个子应用
进入项目,首个显示的子应用,先验证当前子应用列表是否为空,不为空的时候根据route匹配查找出当前的子应用,如果子应用存在,则跳转到子应用对应的url

因为路由的跳转又结合了手动的路由跳转,为了避免多次触发自定义的路由跳转事件,给当前的子应用添加上标记到window对象上

window.__CURRENT_SUB_APP__ = app.activeRule;

if(window.__CURRENT_SUB_APP__ === window.location.pathname){
  return
}

通过主应用生命周期去对应控制子应用的内容显示
beforeLoad开始加载
mounted渲染完成
destoryed卸载完成
传递过去的主应用的生命周期先在微前端里进行保存,然后通过全局的loading来控制子应用的内容显示/loading

微前端挂载到主应用,执行注册函数时,传入的就是主应用的生命周期,遍历执行这个主应用的所有生命周期,微前端框架就可以生成注册到主应用
微前端的生命周期:如果路由变化时,对子应用进行对应的销毁和加载操作

获取需要展示的页面-加载和解析html
在主应用生命周期beforeLoad去获取页面内容
子应用显示页面本质上是通过get请求(network中的doc)去获取页面信息,由此我们就可以在微前端框架中通过get请求去获取到子应用对应的页面,根据url通过fetch拿到res,然后res.text()拿到页面内容,然后将内容赋值给对应的容器就可以显示子应用对应的页面了,但是直接赋值给容器,容器是没法解析html中的标签,link和script(src,js代码)元素,需要专门提取出来这些元素进行处理

微前端框架环境变量设置
通过sandbox设置环境变量,运行js文件

隔离运行环境-快照沙箱(记录当前执行的子应用的变量,当子应用切换的时候,将变量置为初始值)
不同子应用之间的运行环境应该进行隔离,防止全局变量的互相污染
for循环window,new map一个新对象作为快照对象,快照沙箱适合比较老的浏览器

更现代化的沙箱方法:

let a = {}
const proxy = new Proxy(a , {
  get(){},
  set(){}
})
console.log(proxy.a) //此处会触发proxy的get
proxy.a=1 //此处会触发proxy的set

利用proxy去代理window,监听window的改变
设置一个空对象defaultValue去储存子应用的变量,当window改变时,将改变值以key,value的形式存储到defaultValue中。当需要获取window属性值的时候,也在代理的get中去返回defaultValue对应的值,沙箱销毁的时候inactive也只需要将defaultValue置为{}

2.9.css样式隔离

1.css modules 利用webpack配置打包机制进行css模块化处理
2.shadow dom 创建个新的元素进行包裹,但语法较新,兼容性较差
3.minicss 一个webpack插件,将css单独打包成文件,然后每个页面通过link进行引用

2.10.父子通信

比如说动态控制主应用的header和footer的显示与隐藏
1.props
好莱坞原则-不用联系我,当我需要的时候会打电话给你
依赖注入-主应用的显示隐藏,注入到子应用内部,通过子应用内部的方法进行调用
2.customEvent 通过new CustomEvent对象去触发和监听事件

子应用之间进行通信
1.props 子应用1跟父应用交互,子应用2再跟父应用交互,拿到子应用1传递出来的信息
2.customEvent

全局状态管理-全局store
利用发布订阅模式在微前端

export const creatStore = (initData) => (() => {
  // 利用闭包去保存传参的初始数据
  let store = initData;
  // 管理所有的订阅者,依赖
  let observers = [];
  // 获取store
  const getStore = () => {
    return store;
  }
  // 更新store
  const updateStore = (newValue) => new Promise((res) => {
    if (newValue !== store) {
      // 执行保存store的操作
      let oldValue = store;
      // 将store更新
      store = newValue;
      res(store);
      // 通知所有的订阅者
      observers.forEach(fn => fn(newValue, oldValue));
    }
  })
  // 添加订阅者
  const subscribeStore = (fn) => {
    observers.push(fn);
  }
  // 把方法return出去
  return { getStore, updateStore, subscribeStore }
})()
//整个store本质是一个闭包函数

2.11.提高加载性能

提高加载性能-性能缓存
定义一个cache对象,根据子应用的name来做缓存,如果当前的html已经解析并且加载过,就返回已经加载过的内容。如果没有,则走正常加载和解析的流程

提高加载性能-预加载子应用
1.获取到所有子应用列表,不包括当前正在显示的
2.预加载剩下的所有的子应用

三、发布框架与自动部署应用

3.1.通过npm发布框架

1.npm init初始化要发布的包
2.前置条件:需要有一个自己的npm账号;在终端npm login登录上自己的npm账号;使用npm whoami查看当前登录的账号
3.npm publish 进行发布 npm unpublish 取消发布
1.0.0
第一位:主版本号 第二位:次版本号 第三位:修订号
npm version patch 使用命令行修改修订号
npm version minor 次版本号
npm version major 主版本号

3.2.应用部署-创建自动部署平台

创建一个自动发布的express项目platform文件夹
新建一个index. html,点击按钮,遍历所有要发布的应用,然后分别触发ajax
npm install express express-generator -g
全局安装了上面的两个应用后,在命令行就可以使用express命令了
express -e 生成服务代码
文件路径和koa-generator生成的项目差不多
app.js中设置允许跨域

app.all('*', function (req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  next();
});

npm start启动项目,routes中创建一个/start路由,这是实现各个应用自动打包和发布的关键路由

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):