微前端积木系统 - 原理篇

前言

微前端是什么

一种将独立的前端应用组成一个更大的整体的架构风格

微前端是一种架构,将一个页面切分成若干个『微应用』,各个『微应用』可以独立部署,而且每个『微应用』可以供多个页面引入,达到复用的目的。

微前端有哪些形态

微前端并不是一种新的东西,它只是一种架构风格,在很早的一些 web 实践中,其实已经有它的身影:

  • 通过后端模板集成:即通过后端配置路由,将一个 web 应用分割成若干个子应用
  • 通过 iframe 集成:使用 iframe 分隔页面的多个部分。

比较常见一些形态:

  • 通过 package 集成:将微应用发布成 node 包,供容器应用引入
  • 通过<script>集成:每个”微应用”都对应一个 <script> 标签,并且在加载时导出一个全局变量。然后,容器应用程序确定应该安装哪些微应用,并调用相关函数以告知微应用何时以及在何处进行渲染。
  • 通过 Web Component 集成:每个微应用以自定义标签的形式引入。

以上就是微前端的一些应用形式,使用<script>集成的方式往往是最灵活的,使用频率最高的一种方法。本文就这种方法在项目中的落地展开叙述。

为什么要接入微前端

需求分析

项目中,活动规则模块,占据页面大量篇幅,其中以静态布局为主,但是每次都要复制大量文案,并且重新编写 html 等繁复的代码,当中其实耗费了大量的人力。

如何解决这个痛点呢?我们使用 『微前端』 + 『组件积木系统』的架构。

架构选择

<script>集成的架构中,我们要产出:

  1. 容器组件:多个 html 页面容器(html 模板),支持从积木系统注入容器属性。

  2. 微应用组件:多个可复用于容器中的微应用(React/Vue 组件),并且支持从积木系统注入自定义属性参数。

  3. 积木系统: 一个后台系统,用于存储多个容器组件(站点)数据。

    在积木系统中,微前端中的容器组件称为『站点』,更贴近使用者。

实现原理

我们通过简单的代码和注释,直观地解释该架构的原理。

我们将微前端架构中的模板容器对应到积木系统中的模板 或者 站点

微应用则对应到积木系统中的组件

积木系统,负责对模板的配置数据 进行 增删改查 和 保存, 并拉通站点发布功能。

模板容器

模板容器,在积木系统中称为模板(template),如果 一个站点 (site)仅仅包含一个 html 模板,那么模板容器也可以成为 站点

模板数据来自积木系统的数据注入(以下只保留核心代码,请重点看注释)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html>
<head>
<!-- 注入页面的基本信息,如title,description等 -->
<meta charset="UTF-8" />
<title>{{ template.data.title | default(title) }}</title>
<meta
name="keywords"
content="{{ template.data.keywords | default(keywords) }}"
/>
<meta
name="description"
content="{{ template.data.description | default(description) }}"
/>
<!-- 注入cssAssets, 包含多个微前端组件的css文件 -->
{% for item in cssAssets %}
<link rel="stylesheet" href="{{ item }}" />
{% endfor %}
</head>
<body>
<div id="app"></div>
<!-- 假设微应用技术栈为React -->
<script src="//jy.yystatic.com/lib/react@16.12.0/umd/react.production.min.js"></script>
<script src="//jy.yystatic.com/lib/react-dom@16.12.0/umd/react-dom.production.min.js"></script>
<script src="//jy.yystatic.com/lib/prop-types@15.7.2/prop-types.min.js"></script>
<!-- 注入jsAssets,包含了多个微前端组件的js文件 -->
{% for item in jsAssets %}
<script src="{{ item }}"></script>
{% endfor %}
<!-- 注入template data,包含一些全局配置数据,如容器(即template)配置等,env可能的值为 editor,production -->
<script>
window.GLOBAL_DATA = { "env": "{{env}}" };
window.TEMPLATE_DATA = {{ template.data | dump }};
</script>
<!-- 注入components的数据,包括组件的props,json-schema协议等 -->
<script>
window.COMPONENTS_LIST = {{ components | dump }};
</script>
<script>
// 以下代码的主要逻辑为获取window上的配置数据,渲染到容器上
// <Container />的逻辑详见微应用组件的组件渲染章节
ReactDOM.render(<Container />, document.getElementById('app'))
</script>
</body>
</html>

