春苗项目小结之模块化与组件化

这一篇想来整理总结一下春苗项目使用的前端框架及其模块化的思想。这次项目中我们使用了yog框架,这是一套基于kraken-js,并集成了fis静态资源管理等功能的node框架。

##框架简述

我写这篇文章,其实不是为了介绍框架,而是想写写框架使用的体会。加上我本身也只是知其表而不知其里,所以,想更深入地了解yog或是fis的同学,请移步文章末的参考资料。以春苗PC为例,首先我们来看一看目录结构:

春苗PC目录结构

从上图中可以看到,站点根目录下划分了多个子目录:base目录是yog基础运行环境,其余目录都是对应各个子系统,也叫做模块,如group、home。每个模块都是一个完整的app,拥有client和server目录,client目录存放前端代码,server目录存放后端代码。当我们要运行group模块,只需将group app发布到与其同级的base目录下,启动node服务,便可在本地监听端口查看运行效果。

yog框架提供了前后端一体App的拆分能力,同时我们可以将多个业务子系统,部署到同一个yog项目中运行。给我们带来的好处就是,方便的模块化拆分,抽取通用代码,降低模块耦合。

##模块

我们进一步看子系统的部分,这里把项目拆分成了common,group,home,topic,user,wenku-api共六个模块。然而,这些模块又可以分成两类:common模块业务模块。common模块即通用子系统,存放全站的通用代码。其他模块均为对应功能的业务模块。各业务模块仅对common模块有依赖,业务模块之间互不依赖。每个模块内部,都有大致相同的目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
├─client                 # 前端代码
│ ├─page # 页面
│ ├─static # 非组件静态资源
│ │ ├─css
│ │ └─js
│ └─widget # 组件
│ ├─tpl
│ ├─css
│ └─js
├─server # 后端代码
│ ├─action # MVC中的路由动作,处理页面请求
│ ├─model # 存放一些数据层代码,如后端API请求
│ ├─lib # 存放通用库
│ └─router.js # AppRouter路由,用于处理自动路由无法满足的需求
└─fis-conf.js # FIS编译配置

模块内部结构可分成三部分,client,server,以及fis配置。一般的业务模块都完整的拥有如上述的目录结构。

首先来看client前端部分,又细分page,static,widget三个目录:page目录存放页面级模版文件,如layout布局文件等;static目录存放相应的页面级别的静态资源;widget目录存放组成页面的各个组件。

再看server后端部分,这里分为3个层次。router.js用来配置路由,将页面请求转发至action层。action层处理页面请求,解析请求参数,调用model层方法获取数据,然后渲染页面。model层即编写方法请求后端的数据接口。

了解了模块内部的目录规范,以及模块之间的关系,那么问题来了,我们在拆分组件的时候,代码应该如何存放呢?举一个栗子,我们在考虑一个组件该如何放置的时候,可以思考以下几个方面:一,如果是全站通用的组件,那么则放在common的widget目录下,如果仅限于本功能模块使用,那么则放在本模块下的widget目录;二,如果该组件是js驱动的组件,那么放在js目录下,相应的,其他类型的组件则放在其他目录下。

##组件

何为组件,我找到了fis设计之初对其的定义:

组件(widget):能独立提供交互的页面元素区域,俗称页面碎片。一般多个组件构成一个完整的页面。

下面主要着眼前端代码的部分,让我们更深入一层,来到widget目录,这里就是我们存放组件的地方。

1
2
3
4
5
6
7
8
9
10
└─client
└─widget
├─tpl # tpl组件
│ └─header
│ ├─header.tpl
│ └─header.less
├─js # js组件
│ └─widget.js
└─css # css组件
└─button.less

我们看到,所有的组件都可以按组织方式划分为三类。css组件最为简单,一般只涉及css文件,定义可复用的class等;js组件是使用js的方式初始化,一般会提供接口给页面调用;tpl组件则最复杂,它可以封装js组件和css组件,使用模版本身的方式调用,我们可以给tpl组件塞数据。

上文已经提到了widget存放的一些考虑因素。在实际的项目中,我经常会被一个问题困扰,就是“这个组件到底是否需要抽离呢?”。那么这里提出这样一个问题:如何考虑组件是否可以复用或是复用范围呢?有些通用组件较容易判断,如全站统一的页头页尾,那一定是放在common模块下。春苗项目是一个全新的项目,它处于设计初期的阶段。对于站点早期设计时就能明确区分的组件,我们可以直接存放在common。但有些无法确定复用需求的组件,我的经验是先由业务模块维护,当后期的设计需求改变,需要复用时,再进行重构,可以实施以下两种方案:手工copy代码;或抽离到通用模块中,并修改原来的调用路径。

另外还有一种情况是,对于早期设计时,有大于两个的业务模块共享一个通用组件,但随着设计的改变,其中一个业务模块无法再使用公共组件代码时,我们也可以有两种方式处理:重新为该模块单独编写组件;或利用传参判断的方式,在组件内部分别执行不同的代码。当然,具体采用哪一种方案,跟具体的设计需求是分不开的,我们同时要考虑到,代码松耦合,组件该尽量简单。

##分层设计

我们回过去看项目前端整个目录结构,不难发现这样的层次模型:从高到低依次为,站点 > 子系统 > 页面 > 组件。组件是组成系统的基本单位,整个系统就像搭积木一样一层一层搭起来了。我整理了这张思维导图来帮助梳理思路:

前端分层示意

这张图的描述并不很完整,但从中我们可以大致窥见整个框架的分层思想。组件化就像一个链,从下至上串起整个系统。同时我们可以发现,除了纵向的分层,还存在一个横向层次:通用非通用。在纵向四个层次上,每一层都有自己的“通用”形式。

分层下的通用

因此,我们可以对通用部分集中维护,定期升级和改进接口;对非通用部分,按需求开发,且不同功能模块互不影响。这种一个通用子系统+多个业务子系统的模块化思想,既将不同功能代码解耦,又解决了资源依赖的问题。在这个分层结构下,一个前端站点的三类基础代码:HTML,CSS,JS都得到了较好的组织,分级存放,分类维护。

这篇文章是一次学习总结,在写的过程中,深感一些术语理解和表述的吃力,如有错误还请指正~

参考资料:
fis
yog2
fis-plus