一个电商网站的微前端落地实践
背景
XX电子商务平台是一个H5版网站,该网站前端主要使用的技术是Vue1.X
全家桶。项目开发到现在快两年了,一些问题开始显露出来:
- 项目日益庞大,代码结构变得复杂 ,项目中的模块越来越多,很少人能对项目的代码有整体的掌握。对开发而言,总会有“牵一发而动全身”之虞。
-
技术选型落后, vue1.x 至今已经有两年未更新,项目中使用的
Webpack
,Karma
等工具也远远低于最新版本。ssr
,Flow
等新技术也无法用在老项目中。 -
CI
跑测试和打包速度慢,CI
服务器上单次跑测试和打包时间需要15分钟左右。 - 测试成本高。前端所有的代码在单一仓库上,理论上每次上线都需要对整站做回归测试。
考虑微前端的引入
![](https://img.haomeiwen.com/i7515763/a1041e57ab8fdad8.png)
在后端已经引入微服务,拆分出不同模块进行独立开发的时候,前端依旧臃肿,且随着时间增长变得越来越庞大和难以维护。
微前端是将后端微服务的概念引入到前端来,希望对前端代码进行拆分,使得前端各部分能独立维护、独立交付。
![](https://img.haomeiwen.com/i7515763/05598c613f20ee55.png)
微前端带来的好处
- 复杂性降低,代码的拆分使得各部分有自己的职责,每部分都足够小。
- 在技术选型上更加灵活,可以选择最适合的技术和架构。对于每个小服务而言,可以独立进行技术选型,也能很方便升级某服务的框架版本。
- 各服务可以相对独立地开发,部署。每个服务可以扩展自己的功能,也可以交由特定团队开发维护。
- 更易测试,修改了某个服务,可以更专注于此服务的测试。
引入微前端本项目需要考虑哪些问题?
-
CI
、CD
。前端由一个项目变成了多个项目,需要同时建立多套CI
并调整部署方案,这将带来运维成本上升。 -
如何集成?各服务要组成一个完整的站点,用户在各服务之间能无感知地跳转。具体而言,不同服务需要共用
domain
, 是否登录等状态,另外服务间也要能互相通信。 - 组件库共用?前端拆分出多个服务之后,这些服务的技术选型和开发是相对独立的,但对于一个风格统一的大型网站而言,我们又需要一套公共的组件库来保证网站样式和行为的统一,以及降低开发成本,避免同样的组件在各服务间各实现一套。 那么组件库如何能保证在技术选型不一的各服务间共用?
- 项目成员的学习成本和开发环境。使用微前端,更改了之前的开发和部署方式,如何降低这一部分的上手难度和学习成本,并顺利推广到组内所有开发?
我们使用的方案
吕靖 在洞见中发表了一篇微服务实战的文章: 「微前端」- 将微服务理念扩展到前端开发(实战篇)。这篇文章列了四种微前端的方案:
- 使用后端模板引擎插入 HTML
- 使用 IFrame 隔离运行时
- 客户端 JavaScript 异步加载
- Web Components 整合所有功能模块
考虑到XX电商是一个H5网站,每个页面的内容有限,我们不需要将页面中的组件划分到各服务,我们直接以页面为最小粒度进行划分。
所以我们直接采用了第四种方案,使用Web Components技术开发公共组件,各服务独立开发部署,然后通过Nginx连接起来。
1. 如何拆分
引入微前端的契机是客户准备开发 * * 模块,该模块相对独立,跟其他模块耦合较低,我们的初衷是先将其拆分出来进行独立开发和维护。于是将 * * 模块整个流程涉及到的页面拆分出来,新起一个服务。
2. 使用Nginx
将各个服务连接到一起
拆分出新的服务之后,各服务需要衔接在一起。我们用Nginx
将各服务代理起来,访问各服务时,通过特殊路由前缀就能将请求转发到某一服务上,而各服务能共享Nginx Server
的状态(cookie
, session
等)。
![](https://img.haomeiwen.com/i7515763/15e1e51b2464bf4d.png)
3. 实现一个框架无关的组件库
为何强调框架无关? 我们认为,这个共用的组件库最好跟框架无关的,无论服务选择Vue
、Angular
还是React
,最好都能使用这个组件库。不然如果组件库使用Vue实现,那么必定无法无缝迁移到React和Angular中,这样就限制了前端各服务的技术选型甚至主流框架版本。总之,这种考虑为了提高技术选型的自由。
使用 Custom Element
对于组件化,W3C
已经有了Web Components
的草案,使用浏览器原生能力实现组件化已经成为现实。Web Components
草案其中非常重要的一点是提供了自定义HTML Element
的能力。
这里给出使用Custom Element
技术定义HTML Element
的示例:
class Header extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'}).innerHTML = `<h1>My Header</h1>`;
}
}
customElements.define('my-header', Header); // 注册, 第一个参数是标签名称
![](https://img.haomeiwen.com/i7515763/483fce1b0cb76e09.png)
使用Custom Elements
定义标签时,能够使用组件绑定和注销等生命周期:
-
connectedCallback
// 插入到文档中 -
disconnectedCallback
// 从文档中移除 -
adoptedCallback
// 移动到新文档中
也能使用AttributeChangeCallback
来实现对属性变化的监测。
一个更完整的例子: https://github.com/neuland/micro-frontends/tree/master/2-composition-universal
4. 部署问题
多个服务开发完成之后,打包成静态文件,放到Nginx服务器上即可:
server {
listen 8000;
server_name localhost;
location ~ ^/service01/ {
root /var/www/service01;
}
location ~ ^/service02/ {
root /var/www/service02;
}
}
5. 对本地开发环境的升级
使用微服务之后,带来的另一个问题是本地开发环境,最明显的是前端开发要启动的项目变多了,原来打开一个项目并运行起来就能修改的活,可能需要同时打开多个项目。另外我们也需要Nginx
或者Http-proxy
类似的解决方案做请求的转发。那么怎么能使得开发更方便一些?
使用Docker
和Docker Compose
![](https://img.haomeiwen.com/i7515763/589ce01f543174fb.png)
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中。
我们将Nginx
配置打包到docker中,这样就在开发者之间同步了路由配置。
Compose
是用于定义和运行复杂Docker 应用的工具。你可以在一个文件中定义一个多容器的应用,然后使用一条命令来启动你的应用,然后所有相关的操作都会被自动完成。
我们也将前端各项目的运行环境塞到docker中,并使用Docker Compose
将这些服务管理起来,在compose的配置中写入了安装第三方依赖、运行项目的脚本。
services:
nginx:
container_name: nginx
image: nginx:1-alpine
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- "80:80"
service01:
container_name: service01
build:
context: ./
dockerfile: Dockerfile
volumes:
- ../:/code/
working_dir: /code
command: ["bash", "-c", "yarn && yarn dev"]
service02:
container_name: service02
build:
context: ../../service02
dockerfile: Dockerfile.dev
volumes:
- ../../service02/:/code/
working_dir: /code
command: ["bash", "-c", "yarn && yarn dev"]
使用Docker
和Docker Compose
之后,一个新人搭建前端开发环境并启动只需要执行下面几步:
- 将所有前端项目克隆到同一个文件夹下
- 安装
Docker
和Docker Compose
- 执行
docker-compose up --build
然后就可以打开编辑器开始写代码了。
总结
以上就是我们XX电商前端团队的一些微前端实践,主要讨论了落地的一些问题。欢迎大家来拍砖~~