在现代前端工程开发中,自动化构建已经成为一个必不可少的部分,本节我们就来讨论一下前端自动化构建方面的知识。

  目前的构建工具多种多样,设计的思路也略有不同,但是整体的实现原理却是基本一致的。这里我们仍以讲述构建工具和流程原理性的内容为主,目的是希望你不仅掌握如何使用现在的构建工具,同时也能了解构建工具自动完成构建的过程。

  提到前端自动化构建工具,我们可以追溯到软件开发时代的IDE(Integrated Development Environment,集成开发环境)。IDE在软件编译运行阶段将软件所需要的代码、资源、图片等打包成一个可以独立运行的软件安装包,然后在不同平台上安装运行。前端开发中是没有这样的IDE的,因为前端代码不需要软件编译。举一个简单的例子,我们在一个页面中使用了多个背景图片,但是想把这几个背景图片请求合成一个图片(俗称雪碧图),以前我们可能会手动把这些图片放在一个大背景图片中,然后通过元素的背景图偏移量来实现多个元素对它的引用。后来,页面又添加了一个图片,我们就在这个原有合成的大图片中新添加了一个图片。很多人应该有过这样的经历,这样很麻烦,于是我们希望能有一个像软件IDE这样的工具,对代码进行分析,把引用的各种资源打包统一处理,自动输出成为理想的结构。合并多个背景图片只是其中一个场景,这一类问题就是前端构建工具需要解决的,某种意义上,前端构建工具很像软件开发IDE的编译打包处理模块。

  • 5.3.1 自动化构建的目的

  前端构建工具的作用可以认为是对源项目文件或资源进行文件级处理,将文件或资源处理成需要的最佳输出结构和形式。在处理过程中,我们可以对文件进行模块化引入、依赖分析、资源合并、压缩优化、文件嵌入、路径替换、生成资源包等多种操作,这样就能完成很多原本需要手动完成的事情,极大地提高开发效率。

  • 5.3.2 自动化构建原理

  主流的构建工具虽然种类较多,但原理相通。下面我们来看看目前主流的构建工具的实现原理和过程。首先要明确的是,自动化构建是基于项目代码文件级的分析处理,下面以一个通用构建工具实现的文件处理流程为例向大家介绍。

  如图5-3所示,构建的流程主要分成7个基本步骤(不同的构建工具各有差异,但基本原理是类似的):读取入口文件→分析模块引用→按照引用加载模块→模块文件编译处理→模块文件合并→文件优化处理→写入生成目录。

图5-3 构建原理流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <!--style-->
</head>
<body>
    <mod-A></mod-A>
    <mod-B></mod-B>
    <script src="main.js"></script>
    <!--script-->
</body>
</html>

  其中模块A和模块B组件对应的目录文件如下。

1
2
3
4
index.es
index.html
index.scss
img/

  以上面这一个入口文件的index.html源文件为例。这个入口页面文件index.html中含有A和B两个模块,模块A、B组件遵循统一的模块规范:每个组件都包含JavaScript、SCSS、HTML文件和img目录。我们希望构建后将模块A和模块B的内容全部引用到页面上,CSS和JavaScript的脚本资源也经过压缩编译处理,而且最后打包后的资源引用达到最优预上线的状态。根据上面的构建处理流程,我们将构建任务分成7个阶段(分别命名为build1~build7),具体如下。

  1、build1读取入口文件阶段。构建工具会读取index.html源文件到一个字符串Buffer(或者文件对象)中。

  2、build2分析模块引用阶段。根据特定的标识(例如mod-开头的自定义标签)分析出页面字符串Buffer中含有的两个模块A和模块B的引用。

  3、build3按照引用加载模块文件阶段。进入模块A、B目录中读取模块A和模块B包含的HTML、SCSS、JavaScript文件。

  4、build4模块文件编译阶段。进行对应的脚本转译(转译的工具都是通过插件完成的)和依赖分析,例如将ECMAScript 6+脚本转译成ECMAScript 5脚本模块引入、将SCSS文件预处理为CSS等。该阶段完成后,构建工具将生成编译后的代码字符串Buffer。

  5、build5模块文件合并阶段。将所有JavaScript、CSS代码字符串Buffer写入一个新的字符串Buffer中,将模块A、B的HTML字符串Buffer插入index.html的字符串Buffer中,生成线上域名路径 http://www.domain.com/dist/css/main.css 和 http://www.domain.com/dist/js/main.js ,将路径名插入到index.html字符串Buffer中代替注释

1
<!--style-->
1
<!--script-->
的位置,表示CSS和JavaScript脚本引用的最终路径。

  6、build6文件优化处理阶段。将合并后的JavaScript、CSS代码字符串Buffer进行优化,例如去注释、压缩等。或将CSS引用的多个单张背景图合成一张大图,这个阶段的压缩合并处理也都是可以通过不同插件来优化完成的。

  7、build7阶段。将优化完成的字符串Buffer写入到配置好的输出目录中,将文件命名为main.js和main.css,对HTML字符串Buffer进行去除注释等优化处理,最后写入到输出目录中。至此,整个构建流程完成。分析处理后入口HTML文件可能如下,而引用的CSS和JavaScript文件都是转译优化后的代码文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">[前面两行未对齐]
    <title>Document</title>
    <link rel="stylesheet" href="http://www.domain.com/dist/css/main.css">
