达观动态

达观愿与业内同行分享 助力各企业在大数据浪潮来临之际一起破浪前行

基于RequireJS的前端模块化设计

伴随着互联网的飞速发展,web中对于前端的要求越来越高,前端的代码的代码量、复杂度与日俱增,带来了诸如前端代码复用率低,难维护等问题。针对这些现有问题,达观科技采用了requirejs框架,用模块化的思想去解决这些问题。(达观数据 施列宇)

一、什么是模块化

模块化是一种将复杂系统拆分成一个个小的可管理的模块的方式。它可以把系统拆分为职责更单一,更独立的模块,也就是我们软件设计中常常提到的高内聚低耦合的模块。一般来说,前端模块化包含javascript,css以及template三个部分。

二、为什么要将前端内容模块化

简单来说,随着互联网的发展,前端的要求越来越高,导致了前端复杂度高,可维护性差,代码重复率高,命名冲突等问题。模块化正是解决这一问题的良药。

通过模块化,可以将代码中常用的内容封装起来,这样就做到了代码复用,便于维护。像前端中常用到的jQuery框架,就可以被视为一个公共模块,jQuery封装并简化了一系列常用的js操作,轻量级,功能强大,而且不会污染全局变量(所有的方法都只能通过jQuery对象调用),可以说是一个非常优秀的模块。并且,项目中往往也会存在一些不具备复用价值,但过于冗长的代码。我们也是鼓励将这些代码拆分多个相独立的模块,由此来提升可维护性。除此之外,模块化还可以带来不污染全局变量的效果。Web页面不复杂的情况下,我们可以任性的在全局环境下定义变量和函数,但在前端日益复杂化的今天,全局变量污染往往会带来命名冲突的问题,项目持续时间越久,累计的问题就会越多,维护的成本也就越高。

三、JS模块化的发展史

最早的js模块化是通过创建对象的方式来达到效果的。

1

通过js对象实现模块化效果

一个js对象即是一个模块,这样的好处是很直观易懂,并且的确解决了全局污染的问题。与此同时,这种方法的问题也比较突出,那就是js对象的封闭性并不好,重要信息很容易泄露,带来的安全问题难以解决。

针对js对象的安全性问题,js的模块化就选择使用闭包的方式来解决。也就是我们熟知的IIFE(immediately-invoked function expression)模式。

2

通过封闭性良好的匿名闭包,很好的对模块内容实行了封装。这种做法,已经可以很有效的解决了模块化过程中所遇到的问题,即使在现如今,也有很多流行的前端框架,通过这种方式实现模块化。

3

typescript中的模块编译后生成的javascript代码符合IIFE模式

IIFE模式是现代模块化工具的基石,其引入参数的过程,是实现现代js模块化依赖注入的基本方式。

随着js模块化发展的日益深远,js逐渐形成了模块定义的两大标准:通用模块定义 ( Common Module Definition) 以及异步模块定义 ( Asynchronous Module Definition)。

CMD是以seajs为代表的模块化标准,特点是依赖就近。

4

CMD模式下模块调用方式

进入模块时,模块自身并不知道依赖哪些模块,如图中所示,代码在执行到一定阶段后,通过require()方法引入需要用的模块,通过该函数返回的对象,调用模块的方法。这种做法的好处是,随时都可以调用外部模块而不用预定义,简单方便。但与此同时,在代码执行期间,需要不断的遍历工程多次来查找require对应模块的位置,这对代码的整体性能有所牺牲。

5

AMD是以requirejs为代表的模块化标准,其特点是推崇依赖前置

AMD模式下模块调用方式

如图,在进入模块时,模块已经知道了依赖关系,只有在所有的依赖项加载完成时,模块内部代码才会被执行。这种写法的优点是,性能相比CMD要好,在模块运行时已经知道需要加载哪些模块,不再需要对整段代码进行遍历查找依赖。

顺便一提,我们在requirejs的官方文档中也会发现和CMD用法相同的API,但requirejs官方还是推荐使用AMD的方式来建立依赖关系。

