APP开发中的工程多模块组织

light使用跨平台的技术解决移动原生应用的开发方案,以平台无关的方式组织PP开发,light可以在同一个工程当中处理APP配置与业务代码的开发,可以在项目期初迅速的完成演示性APP的构建和组织。

但是,一旦APP中业务实现代码量在需求实现的过程中膨胀,一开始采取的“快速构建”的思路就会碰壁,会出现一些意想不到的问题。随着代码量的扩张,单个项目的视图数量突破100,在现阶段支持的情况来看,部分客户项目的视图数量会超过200。

由此而来,就带来了工程管理的挑战,如何维护一个视图量超过200的移动端应用项目?

现阶段APP工程组织的弊端

成熟的架构都存在有完善一致的模块组织方案,比如java中的jar包,又比如python中的modules。在前端资源管理里面也存在一种松散的包组织方式,npm规范下的NodePackage

NodePackage适合与对工程依赖的管理,这里的模块实为技术定义的依赖包,而非业务定义的模块包。我们希望提供一种,类maven对于java的工程组织规范,也是一种在APP应用开发中业务模块组织的规范。

在详细描述此方案之前,需要先描述“完整构建”下的模块组织方案到底有哪些弊端?

编译耗时

lighting工程对于前端资源的编译借助于webpack的依赖树的构建和处理,在资源处理的过程中webpack负责梳理以应用入口为编译入口的情况下的资源依赖树,并以此为基础编译处理关联的资源,如less和vue。

其中主要涉及两个构建流程的耗时,依赖树构建和资源转换。

依赖树的构建和整个工程中所依赖的资源的数量相关,包括所有的样式/模板与脚本,还包括图片和字体。而且当工程内资源数量上升时,依赖树的构建耗时是加速上升的,这就带来了“编译耗时”的问题。

资源转换中最耗时的部分来自于es6向es5的转换,和实际处理的代码量成正相关。

总上,工程中代码的数量带来了编译构建的大量的资源消耗并由此导致了“编译耗时”过长的问题。

代码膨胀

代码量膨胀是开发人员不得不面对的又一个问题,尤其是团队协作开发的情况下。

在不做代码隔离的情况下,相当与每个开发人员都在独自面对200+的视图业务逻辑,更有甚者,单个视图的逻辑代码行数达到了1000+,这是一种什么体验?

需求响应困境

代码膨胀带来的最直接的影响就是,业务/代码关联性被打破了。需求的迭代速度,都是以整个项目业务的迭代速度为衡量的。

发布部署困境

移动应用的维护有着复杂的流程,尤其是外部流程依赖的项目比如iOS应用的上架审核。

单一工程构建带来的问题就是单一工程的发布部署,在重新集成APP的情况下,需要完整经过发布->上架->用户安装->用户反馈的流程,周期比较长。在不需要重新集成APP的情况下(如使用远程资源),单一工程发布会增加用户设备获取完整新版本的难度,至少多余的流量消耗是应该面对的问题。

模块化组织方式

移动端应用应该如何组织自己的多业务模块工程?

light工程的目录规范中增加了modules目录作为子模块存放的路径,此目录下的直接子目录为工程的业务模块,每个业务模块都为独立的light工程,工程的可选类型为H5和JSN两种技术。

modules目录所在的工程应用APP类型的工程,是整个应用的构建入口和组织者,此工程定义了对子模块的依赖,主要体现在native/config.js的配置当中。

由此,完整的移动端应用的目录规范需要调整为如下图所示:

就开发实现而言,子模块是不应该包含子模块的,也就是说,不允许出现嵌套模块。

模块化拆分方式

业务模块划分的依据

light工程对应用的组织是以视图(view)为单位的,所有的视图都在应用构建之初定义在了工程的入口文件-index.html当中。

视图是应用中用户可视区域的完整内容,同一个业务流程可能有多个视图的串联完成。同一个视图应该尽量不涉及多条业务流程。

模块就是指:包含了完成应用关联业务流程的一组视图的前端工程。模块划分的依据也就是:检查同一个模块下的每一个视图,是否都是为同一条业务流程服务的,是则可以联合处理,否则需要剔除。

模块和父子视图的概念会有重叠的地方,同一个父视图下面大多是同一条业务流程下的环节可以按需拆分为一个子模块或者多个子模块。

公共依赖的处理方式

在“完整构建”单个工程的情况下,不存在公共依赖的说法,不同的视图可以复用工程内的所有资源。

在多模块的场景下,要求每个模块间工程资源相互隔离各自为政是不切实际的,这个时候就需要一种统一的依赖查找的机制方面不同的子模块依赖和使用相同的资源,如UI组件,工具库,接口请求封装等。

这个时候就应该使用npm包来应对类似的问题。

各个子模块应该尽量少的依赖外部模块,仅在单个模块中使用的依赖应该隶属与此模块进行管理。当存在多个子模块依赖同一份资源时,应该将被依赖资源发布或者整理为npm包,各模块以使用外部资源的方式使用公共的依赖。

模块间跳转

各个子模块之间并不是绝对的相互隔离,可以发生逻辑上的跳转关系和数据传递。

1
2
3
4
5
Light.navigate("@module1/index.html#/buy/stock",{
id:1
},{
title:'购买股票'
})

如图所示的代码可以跳转到module1下的web容器打开的index.html下的buy/stock,并且传递给此视图的id为1的参数。

模块间数据共享

运行在同一个APP环境下的数据存储是共享的,包括两种容器类型jsn/h5,都是使用了统一分运行时的存储,数据可以方便的在不同的模块,甚至不同类型的模块之前共享数据。

1
2
3
4
5
6
LightSDK.native.readData("data",function(data) {

});
LightSDK.native.writeData("data",{hello:'world'},function() {

});

编译/构建与集成/打包

模块分割带来的显而易见的好处是,不同的模块可以独立的维护了。任何交付流程的环节都不需要应付完整的APP业务内容了。

在业务开发阶段,每个开发人员都是看到完整内容的一部分,更加方便聚焦和实现。

开发完成后,对APP内容进行配置,将子模块配置进应用的菜单并且将子模块编译构建时登记为离线包,就可以获取对单个模块后续更新的能力。

总结

本文描述了配合light移动应用离线包机制的前端工程模块化方案,通过对前端资源的拆解分割,可以缩减整个前端流程的开销,提高应用开发交付的效率。