</head>
<body>
    <div class="mod-A">
        ...
    </div>
    <div class="mod-B">
        ...
    </div>
    <script src="http://www.domain.com/dist/js/main.js"></script>
</body>
</html>

  处理的文件以字符串Buffer或文件对象的形式存在于整个过程中,最终生成文件的规则一般是按照用户自定义的配置来完成的。不同阶段的处理流程一般可以通过第三方插件来实现,例如上面提到模块化封装、依赖合并、压缩优化、文件嵌入、路径替换、生成资源包等都可以借助插件来做,必要的时候也会自己编写插件。

  从某种意义上来说,构建流程中代码资源文件就像是工厂生产线的产品一样,经过多个加工机器进行处理加工,最后打包封装生成想要的产品。早期也有通过不断读写文件处理方式实现的构建工具,即每经过一次处理就将文件写到磁盘的某个临时目录下,处理下一个插件时再从这个目录中读取出来,但这种方式由于频繁地对磁盘进行读写,因此构建速度较慢,目前已经很少使用了。

  • 5.3.3 构建工具设计的问题

  通过上面的分析,我们对构建工具的流程和原理有了较直接的认识,并可以使用基础的工具搭建自己理想的构建环境。那么在现代前端实际开发中通常希望构建工具帮我们处理哪些问题呢?

1、模块分析引入

  目前前端开发模式基本已经进入组件化开发阶段,组件化实现的方式也比较多,所以首先希望构建工具能自动帮我们完成对组件模块的引入和分析,例如自动分析require或import的模块进行合并打包。这样在开发过程中就可以专注于组件本身,而不用关心组件模块引用最终是怎么被构建打包的。 重点了解一下JavaScript组件模块文件的依赖分析过程,以require的引用方式为例。

  1.从入口模块开始分析require函数调用依赖。

  2.根据依赖生成JavaScript AST(Abstract Syntax Tree,抽象语法树,是将JavaScript代码映射成一个树形结构的JSON对象树)。

  3.根据AST找到每个模块的模块名。

  4.得到每个模块的依赖关系,生成一个依赖字典。

  5.根据模块化引用机制包装每个模块,传入依赖字典以及import或require函数,生成执行的JavaScript代码。

2、模块化规范支持

  目前,前端开发中使用ECMAScript 6+标准实现的项目居多,但大多数情况都是通过构建工具插件将其转译成ECMAScript 5语法代码在浏览器中运行的。这里涉及多种JavaScript模块化规范之间处理转换的问题,好的构建工具应该尽可能支持较多种类模块化规范进行打包,这样我们就不用考虑模块化规范不统一的问题了。

3、CSS编译、自动合并图片

  CSS的预处理、图片引用的分析、内联图片资源、自动合并雪碧图等功能在一个理想的构建工具里都是必不可少的。如果构建工具能自动移除解析后重复冗余的CSS规则就更完美了。

4、HTML、JavaScript、CSS资源压缩优化

  为了尽可能让线上资源的体积最小化,发布到线上的文件通常需要经过构建工具的压缩优化处理,例如HTML内注释和多余空格的压缩、JavaScript的uglify操作、CSS的压缩等,这些都是我们希望构建工具自动完成的。

5、HTML路径分析替换

  在开发过程中,为了方便开发调试处理,我们通常使用相对路径来进行文件的引用。但是项目开发完成上线后,静态资源会发布到绝对路径甚至不同的域名CDN路径上,这就需要在上线前对文件路径进行替换,所以我们希望构建工具也能自动完成这一工作,即提供将文件相对路径自动替换成绝对路径或线上CDN路径的能力。

6、区分开发和上线目录环境

  为了方便开发和调试,希望浏览器在使用构建工具开发时能加载未压缩处理的代码文件,但是准备上线前需要对文件进行压缩优化处理,这就要求构建工具能区分开发和线上环境的不同资源。我们常常希望能够在配置构建任务时指明开发和发布资源目录来满足不同场景下的需要。

7、异步文件打包方案

  异步文件加载的场景很常见,尤其是在移动端,我们通常将首屏的内容优先展示,然后滚动异步加载后面的内容。为了保证首屏加载的资源最小,非首屏的内容都希望通过JavaScript来异步渲染,这就需要构建工具能将非首屏的组件打包成异步资源,以按需或异步的方式加载。同时我们又不希望在开发过程中太关注异步组件和同步组件的区别,所以通常将异步组件放在异步的目录里进行单独打包或加入特殊的标识,不过也需要构建工具支持才行。

8、文件目录白名单设置

  为了加快项目代码构建的处理速度,建议提供一些配置绕过不需要处理的文件目录,否则文件目录较多的情况下构建处理速度就会比较慢。

  除了这些,我们还可以配置构建工具来完成更多的事情,具体可以根据特殊需要来选择使用,所以通常构建工具也应该是可以扩展的,通过配置更多的插件来共同完成项目自动化处理中的任务。

  这一节主要讲解了构建工具的工作流程和原理,介绍得相对比较理论化,希望大家结合自己使用的构建工具去理解消化。对于构建工具的选择,读者可以比较当下主流的构建工具并选择一个合适的使用。构建工具仍在不断更新变化,但无论如何,构建的基本原理是确定的,它的最终目的仍是自动化完成一些事情,提高开发的效率。