今天开始我们进入本专栏的第二模块:构建效率。本模块主要探讨如何优化构建细节。在这之前,我想先聊一聊前端开发历程中构建工具的演进。通过这节课,让你对构建工具的诞生发展及它们各自解决的问题有一个直观了解。
前端开发语言发展历程回顾: 1991 年,Tim Berners-Lee 发布了第一份 HTML 标准。 1994 年,Håkon Wium Lie提出了 CSS 的概念,两年后 HTML 4.0 中首次支持了 CSS。IE3 也成了第一个支持 CSS 的商用浏览器。 1995 年,Brendan Eich 发明了名为*Mocha*的浏览器脚本语言,并在随后命名为了*LiveScript,几个月后又重新命名为我们现在所熟知的JavaScript*。 1997 年,随着 IE4 的发布,*DHTML*(Dynaimic HTML)的概念被提出,指代用于创建包含交互和动画效果页面的一系列技术结合,包括 HTML、CSS、JS、DOM 等。 2004 年,*Ajax*技术随着 Google 在 Gmail 中的广泛运用而逐渐被更多的 Web 开发者所使用(尽管最早的 Ajax 可以追溯到 1999 年 IE5 时代的 XMLHTTP ActiveX)。
前端主要的开发语言:HTML、CSS 和 JS 都诞生在 20 世纪 90 年代。2000 年前,网页呈现和交互都较为简单,开发框架和处理工具都在孕育中,即便是早期前端开发者所熟知的 jQuery 框架,也远未出现。
文件压缩与合并工具发展历程回顾: 2001 年,Douglas Crockfold 发布了 JSMin工具,用于去除 JS 代码中的注释和空格。 2004 年,Dave Shea 在他的文章中参考早期游戏开发中使用的 Sprite 图方案,提出了 CSS Sprite 的概念,即将多张小图合成为一张大图,然后通过 CSS 控制在不同元素中使用图片的局部区域,从而减少网络请求,提升网页性能。 2006 年,Yahoo 发布了 YUI 库,其中包含了基于 Java 的代码压缩工具 YUI Compressor。 2009 年,Google 发布了 Closure Toolkit,其中包含的 Closure Compiler 提供了比 YUI Compressor更多的代码优化功能,并支持 Source Map 和多文件合并。 2010 年,Mihai Bazon 发布了压缩工具UglifyJS,并在 2012 年的升级版本 UglifyJS2 中增加了对 Source Map 的支持。
2000 年以后的 10 年里,随着更多 CSS 与 JS 框架的诞生,代码优化的工具也应运而生。为了获得更好的访问体验,开发者需要更少的资源连接数与更小的文件体积,这就分别对应了两类工具:文件压缩工具和合并工具。
这个时期的工具有一些共同点,例如都是基于其他语言(例如 C 和 Java)实现的,因此需要安装对应的依赖环境,这些工具也都需要通过命令行执行。对于用户来说,随着开发项目的增多,容易造成效率的低下。这类问题直到下一个十年,随着 NodeJS 的发布才逐渐改变。
包管理工具发展历程回顾: 2009 年,Ryan Dahl 发布了第一个版本的Node.js。 2010 年,Node.js 核心开发人员Isaac Z. Schlueter 编写了对应环境的包管理工具npm。 2012 年,Twitter 发布了名为Bower的前端依赖包管理工具。 2016 年,Facebook 发布了 npm registry 的兼容客户端Yarn。
人们可以把代码包发布到 npm 中
2009 年 NodeJS 发布,这对前端开发领域产生了深远的影响。一方面,许多原先基于其他语言开发的工具包如今可以通过 NodeJS 来实现,并通过 npm(Node Package Manager,即 node 包管理器)来安装使用。大量的开发者开始将自己开发的工具包发布到 npm registry 上,包的数量在 2012 年初就达到了 6,000 个,而到 2014 年,数字已经上升到了 50,000 个。
另一方面,安装到本地的依赖包在前端项目中如何引用开始受到关注。Twitter 发布的 Bower 旨在解决前端项目中的依赖安装和引用问题,其中一个问题是,在 npm 安装依赖的过程中会引入大量的子包,在早期版本(npm 3 之前)中会产生相同依赖包的大量重复拷贝,这在前端项目中会导致无谓的请求流量损耗。而 Bower 在安装依赖时则可以避免这类问题。然而随着更多模块化打包工具的诞生,它的优势逐渐被其他工具所取代。直到 2017 年,Bower 官方宣布废弃这个项目。
著名的 node_modules hell(源自 reddit 用户 xaxaxa_trick)
npm 的另一个饱受诟病的问题是本地依赖管理算法的复杂性以及随之而来的性能、冗余、冲突等问题。而 2016 年发布的 Yarn 正是为解决这些问题而诞生的。和 npm 相比,Yarn 的主要优点有:
任务式构建工具发展历程回顾: 2012 年,Ben Alman 发布了基于任务的构建工具 Grunt。 2013 年,Eric Schoffstall 发布了流式的构建工具 Gulp。
随着 NodeJS 和 npm 的发布,大量的前端工具包发布到 npm 仓库,开发者通过简单的命令行指令就可以方便地下载和使用,前端的工程化也在这一时期开始蓬勃发展。其中一种趋势就是,使用自动化的任务式构建工具来替代手工执行各种处理命令。
Grunt 和 Gulp 这两种任务式的构建工具的基本组成包括:核心的处理工具(grunt-cli/gulp-cli)、配置文件(Gruntfile/Gulpfile),以及一系列常用的任务插件(Clean、Watch、Copy、Concat、Uglify、CssMin、Spritesmith……)。在项目里通过编写配置文件,就可以定义工作流程中的各种自动化构建处理,例如在发生变更时,通过 Watch 插件监控文件,从而自动执行代码的检查与压缩等。
这两种工具的差异性主要体现在:
任务式的构建工具,虽然解决了开发流程中自动化执行预设任务的问题,但不能解决项目中代码如何组织成不同功能的代码包、不同代码之间如何相互依赖等问题。而解决这类问题的方式就是:模块化。
模块化发展历程回顾: 2009 年,Kevin Dangoor 发起了 ServerJS 项目,后更名为 CommonJS,其目标是指定浏览器外的 JS API 规范(例如 FS、Stream、Buffer 等)以及模块规范 Modules/1.0。这一规范也成为同年发布的 NodeJS 中的模块定义的参照规范。 2011 年,RequireJS 1.0 版本发布,作为客户端的模块加载器,提供了异步加载模块的能力。作者在之后提交了 CommonJS 的 Module/Transfer/C 提案,这一提案最终发展为了独立的 AMD 规范。 2013 年,面向浏览器端模块的打包工具Browserify发布。 2014 年,跨平台的前后端兼容的模块化定义语法 UMD发布。 2014 年,Sebastian McKenzie 发布了将 ES6 语法转换为 ES5 语法的工具 6to5,并在之后更名为Babel。 2014 年,Guy Bedford 对外发布了 SystemJS 和 jspm 工具,用于简化模块加载和处理包管理。 2014 年,打包工具 Webpack 发布了第一个稳定版本。 2015 年,ES6(ES2015)规范正式发布,第一次从语言规范上定义了 JS 中的模块化。 2015 年,Rich Harris 发布的 Rollup 项目,基于 ES6 模块化,提供了 Tree Shaking 的功能。
下面我们介绍一些模块化的构建工具。
这节课中,我们讨论了前端构建工具的演进:从早期的单独功能的压缩与合并工具,到 NodeJS 与包管理工具的诞生,随之而来的是任务式构建工具的发展,以及模块化概念与工具的不断探索。我们现在使用的构建工具,一方面立足于这些过去积累下来的前人的经验与智慧,另一方面也顺应着不断发展的前端开发需求。
本节课的课后讨论题是:最后出现的模块化的构建工具为什么能取代任务式的构建工具呢?以 Webpack 为例来聊聊看吧。
下节课,我们深入到 Webpack 的体系中,来聊一聊 Webpack 的完整工作流程。