走进前端工程化
虽然前端工程化的概念兴起还没几年的时间,但是对于“工程化”这个词并不是一个新鲜词了,在其他软件开发的领域很早就已经有了高度的工程化,例如Web服务端开发。只不过那个时候,前端工程师并没有工程化的意识,也没有必要对前端进行工程化的操作,毕竟在那个时期,前端的开发工作只能算是整个项目开发过程中的“附属品”。那为什么这几年,前端工程化的概念又突然成为了热点话题了呢?要想讲好前端工程化其实并不容易,我就从下面几个方面入手,对前端工程化的概念做个简单的讲解,仅仅是个人理解,希望大家多多交流讨论。
1、前端为什么要工程化?
在回答前端工程化这个问题之前,我们应该先考虑另外一个问题:前端开发中会不会涉及到业务?再详细一点,就是前端工程师到底需不需要了解服务端的业务逻辑,再或者说,要不要将一部分服务端的业务逻辑放到前端来实现。这个问题没有标准的答案,实际上应该属于工程协作问题,就是谁该干什么的问题。
最早的前端开发就是实现页面,顶多再写写JS让页面可以有交互的特效。但是随着需求的增加,我们不仅要做Web应用,还要做App、小程序以及各种端。在这种需求日增的情况下,必须得考虑一种新的方式,优化前端的开发工作,例如,解决代码冗余,项目可维护性,提升版本迭代速度等等一系列的问题。前端工程化的概念也就是在这中情况下被提出了。
2、实现前端工程化的基础——前后端分离
实际上,现在的前端工程化应该还处于早期阶段,毕竟前端工程师这个岗位也才诞生没几年。在互联网发展的早期,Web应用很多情况下都是由服务端工程师一肩挑,前端开发顶多是写写HTML代码,实现页面的布局,然后再把写好的HTML静态文件交给后端工程师套模板,因为当时大部分的Web应用都是使用的服务端渲染技术,例如Java的JSP。
这种传统模式下的协作开发效率非常低,如果在页面测试中发现了一个bug,这个bug是由于一个ClassName的值少写了一个字母,你说这种情况到底是前端工程师的错,还是后端工程师套模板时的粗心大意呢?毕竟一个网站上有成千上万个DOM节点,谁也不能保证一行代码都不会写错。再假如,项目上线后发现网页的实际像素和设计稿上存在1像素的偏差,这个时候就需要由前端工程师重新设计一次HTML静态页面,然后再交给后端工程师继续套模板,等整个流程走完之后,你会发现一个更加严重的问题,整个项目中仅仅是1像素的偏差,就有可能要调动整个开发团队来处理这1像素的问题,极大的浪费了团队资源。
上面的问题也仅仅是传统开发问题中的冰山一角,面对这么多的问题,前后端分离开发也就应运而生了。前后端分离开发,为前端工程化的发展提供了生存的土壤。随着市场需求的不断变化,前端开发从传统的WebPage模式,转变到WebApp模式,Web产品形态的变化也不断推动着前端工程师的工作内容发生变化。在应对各种“变化”,前端工程师们也要设计出自己的前端开发“方法论”。
前端工程化的主要目标就是解放生产力、提高生产效率。通过制定一系列的规范,借助工具和框架解决前端开发以及前后端协作过程中的痛点和难度问题。
3、如何实施前端工程化?
明确前后端开发的分工,是实现前后端分离的第一步,也是后面实现前端各种优化方案的基础。前端工程师主要负责的内容包括:
- 静态资源和动态资源的处理;
- JavaScript实现前端业务逻辑;
- HTML模板文件的产出;
- 中间层Web服务,一般由Node.js实现;
- 前端单元测试;
- 前端项目部署;
其中,静态资源包括.js文件、.css文件以及各种格式的图片、媒体文件等,这些文件不依赖于服务器,只需要在浏览器里面解析就可以了;动态资源是指HTML的模板,如果项目不是由服务器完成渲染的SPA(单页面)应用,那我们就要考虑如何实现HTML模板的渲染了。前端项目部署是指静态资源文件在测试服务器上的部署,以及HTML模板文件在Node.js中间层服务器上的部署。
从项目开发的整体环节来说,实现前端工程化还需要熟练掌握下面几个方面:
(1)使用Webpack实现项目构建
构建,简单来说就是编译,前端开发的所有文件最终归属是要交给浏览器去解析、渲染,并将页面呈现给用户,构建就是将前端开发中的所有源代码转化为宿主浏览器可以执行的代码。前端构建产出的资源文件只有三种,HTML、CSS、JS文件。需要完成编译的内容有:
- 无法被浏览器直接识别的JS代码,包括ES6/7/8/9/10等符合ECMAScript规范的JS代码;
- 无法被浏览器直接识别的CSS代码,包括SASS/LESS等预编译的CSS语法;
- 无法被浏览器识别的HTML模板代码,包括jade、ejs、artTemplate、mustache等Node.js模板引擎;
项目构建其实就是为了弥补浏览器自身的缺陷和不足,是一种面向语言的编译过程。那么,除了针对语言本身之外,前端的构建还应该考虑到Web应用的性能优化。这些优化主要是为了减少HTTP请求,提升用户体验,包括:
- 依赖打包,将同步依赖的文件打包在一起,减少HTTP请求数量;
- 资源嵌入,例如将小于10kb的图片编译为base64格式嵌入文档,减少HTTP请求;
- 文件压缩,减少文件体积,缩短请求时间;
- 为文件加入hash指纹,以应对浏览器缓存策略;
- 将开发环境下的域名与静态资源文件路径修改为生产环境下的域名和路径; 文件名称的改变;
这里需要解释一下,前端的构建工具除了Webpack之外,还有其他的工具,例如Gulp、Grunt等,为什么这里只提到了Webpack?其实,Gulp、Grunt只能算是工作流管理工具,它们本身是不提供具体的功能,所有的构建、部署等功能都要由对应的插件来完成,使用Gulp和Grunt只是便于项目各个环节工作流程的控制。而且,这两款工具的话题热度和使用率远不及Webpack,Webpack虽然是近两年才崛起的构建工具,但目前依然成为了最流行的构建工具之一,Vue和React两大前端框架的Webpack Loader也是有官方或作者本人编写的。所以,我们在讲前端工程化构建时,推荐使用Webpack这款工具。
(2)使用Babel完成JavaScript编译
JavaScript可以说是前端最为核心的一门编程语言了,也就是我们常说的“JS”,JS本身是可以直接在浏览器中执行的,那么为什么还要使用Babel再编译一次呢?其实,这里要解释一下,在ECMAScript2015(简称ES6)正式发布以后,前端工程师关注的重心就由“JS”转向了“ES”,作为专业的前端工程师们应该都了解,JavaScript ≠ ECMAScript。
ECMAScript是一个标准,JavaScript是对ECMAScript标准实现的一个子集。宿主浏览器的API(BOM和DOM)再加上JavaScript,就组成了我们传统认知中的JavaScript。但是随着ECMAScript的版本不断迭代更新,带来的并不是开发的便利,由于浏览器对ECMAScript新规范的支持实现比较滞后,即使是目前最新版的Chrome浏览器也没有完全支持ECMAScript2015(ES6)的所有规范,而且ECMAScript2017都已经发布了,为了更好的让新的ES规范能够无缝衔接浏览器,Babel编译JavaScript语法的作用就突显出来了。
Babel的作用简单来说,就是将浏览器未实现的ECMAScript规范语法转化为可运行的低版本语法,例如将ES6的class转化为ES5的prototype实现。
(3)CSS预编译
CSS作为浏览器可以直接识别的样式语言,弥补了HTML原生样式的不足,对于早期互联网开发中,样式的要求并不复杂,仅需要少量的CSS代码即可。但是在如今追求用户极致体验的潮流下,CSS的开发要求不断提高,复杂的CSS开发变成一件非常繁琐和痛苦的事情。最主要的原因还是受限于浏览器的实现和CSS自身的弱编程能力。
CSS预编译器的工作原理是提供便捷的语法和特性供开发者编写源代码,随后经过专门的编译工具将源码转化为CSS语法。
(4)模块化开发
模块化开发和组件化开发是两个完全不同的概念,模块化属于架构层面的概念,前端工程化与模块化的关系就类似于组装车间与零件的关系。使用模块化开发,可以解决下面几个问题:
- 避免命名冲突;
- 便于依赖管理;
- 利于性能优化;
- 提高可维护性;
- 提高代码可复用性;
在ES6规范发布之前,前端模块化开发主要有三种规范,分别是:CommonJS、AMD、CMD。
CommonJS是一种只适用于JavaScript的静态模块化规范,适合Node.js开发,但并不适合浏览器环境;而AMD/CMD规范并不是完全一致的,但核心功能是统一的,两个规范都重点解决了浏览器对前端模块化的需求。
ES6 Module规范推出之后,前三者的模块化规范也逐渐退出前端的历史舞台。ES6 Module是语言层面的规范,与应用场景无关,所以一个不涉及运行环境API调用的模块可以在任何场景下运行。但是目前浏览器还没有完全支持这种规范,所以,要实现ES6 Module规范的话,还需要使用构建工具进行编译。
(5)组件化开发
前面提到了,组件化和模块化是两个完全不同的概念,模块化是文件层面上对代码和资源的拆分,组件化是设计层面上对UI的拆分。从UI中拆分出来的一个结构单元,成为UI组件,一个UI组件单元包含了HTML模板、CSS样式、JS逻辑。在页面的设计过程中,页面上的每一个元素都是组件,页面也是一个组件,只不过页面是一个大型组件,然后这个大型组件又由多个中小型组件拼装而成。中型组件还可以再拆分成小型组件,小型组件再拆分成DOM元素,DOM元素也属于浏览器自身的组件,是组件的基本单元。这种组件化开发就是前端开发的“分治思想”。
(6)开发环境的本地服务器与Mock服务
在前端工程化开发中,通过构建工具可以将代码进行编译,然后在浏览器中进行调试,但是在开发过程中源码的每次修改都需要执行一次构建,构建完成后才能在浏览器里运行,这对前端工程师来说无疑就是一种灾难。要完美的解决这个问题,可以使用本地服务器与构建工具结合,对源码进行监听并在修改之后触发动态构建,使用自动化构建的方式代替人工。这种动态构建是使用本地服务器解决开发层面上的问题。
Mock服务解决的是前后端协作开发的问题,前后端开发人员提前约定好规范,前端工程师通过本地服务器提供的Mock数据接口辅助前端逻辑的编写和功能模块的开发。如果项目中需要服务器端渲染(SSR),本地服务器还需要具备解析HTML模板的能力,同时Mock服务提供SSR所需的初始化数据。
前端工程师可以使用本地服务器提供的Mock数据接口,在后端人员开发的同时,进行前端逻辑的并行开发,等到后端真实接口开发完成后,将前端请求的地址从Mock服务迁移到服务器的生产环境即可。
(7)规范化约束
无论是服务器端开发还是前端工程化开发,规范化的约束都至关重要。开发人员在设计项目的整体架构时,为了考虑到项目的可扩展性、可维护性、高内聚性等因素,会对代码进行封装,使用配置化操作,为项目开发带来便利,这必然要求业务代码编程范式遵循既定的约束。这种约束虽然带来了开发上的便捷,但是在一定程度上制约了代码的可移植性。例如,在项目中使用了某个构建工具来解决项目需求,但是如果有一天项目需要更换另一个构建工具时,代码中原有的构建工具的配置会成为冗余代码,而且不能保证这类配置不会对新构建工具产生冲突。即便是没有产生冲突,对代码的性能优化也是会带来一定负面影响的。工程化方案作为一种服务,应该尽量降低对项目产生的负面影响。这是制定编程范式约束规范时最重要的考虑因素。
(8)项目部署流程化
站在前端开发的范畴来说,项目部署是指前端开发人员将构建产出的代码包部署到测试服务器的过程,而并非是将测试完成的代码发布到生产环境的过程。在部署过程中,要考虑目标服务器、路径信息是否与项目一一对应,并且可供负责部署到生产环境的开发人员进行配置,部署的操作流程应尽量简单。
在部署流程中,使用命令行取代工具执行(例如FTP)本地部署,能够极大的提高部署的速度和效率,但是这只是初级阶段的部署流程。考虑团队协作和安全方面的因素,最佳的方式应该是搭建一个可供严格审查、队列控制、操作简化的部署平台,并且有专人负责掌握流程进度。虽然这种搭建部署平台的方式在一定程度上减缓了整体的部署速度,但是加强了团队协作和安全保障。
4、前端工程化未来的发展如何?
目前,Web应用开发的分工模式还处于探索期,而且从最近流行的“大前端”的趋势来看,前端工程师的发展或许会突破Web领域,向多端领域发展,例如React Native、Weex、Electron、小程序等。从诞生之初“切图仔”到大前端概念,前端工程师的定位和负责的技术范畴一直在变化,但是前端工程师产出的服务对象永远是用户。在Node.js之前,浏览器是前端工程师生存的“一亩三分地”,Node.js的出现打破了这个局面,以致出现了“大前端”的发展趋势。
Node.js中间层+浏览器是目前实现“大前端”的基本模式,前端工程师掌握着与用户相关的所有资源,能够全面地掌握开发进度以及实现更合理的前后端分离方案。这种模式让前端工程师突破浏览器的瓶颈,迈向Web应用层面,这也是未来前端发展的主流趋势。
无论是专注于浏览器,还是兼顾了Node.js中间层,前端工程师的利剑始终指向的是面向浏览器的Web领域,前端工程化所提供的是一种服务,服务对象是不断参与到项目迭代过程中的前端开发人员,服务的内容涉及到开发、构建、部署等各个环节。
前端工程师在未来的定位必然会发生变化,但是前端工程化唯一不变的原则是始终以前端开发为中心,前端工程化没有统一的行业标准,也没有固定的形态,更没有最合理的方案,只要前端工程师的定位还在不断的变化,前端工程化的进程将会一直持续下去。