从"微前端"到“微模块”

7,452

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

前言:作为“前端微模块”这个概念有点新,之前虽然也有人提过这个词(可百度),但都只是简单的将其等同于动态加载模块,并没有赋予其更大的意义,好像也没有看到具体的落地方案。小弟也是突发奇想,摸着石头过河,想和大家讨论一下“前端微模块”会不会成为一片广阔的天空?

微前端够用吗?

从产品的角度

某个大型应用包含A,B,C,D,E,F,G等若干功能,原来一直是整体打包出售...

随着用户需求的多样化,有的用户仅需要部分功能,于是聪明的前端架构师“小李”利用时下流行的微前端技术,将应用拆分成了 3 个子应用:

  • 【基础应用】包含功能:A
  • 【子应用A】包含功能:B,C,D
  • 【子应用B】包含功能:E,F,G

这样等于有 3 个套餐可以供客户选择:

  • 套餐A:基础应用 + 子应用A
  • 套餐B:基础应用 + 子应用B
  • 套餐C:基础应用 + 子应用A + 子应用B

然而用户的需求越来越精细化,有的需要ABCD,有的需要ACEG,有的需要ABDF,而且同一个功能可能还存在需求版本的不同,这让“小李”无可适从。

“微前端”还不足够灵活、粒度不足够细。

从开发的角度

对于“发送短信验证码”、“忘记密码”等某些通用的业务流程和功能,如果多个工程都需要,你是如何跨工程共享和维护的呢?是简单的复制粘贴?还是一股脑全放基座里面?

“微前端”并没有解决工程之间代码的复用和维护的问题。

将业务功能放进模块

对于后端开发来说,按业务功能来划分模块几乎是业界共识,而在前端开发中往往是按UI界面来切割模块,这样的前端模块实际上只是Component组件,不具备独立性与完整性。

如果我们将完整的业务功能(包括UI组件、样式、图片、交互流程、业务逻辑、API请求、数据管理等)都打包到一个NPM包中,并利用NPM的版本和依赖管理机制来维护客户需求,岂不美哉?

试想一下,某客户需要 A,C1(C功能的某个版本),E2(E功能的某个版本),G 功能,我们只需要安装相应版本的NPM包:

npm install A C@1 E@2 G

业务模块变成了NPM包,版本号被关联至需求。

Great!我们称这些包含完整业务功能的模块为前端微模块,可见所谓的微模块其实就是包含业务功能的NPM模块。

微模块的划分

微模块是实现特定业务功能所需资源的集合:

  • 划分视角: 业务功能(非UI界面)
  • 划分原则: 高内聚、低耦合(有清晰的边界)

请注意"高内聚、低耦合"是唯一的划分标准,并不要求单一职责,所以一个微模块中可能包含多个功能点、多个UI组件,一组相关视图。

如果二个微模块之间紧密依赖,交互密切,请不要分割它们,这样会使问题复杂化。一种常用思路是借助于后端Restful理念,将每种资源的维护(增删改查封)装成一个独立的微模块。

微模块与UI组件的区别

微模块和UI组件都是一个NPM包,似乎有点相似,但其实它们有本质的区别:

  • UI组件是一种单体的封装;而微模块是一种资源的集合,它们一个是苹果一个是篮子
  • UI组件为复用而生,提取它是为了在多个业务场景中复用渲染与交互逻辑;比如一个List组件,就是用来展示列表,而这个列表是用户列表?还是文章列表?你不会把它绑定到具体的业务上,因为一旦绑定具体业务,那么你这个UI组件就失去通用性了;而微模块恰好相反,它包含具体的业务逻辑,只能用在一个业务场景中,提取它的目的不是为了复用,而生为了解耦与自治
  • 微模块有独立自治的能力,有自己的View、Controller、Model,是一个可以独立运转的微型子系统。

微模块的开发和维护

微模块的开发和维护就是对NPM包的开发和维护,并不附加任何新的学习成本,你需要做的只是维护它的依赖关系,并对外封装API,以保证独立性与易用性。

微模块的部署

通常有2种方式使用和部署微模块:

  • 静态编译:微模块作为一个NPM包被安装到工程中,通过打包工具(如webpack)正常编译打包即可。这种方式的优点是代码产物得到打包工具的各种去重和优化;缺点是当某个模块更新时,需要整体重新打包。
  • 动态注入:利用打包工具的动态加载功能(如webpack5 的 Module Federation)将微模块作为子应用部署(与时下流行的微前端类似)。这种方式的优点是各子应用独立部署运行,当某子应用中的微模块更新时,其它应用无需重新编译,刷新浏览器即可动态获取最新模块;缺点是没有打包工具的整体编译与优化,代码和资源容易重复加载或冲突。

Elux对以上二种部署方式都有支持和示例。

Elux中的微模块

