软件技术学习笔记

个人博客,记录软件技术与程序员的点点滴滴。

React微前端实战教程(体验篇)

在前端领域,用户体验放到第一位。在复杂业务操作的前端项目中,SPA的体验要比多页面或iframe嵌套的效果好很多。我们不能为了代码解耦、浏览器中无冲突而放弃SPA。

在以前的项目中,我们已经使用npm发包的方式将代码分割到多个仓库。在集成前端应用时,再使用npmWebpack工具构建成一个SPA。但仍有一个明显的缺点:每次底层组件更新时,上层SPA都需要重新集成打包、部署。同时,底层组件个数不少又经常更新,上层SPA频繁地集成与部署,让人很反感。

参照后端微服务,微前端(或称为前端微服务)也应该有以下特点:独立开发、独立部署、灰度发布。选择Micro frontends + SPA是最佳的DevOps与体验方案。经过几天的奋战,已经实现了一个基于React框架的微前端Demo,见: https://micro.qinzhiqiang.cn

1 项目设计

微前端主要拆分为三个部分:统一工程化构建脚本、浏览器中运行时框架、后端运维服务,其余的是可以无数多的微前端APP。本项目中,我们只考虑统一的React+Router+Redux前端技术栈,也可以确保开发人员在本地开发拥有独立SPA的开发体验、无感知微前端的存在。

如果运行时框架要同时支持React+Vue+Angular,至少需要统一托管浏览器History的变化、微前端APP的启动,意味着与各个前端框架强绑定的Router都无法使用,需要我们实现统一的Router,工程量比较大。

Demo中需要以下8个项目:

  1. react-micro-frontend-scripts,工程化核心。Webpack与NodeJS脚本,控制微前端项目的元信息输出、JS符号导入/导出。
  2. react-micro-frontend-polyfill,浏览器Polyfill。引入react-app-polyfill不同的内容,构建支持IE9、IE11和其它浏览器不同的Polyfill入口文件。
  3. react-micro-frontend-framework,运行时核心。运行在浏览器中的微前端框架,负责微前端App的资源加载、组件显示,同时提供公共库JS功能服务。切换到不同的主路由时,可以展示不同的微前端APP。
  4. react-micro-frontend-app-home,主页微前端APP。经部署了多个版本,用户可以看到有差异的不同主页。
  5. react-micro-frontend-app-example,与Redux、Saga、Epic相关的微前端APP样例。
  6. react-micro-frontend-app-example-sub,嵌套在example中展示的另一个微前端APP样例。已部署多个版本,其中一个版本有打字效果。
  7. react-micro-frontend-app-example-echarts,微前端依赖样例,依赖从CDN加载的Echarts。定义Echarts为微前端3rd-echarts,框架加载Echarts完成之后才加载该APP。
  8. react-micro-frontend-server-go,服务与运维核心,GoLang编写。处理微前端部署的元信息,提供SPA默认请求服务、不同用户提供不同的微前端版本服务;控制微前端版本上线、下线。也能提供微前端静态资源服务,方便本地开发微前端。

2 效果体验

用户开始浏览 https://micro.qinzhiqiang.cn 时,后端server-go动态生成一个HTML页面,包含以下内容:微前端Metadata、加载Framework的CSS/JS。部署多个framework版本时,可给用户返回不同的framework版本。

<link href="/rmf-framework/v1.0.0/framework.08a0d251.chunk.css" rel="stylesheet">

<script>var rmfMetadataJSONP = {/*...*/} </script>

<script>function(){/*inline runtime-framework.xxxx.js javascript*/}</script>
<script src="/rmf-framework/v1.0.0/vendor-polyfill.a034dfcb.chunk.js"></script>
<script src="/rmf-framework/v1.0.0/vendor-redux.0367ad19.chunk.js"></script>
<script src="/rmf-framework/v1.0.0/framework.bcc5ef2e.chunk.js"></script>

2.1 微前端Metadata

Metadata中包含:各个微前端APP的CSS/JS资源路径与渲染位置、默认路由。可以F12查看全局变量rmfMetadataJSONP。独立的请求URL为: https://micro.qinzhiqiang.cn/api/metadata/info 。对于不同的用户,可以返回不同的Metadata。在Demo中,我们随机返回不同的app-home版本,F5刷新页面就可以体验。

{
    "apps": [
        {
            "id": "3rd-echarts",
            "dependencies": [],
            "entries": [
                "https://cdn.bootcdn.net/ajax/libs/echarts/4.8.0/echarts.min.js"
            ],
            "renders": []
        },
        {
            "id": "app-example",
            "dependencies": [],
            "entries": [
                "/rmf-app-example/v1.1.0/app-example.1c738c30.css",
                "/rmf-app-example/v1.1.0/app-example.c5d5e9b7.js"
            ],
            "renders": [
                {
                    "renderId": "root",
                    "routePath": "/app-example",
                    "componentKey": "default"
                }
            ]
        },
        {
            "id": "app-example-echarts",
            "dependencies": [
                "3rd-echarts"
            ],
            "entries": [
                "/rmf-app-example-echarts/792735ad/app-example-echarts.3c31ae87.css",
                "/rmf-app-example-echarts/792735ad/app-example-echarts.67238f12.js"
            ],
            "renders": [
                {
                    "renderId": "app-example-sub",
                    "routePath": "/app-example/echarts",
                    "componentKey": "default"
                }
            ]
        }
    ],
    "extra": {
        "defaultRoute": "/home"
    }
}

2.2 不同版本的主页

老版本主页: 老版本主页

新版本主页,React Logo像卫星一样远近距离围绕旋转: 新版本主页

2.3 Example嵌套Sub

微前端嵌套,Example App中嵌套Example App Sub

Example App Sub本身也是一个独立的微前端,可以按需加载到任意地方。同时,其内部中使用Webpack的import()动态加载自身内容。

Example嵌套Sub

打字效果版本: Sub打字效果版本

2.4 前端资源加载次序

从输入URL到显示主页:

  1. 用户浏览某个URL时,server-go返回SPA入口HTML文件。
  2. 解析入口HTML文件时,加载framework的CSS与JS文件。
  3. 执行framework JS内容时,渲染root节点,其中的根路由使用<Switch>渲染render == 'root'的微前端。未匹配路由时,<Redirect>到用户的默认路由/home.
  4. 使用AsyncApp组件渲染app-home,动态加载其相关的CSS与JS文件。待app-home的CSS/JS加载完成时,渲染app-home动态注册的React组件HomeApp

加载主页

跳转到/app-example/sub路由:

  1. 框架使用AsyncApp组件渲染app-example,动态加载app-example的CSS/JS之后,渲染app-example动态注册的React组件ExampleApp
  2. ExampleApp中使用AsyncApp组件渲染app-example-sub,动态加载app-example-sub的CSS/JS之后,渲染app-example-sub动态注册的React组件ExampleAppSub
  3. ExampleAppSub中使用import()动态加载其它内容:1.da13f959.chunk.css、1.1eedc88a.chunk.js。

加载Sub子页面

跳转到/app-example/echarts路由:

  1. 微前端之间有依赖关系,框架先加载依赖的3rd-echarts,再加载app-example-echarts
  2. app-example-echarts使用preload技术,并行请求3rd-echartsapp-example-echarts的CSS/JS,提升用户体验。

加载Echarts子页面