四、requirejs实战

作为初创公司,达观数据倾向于采用成熟的健壮的开发框架进行前端开发,完善的解决方案以及丰富的插件库可以帮助项目主体快速成型。目前,达观大数据前端部分采用的是angularjs + requirejs + gruntjs +bowerjs的框架来实现前端设计。其中就用到了requirejs作为前端代码依赖管理工具。

1.首先我们可以通过bower来在项目中安装requirejs , 不熟悉bower的同学可以参考https://github.com/bower/bower。

6

通过bower一键安装requirejs到项目中

2.编写requirejs的配置文件。通过requirejs提供的config方法,编写requirejs的配置文件。这里介绍一些比较常见的参数。

7

requirejs的config文件需自己定义

baseUrl: baseUrl用于改变依赖基本路径。一般而言,配置文件中模块定义路径是根据配置文件的位置计算出来的相对位置,这种情况下可以不用设置baseUrl。但如果项目需要迁移,那就需要改变模块存放的整体位置,这种情况下将每个模块的相对路径都进行更改是一件非常痛苦的事情。requirejs提供的baseUrl可以很好的解决这个问题,只需要重新设置baseUrl就可以不用更改配置文件中的每一条path。

paths: paths定义每个模块的路径。一个模块可以有多个路径,这样假使通过第一个路径加载失败,也可以从后面定义的路径中获得该模块。

shim: shim定义的是模块之间的依赖关系。实际项目中常常会遇到没有定义依赖关系的模块,这时我们需要通过shim参数手动定义每个模块的依赖项,使项目能够正常运行。

这样一个项目中的依赖部分就算完成了,之后在每个模块定义的时候,只要遵循requirejs模块定义语法,就可以实现web前端的模块化加载。

3.在html中加入requirejs的引用,并通过属性data-main加载项目中的入口脚本。

8

在html中引入requirejs

一般而言,项目引用的脚本文件需要代码管理工具进行合并、压缩、混淆操作。达观科技使用的是grunt脚本管理工具进行相关操作,这里使用到grunt提供的相应的插件grunt-contrib-requirejs来自动完成任务。 插件和grunt的一样,都是通过node.js提供的npm方法安装。安装完成时,可以在配置文件package.json查看到该插件。

9

package.json中存在grunt-contrib-copy的依赖项

确认该插件安装完成后,就可以在grunt的配置文件Gruntfile.js编写该插件的配置信息了。

10

grunt-contrib-requirejs在Gruntfile.js中的配置信息

其中几个核心参数的含义如下:

baseUrl: 类似于上一节中requirejs配置参数中的baseUrl,如果requirejs config中没有指明,需要写上requirejs config所在的路径。

mainConfigFile: 指向的requirejs的配置文件。

out: out指的最终输出的结果,值的注意的是该输出结果的路径不是相对于baseUrl的地址,而是相对于Gruntfile.js文件的地址。

更多配置的介绍可以查阅https://github.com/gruntjs/grunt-contrib-requirejs。

通过以上一系列的部署,requirejs已经可以在web项目中产生作用了。

五、结束语

本文主要阐述了我对于web前端模块化的一些理解。简要的分析了web前端的模块化的含义,必要性,并适当介绍了前端模块化的发展的一些历程,最终通过require实战的方式,向各位读者传述如何在web工程中加入模块化架构设计。模块化设计可以减少项目的复杂度,提升项目的可维护度,并对代码复用,减少代码量产生了一定的作用。在实际工作中,前端模块化的设计已不鲜见,目前前端可用于模块化的框架不下于10款,掌握了模块化设计的思路,对选择合适的模块化框架很有益处。在前端日新月异的今天,新框架总会源源不断的涌现,开发者往往苦恼于如何选择合适的框架,其实只要掌握了核心思想,正如本文所说的模块化思想,才可以很快的对比出各个框架的优缺点,才会在这些框架的选择中游刃有余。(达观数据 施列宇)