我们先看一下时下流行的前端工程目录,假设有独立的功能ModuleAModuleB

src
├── assets
├── consts
│      ├── ModuleA
│      │      ├── Const1.ts //A中使用的一些常量
│      ├── ModuleB
│             ├── Const2.ts //B中使用的一些常量
├── utils
├── components
│      ├── ModuleA
│      │      ├── Component1.ts //A中使用的一些UI组件
│      ├── ModuleB
│             ├── Component2.ts //B中使用的一些UI组件
├── containers
├── pages
│      ├── ModuleA
│      │      ├── Page1.ts //A中使用的一些页面
│      ├── ModuleB
│             ├── Page2.ts //B中使用的一些页面
├── models
│      ├── ModuleA
│      │      ├── Store1.ts //A中使用一些状态定义
│      ├── ModuleB
│             ├── Store2.ts //B中使用一些状态定义
│

其特点是以“文件职能”作为一级分类、“功能模块”作为次级分类。

现在如果我需要拿掉ModuleB,或者新增ModuleC,你将不得不进行多个目录的操作。随着文件越来越多,相互引用越来越复杂,ModuleB的相关资源和依赖像一堆乱麻散落在各个不同文件和文件夹中,你会发现要干净的剥离ModuleB是一个巨大的任务...

那应当如何改进呢?

  • 将“功能模块”作为一级分类,“文件职能”作为次级分类
  • 注意模块的对外封装,不要随意绕过封装来引用模块内部资源

以下是Elux工程的常用结构:

src
├── modules
│      ├──  ModuleA
│      │     ├── assets
│      │     │     ├── imgs //A中使用的一些图片等
│      │     ├── consts
│      │     │     ├── Const1.ts //A中使用的一些常量
│      │     ├── utils
│      │     ├── components
│      │     │     ├── Component1.ts //A中使用的一些UI组件
│      │     ├── views
│      │     │     ├── View1.ts //A中使用的一些业务视图
│      │     ├── model.ts //A的数据模型
│      │     └── index.ts //A的对外封装与导出
│      │ 
│      ├── ModuleB
│      ├── ModuleC

可以看到在Elux工程中,所有与功能模块相关的文件都被放到了一个独立的文件夹中,并通过index文件统一对外导出,这便是Elux中微模块得以独立开发、安装和运行的基础。

微模块vs微前端

微前端是一个广义上的概念,微模块也是实现微前端的一种解决方案。与之前狭义上的微前端相比,微模块灵活性更高,但隔离性更差(只能依靠module和约定的namespace)。如果你没有统筹整个项目的权利,或者项目本身就是异构系统(各子应用采用不同技术栈),那微前端还是首选方案。

如果用2个字概括它们的特点:

  • 微前端更加注重:隔离
  • 微模块更加注重:自治

广义与狭义的微模块

或许你会说,不否认微模块很好,可是我们项目体量还没那么大,也不需要拿到市场上去给客户定制,那微模块对我来说没啥意义呀?

其实我们以上所说的都是狭义上的微模块,也就是严格遵循应用由微模块组合而成,不存在其它全局资源与顶级组件,其目的就是保证微模块的完整性,让它们可以自由组合、独立部署。

而广义上的微模块或许并不以独立开发和部署作为目标,可以存在公共资源和组件,也无需真的将业务模块发布成一个个可安装的NPM包,我们只是要借助微模块这种解耦模式来组织我们的代码。

不管你是不是真的需要独立开发和部署微模块,以微模块的模式来架构我们的应用,让资源高内聚、低耦合,让工程保持清晰的脉络结构,提高代码的可维护性和可复用性,这才是广义上微模块能给我们的启迪。

实例演示

为了证明该想法非纸上谈兵,可以看看我的开源项目:Elux-基于“微模块”和“模型驱动”的跨平台、跨框架『同构方案』,里面的所有工程模版都是基于“微模块”思想而架构的,其中Micro: 基于Webpack5的微前端 + 微模块方案这个模版是以上所谓的“狭义上的微模块”,也就是以独立开发和部署为目标的“微模块”,其它工程只是“广义上的微模块”。

安装方法:

npm create elux@latest 或 yarn create elux

@elux/cli-init@2.1.1
新建项目: /Users/hiisea/work/elux/aaa
totally [44P] templates are pulled from ...

? 请选择平台架构 
  CSR: 基于浏览器渲染的应用 [16P] 
  SSR: 基于服务器渲染 + 浏览器渲染的同构应用 [8P] 
❯ Micro: 基于Webpack5的微前端 + 微模块方案 [8P] 
  Taro: 基于Taro的跨平台应用(各类小程序) [8P] 
  RN: 基于ReactNative的原生APP(开发中...) [4P] 

抛砖引玉,你也许不会真的使用它,但它或许能给你带来新思路...