一、应用注册

微应用注册是微前端实现的核心功能。创建一个配置文件app.config.js,用于子应用的注册,这个应用注册表拥有每个子应用及对应的入口。项目运行时,主工程项目读取项目的配置文件。子应用修改代码需要更新配置信息或者新增子应用时只需要在配置文件中更新或添加信息,主工程项目无需修改代码重新打包,直接动态读取配置文件即可。主工程项目提供注册的接口,子应用进行注册,最终聚合成一个单页应用。应用注册表信息包括:项目名称name、路由前缀path(当子模块有多种url 前缀的时候,path 也可以为数组形式)、子项目渲染出口文件main、是否为基础项目base,子项目对外接口文件:store。app.config.js  文件格式如下:

let apps = [{ 
  base:false,
  main:"/eform/index.1e3g4e46.js",
  name:"eform",
  path:"/eform",
  store:"/eform/store.js"
}]

用户访问主项目index.html 后,浏览器运行app.config.js 文件,读取应用配置文件,通过single-spa 框架提供的registerApplication 接口对子项目依次进行注册;最后。注册配置文件中配置的各个子应用后,首先加载主项目应用,再通过路由判定,动态加载子应用。在single-spa 注册阶段,不会下载子应用的代码,当子应用被激活时才会加载代码,从而实现按需加载。本文使用SystemJS(一个通用的模块加载器,能在浏览器或者NodeJS 上动态加载模块)。作为项目的模块加载工具,通过遍历app.config.js 配置集合注册所有模块。

二、应用通信

微应用之间的通信及交互分为主子应用、子应用之间的通信及交互方式。本文设计基于Vuex  实现微前端的消息总线。主应用提供全局派发器GlobalEventDistributor,主要的职责是触发各个模块对外的API,函数会被种到每一个模块当中,便于每一个模块可以调用其他模块的api,通过这种方式每个子应用中就可以直接调用跟查询其他子应用的api 与状态。每一个子应用,会对外提供一个Store.js,主子应用使用建立的GlobalEventDistributor 和store 进行状态管理实现消息传递。主应用中注册子应用时,将子应用store 实例和派发器一起组装成一个对象之后,以props 的形式传入传入每一个单独模块中。在子应用的配置中加入消息实体属性,通过该属性值的修改,动态将消息通知给子应用。子应用使用主应用共享的store 实例并设置只属于子应用的store 实现响应式[8]。子应用传递主应用的方式为子应用中导出挂载前回调事件,在事件参数中,将子应用待传递的参数对象置入,发布到主应用的事件总线上,主应用监听到消息后, 从其事件总线上获取子应用的数据对象。

三、路由协同

为了能够实现在主应用中切换子应用和子应用的路由,需要实现主子应用的路由协同。本文采取子应用的路由系统注册进主应用之后,再由子应用内部的路由系统接管路由改变事件,通过消息总线实时监听路由的变化。同时在子应用路由卸载时,主应用触发相应的销毁事件,子应用在监听到该事件时,调用自己的卸载方法卸载自身运行时上下文资源;其次,需要设置默认的子应用作为主应用的默认路由规则,对默认子应用进行预加载,并定义默认子应用加载完成后的回调事件。

四、构建与部署

(1)应用的构建

本文采用webpack 工具进行前端工程化构建。项目中应用的开发技术浏览器可能不同,需要借助webpack 构建编译,在webpack 的配置文件中使用加载器(loader)和插件(plugins)处理,编译为浏览器可识别的代码。在实际业务中会有很多同类型的Vue  项目,产生大量的重复代码、重复引用,可通过配置externals 可以极大减小子应用打包出来的体积。子应用根据自身的情况设置不需要重复参与打包的库。通过webpack  将子应用打包成umd(Universal  ModuleDefinition,称之为通用模块规范,可以使SystemJS  加载的文件格式)格式的library。为了防止样式污染,CSS 作用域方面,使用webpack 在构建阶段为业务的所有CSS 都加上自己的作用域,子应用对外输出不需要入口HTML 页面,只需要输出的资源文件即可,资源文件包括js、css、fonts 和imgs 等。

output: {
  path: xxx,
  publicPath: xxx,
  filename: '[name].[chunkhash:8].js',
  chunkFilename: 'js/[name].[chunkhash:8].chunk.js',
  libraryTarget: 'umd',
  library: xxx,//模块的名称
}

主应用的index.html  设置需要动态加载的资源,SystemJS  只是在加载index.html 时注册了这些CDN 地址,不会直接去加载,当子应用里用到的时候,SystemJS 会接管模块引入,SystemJS 会去注册的map 中查找匹配的模块,就再动态去加载资源。这样就避免了不同子应用在这套架构下产生的多余加载。

(2)构建后集成和独立部署

微前端的部署,是一个应用聚合的过程。使用webpack 构建应用,把每个子项目打包好的静态文件,放到服务器的正确的静态目录下并更新配置文件。在资源服务器上起一个监听服务(Nodejs  脚本+pm2  守护),原有子项目的部署方式完全不变, 当监听服务检测到文件改动时,去子项目部署文件夹里找它的index.html,把入口js 用如下正则匹配出来,写入apps.config.js。

const reg = new RegExp(`src="\(\\/${content[i]}\\/index\\.\\w{8}.js\)`)