微应用组件

微应用组件,在积木系统中称为 组件(component)

对于每一个微应用,我们不关心微应用的技术栈,但是通常我们的 html 模板能够根据微应用的技术栈调用与之对应的render方法,将微应用逐一渲染到页面上。

因此,我们需要微应用打包产出component.umd.js文件,以及样式文件component.css文件。

以 image 组件为例,我们产出两个文件

1
2
3
4
5
<link
rel="stylesheet"
href="https://jy-test.yystatic.com/jy/jy-component-react/jy-component-image-f17d27b33d.css"
/>
<script src="https://jy-test.yystatic.com/jy/jy-component-react/jy-component-image-f17d27b33d.js "></script>

这两个标签在模板编译阶段,将动态插入到如下容器组件的模板片段中

1
2
3
4
5
6
7
8
<!-- 微应用CSS文件 -->
{% for item in cssAssets %}
<link rel="stylesheet" href="{{ item }}" />
{% endfor %}
<!-- 微应用js文件 -->
{% for item in jsAssets %}
<script src="{{ item }}"></script>
{% endfor %}

组件渲染

接着模板容器的渲染阶段

Container.jsx 的核心代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 获取组件列表并渲染
const components = window.COMPONENTS_LIST

return (
<div id='container' className={styles.container} style={style}>
{components.map((component) => {
const fn = window[component.name]
if (!fn) return null

const componentFn = fn.__esModule ? fn.default : fn
// console.log(data[component.uid])
return (
<React.Fragment key={component.uid}>
{componentFn(component.data, component.uid, global.env)}
</React.Fragment>
)
})}
</div>
)

积木系统

积木系统,是一个简单的中后台,它的核心功能是修改站点,储存站点,发布站点。

我们的站点可以是一个模板或者多个模板构成,这里仅讨论单模板站点,所以我们可以把站点简单理解为前面提到的模板

一个模板包含多个组件,在积木系统中加载模板, 渲染到积木系统的预览界面中,通过控制台修改站点配置,实时预览站点。

数据录入

微前端应用(模板容器)与积木系统之间通过一套设计好的数据协议互相依存。积木系统通过用户定义的模板配置变量,预设配置,支持录入和修改模板容器组件的配置信息。

我们通过表单注入站点组件微组件组件Props, 而这些Props`足够我们实现很多功能,例如:

  • 传入api地址:组件内部完成后端请求等逻辑,渲染到页面,实现一个可以注入不同数据源的,逻辑相同的微应用。
  • 传入富文本片段:组件获取富文本片段并渲染,实现了一切静态页面,文案内容的可编辑化的一个富文本微应用。

积木系统需要配套一套表单控件,用于录入数据。假如要实现以上两个微应用,只需要配套一个富文本插件和简单的文本框就可以了。

产出

积木系统产出一个 html 文件,这个 html 文件就是上面提到的模板,只不过积木系统负责将后台保存的站点数据插入到其中,而这些数据就是用户从积木系统录入的。

这样,就产出了一个独立且可用的 html 文件。

站点发布

在积木系统,根据不同的业务组,配置对应的webhooksUrl,当站点修改完毕,点击控制台上的发布按钮,将站点信息提交到 webhookUrl, webhooks 服务负责接收站点的 html 文档数据,将一个可访问的html模板文档上传到服务器,至此发布完成。

总结

可见,微前端架构不过如此,本文旨在从一个简单的例子介绍整套流程框架的实现。

在实际使用中肯定会有很多细节问题,包括积木系统的数据库设计,多个模板之间的通讯,多个组件之间的模块打包与资源共享问题, webhooks 服务的搭建等等。

这些配套设施往往影响到积木系统的易用性,可用性等等,但是这不妨碍我们了解微前端的概念,这里仅作简单分享,欢迎指正。