### 一、什么是前端工程化
前端工程化就是使用软件工程的技术和方法对前端开发流程的标准化、工具化、规范化、简单化和自动化。 其主要目的是为了提高开发过程中的开发效率,减少不必要的重复开发时间。
一切以提高效率、降低成本、质量保证为目的的手段都属于工程化。
- 提高效率(通过模块/组件化复用各项能力,以及自动化工具提高效 率)
- 保障质量(规范化避免犯错,通过引入准入检测、监控、自动化测试 等手段保障研发和运维期间的质量)
- 降低成本(提高质量、保障质量,以及自动化带来的开发难度的降低 变相的就降低的整个软件开发的成本)
一切重复的工作都应该被自动化
前端工程化贯穿整个前端项目各个阶段,包括代码规范、流程规范、分支管理、程序开发、前后端联调、自动化测试、应用构建、系统部署和监控、运维等。
### 二、为什么要使用前端工程化
随着近些年来前端技术的不断发展,越来越多复杂的业务放在了前端,前端不再是简单的 HTML + CSS + JavaScript 组合。 业务的复杂性导致前端代码量增加,因此前端代码的可靠性、可维护性、可拓展性以及前端应用的性能和开发效率成为重要问题。前端工程化应运而生,旨在解决这些问题。
前端工程化的优势包括:
- 提高团队合作效率和降低沟通成本。
- 实现更好的协同,减少开发和测试人员的重复劳动。
- 提高代码质量和可维护性,降低发布的常见问题
### 三、手动部署vs前端工程化部署
在没有引入工程化部署之前,我们仔细想一想,如果没有这些工具,我们在上线前需要做哪些事情?
- 解决模块化的问题,由于部分浏览器并不支持ES Module的方式,我们需要将我们的源代码转换成浏览器认识的格式
- 解决CSS、JS代码的浏览器兼容性问题
- 对HTML代码、CSS代码、JS代码、图片等资源进行压缩
- 对未使用的代码或运行不到的代码进行删除
- 将较大的文件分离成多个较小的文件(代码分割)
- 将较小的文件进行合并
- 对 .jsx、.tsx、.vue、.less、.sass等文件进行解析,转换成浏览器能识别的代码(loader解析)
- 进行代码校验以及类型校验
- 对第三方模块进行抽离
- 手动配置相关的服务,如Nginx或Apache,并在配置文件里将请求指向正确的文件路径和服务端口, ......
### 四、如何实施前端工程化
具体可以从以下几个方面来实现前端工程化:
-
制定代码规范
制定统一的代码规范,例如命名、缩进、注释等,使用工具来检查和格式化代码,保证代码的可读性和一致性。常见的代码规范工具有 ESLint、Prettier 等。
制定代码规范常用方法:
-
参考已有的代码规范文档,例如腾讯、百度、阿里等公司或社区发布的前端代码规范,结合自己或团队的实际情况进行选择或修改。
-
使用代码规范工具,例如 ESLint、Prettier、Stylelint 等,来检查和格式化代码,保证代码符合预设的规范标准。
-
ESLint: 一个 JavaScript 和 TypeScript 的代码规范检查工具,可以检测代码中的语法错误、风格问题、潜在的 bug 等,并且可以自定义规则或使用现有的规则集,例如 Airbnb、Google 等。
-
Pretti+er: 一个代码格式化工具,可以自动修复代码中的缩进、空格、分号、引号等格式问题,使代码更加整洁和一致。
-
Stylelint: 一个 CSS 和 SCSS 的代码规范检查工具,可以检测代码中的语法错误、风格问题、重复代码等,并且可以自定义规则或使用现有的规则集,例如 Standard、Bootstrap 等。
-
SonarQube: 一个代码质量管理平台,可以对多种语言的代码进行静态分析,检测代码中的安全漏洞、坏味道、重复度等,并且提供可视化的报告和建议。
-
-
在团队内部进行 code review 和反馈,及时发现和改正不符合规范的代码,形成良好的编码习惯。
-
-
代码版本管理
用于管理和协调代码的版本、分支、合并等,方便团队协作和回溯。常见的代码管理工具有GitLab、 Git、SVN 等。
-
Git:比较流行的开源分布式版本控制系统。原理是将代码分为工作区、缓存区和版本库三个部分,每次修改代码后,需要先将修改添加至缓存区,然后再提交到版本库。Git还提供了多种分支管理策略,如GitFlow,可以根据不同的开发场景创建和合并不同的分支,如feature分支、release分支 hotfix分支。
-
GitLab:GitLab是一个基于Git的开源的DevSecOps平台,提供了从项目管理、代码托管、持续集成、持续交付、安全测试、监控等一系列的功能。GitLab可以部署在自己的服务器上,也可以使用GitLab.com这个云服务。
-
SVN:SVN是subversion的缩写,是一个开放源代码的版本控制系统,通过采用分支管理系统的高效管理,简而言之就是用于多个人共同开发同一个项目,实现共享资源,实现最终集中式的管理。
-
-
模块化开发
用于将复杂的前端代码分解为有明确功能和接口的模块,便于复用、维护和测试
模块化开发和组件化开发是两个完全不同的概念,模块化属于架构层面的概念,前端工程化与模块化的关系就类似于组装车间与零件的关系。使用模块化开发,可以解决下面几个问题:
- 避免命名冲突;
- 便于依赖管理;
- 利于性能优化;
- 提高可维护性;
- 提高代码可复用性;
在ES6规范发布之前,前端模块化开发主要有三种规范,分别是:CommonJS、AMD、CMD。
CommonJS是一种只适用于JavaScript的静态模块化规范,适合Node.js开发,但并不适合浏览器环境;而AMD/CMD规范并不是完全一致的,但核心功能是统一的,两个规范都重点解决了浏览器对前端模块化的需求。
ES6 Module规范推出之后,前三者的模块化规范也逐渐退出前端的历史舞台。ES6 Module是语言层面的规范,与应用场景无关,所以一个不涉及运行环境API调用的模块可以在任何场景下运行。但是目前浏览器还没有完全支持这种规范,所以,要实现ES6 Module规范的话,还需要使用构建工具进行编译。
-
组件化开发
用于将页面拆分为可复用的 UI 组件,每个组件有独立的 HTML、CSS、JS 代码,可以根据不同的需求进行组合。常见的组件化框架有 Vue、React 等。
前面提到了,组件化和模块化是两个完全不同的概念,模块化是文件层面上对代码和资源的拆分,组件化是设计层面上对UI的拆分。从UI中拆分出来的一个结构单元,成为UI组件,一个UI组件单元包含了HTML模板、CSS样式、JS逻辑。在页面的设计过程中,页面上的每一个元素都是组件,页面也是一个组件,只不过页面是一个大型组件,然后这个大型组件又由多个中小型组件拼装而成。中型组件还可以再拆分成小型组件,小型组件再拆分成DOM元素,DOM元素也属于浏览器自身的组件,是组件的基本单元。这种组件化开发就是前端开发的“分治思想”。
-
使用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实现。
-
CSS预编译
CSS作为浏览器可以直接识别的样式语言,弥补了HTML原生样式的不足,对于早期互联网开发中,样式的要求并不复杂,仅需要少量的CSS代码即可。但是在如今追求用户极致体验的潮流下,CSS的开发要求不断提高,复杂的CSS开发变成一件非常繁琐和痛苦的事情。最主要的原因还是受限于浏览器的实现和CSS自身的弱编程能力。 CSS预编译器的工作原理是提供便捷的语法和特性供开发者编写源代码,随后经过专门的编译工具将源码转化为CSS语法。
-
构建工具
使用构建工具来自动化处理开发阶段的代码,例如转译、压缩、合并等,使代码能够在生产环境中正常运行。常见的构建工具有 Webpack、Vite、Gulp 等。
项目构建其实就是为了弥补浏览器自身的缺陷和不足,是一种面向语言的编译过程。那么,除了针对语言本身之外,前端的构建还应该考虑到Web应用的性能优化。这些优化主要是为了减少HTTP请求,提升用户体验,包括:
- 依赖打包,将同步依赖的文件打包在一起,减少HTTP请求数量;
- 资源嵌入,例如将小于10kb的图片编译为base64格式嵌入文档,减少HTTP请求;
- 文件压缩,减少文件体积,缩短请求时间;
- 为文件加入hash指纹,以应对浏览器缓存策略;
- 将开发环境下的域名与静态资源文件路径修改为生产环境下的域名和路径;
- 文件名称的改变;
构建,简单来说就是编译,前端开发的所有文件最终归属是要交给浏览器去解析、渲染,并将页面呈现给用户,构建就是将前端开发中的所有源代码转化为宿主浏览器可以执行的代码。前端构建产出的资源文件只有三种,HTML、CSS、JS文件。需要完成编译的内容有:
- 无法被浏览器直接识别的JS代码,包括ES6/7/8/9/10等符合ECMAScript规范的JS代码;
- 无法被浏览器直接识别的CSS代码,包括SASS/LESS等预编译的CSS语法;
- 无法被浏览器识别的HTML模板代码,包括jade、ejs、artTemplate、mustache等Node.js模板引擎;
常见的构建工具有:
-
YUI Tool + Ant
YUI tool 是 07 年左右出现的一个构建工具,功能比较简单,用于压缩混淆 css 和 js 代码,需要配合 java 的 Ant 使用。
当时 web 应用开发主要采用 JSP,还不像现在这样前后端分离,通常是由 java 开发人员来编写 js、css 代码,前端代码都是和后端 java 代码放在一起的。因此前端代码的压缩混淆也就基于 java 实现了。
-
Grunt / Gulp
Grunt / Gulp 都是运行在 node 环境上的自动化工具。 在开发过程中,我们可以将一些常见操作如解析 html、es6 代码转换为 es5、less / sass 代码转换为 css 代码、代码检查、代码压缩、代码混淆配置成一系列任务,然后通过 Grunt / Gulp 自动执行这些任务。
Grunt 和 Gulp 的不同点:
使用 Grunt的过程中,会产生一些中间态的临时文件。一些任务生成临时文件,其它任务可能会基于临时文件再做处理并生成最终的构建后文件,导致出现多次 I/O。
Gulp 有文件流的概念,通过管道将多个任务和操作连接起来,不会产生临时文件,减少了 I/O 操作,流程更清晰,更纯粹,大大加快了构建的速度。
-
Webpack / Rollup / Parcel
Webpack、Rollup、Parcel 统称为静态模块打包器。
这一类构建工具,通常需要指定入口 - entry,然后以 entry 为起点,通过分析整个项目内各个源文件之间的依赖关系,构建一个模块依赖图 - module graph,然后再将 module graph 分离为三种类型的 bundle:
entry 所在的 initial bundle、lazy load 需要的 async bundle 和自定义分离规则的 custome bundle。 这几个构建工具各有优势:
- Webpack 大而全,配置灵活,生态丰富,是构建工具的首选。
- Parcel 号称零配置,使用简单,适合不太需要定制化构建的项目使用。
- Rollup 推崇 ESM 标准开发,打包出来的代码干净,适用于组件库开发。
-
Vite / Esbuild
新一代构建工具。
esbuild, 基于 go 语言实现,代码直接编译成机器码(不用像 js 那样先解析为字节码,再编译为机器码),构建速度比 webpack 更快。
vite, 开发模式下借助浏览器对 ESM 的支持,采用 nobundle 的方式进行构建,能提供极致的开发体验;生产模式下则基于 rollup 进行构建。
-
开发环境的本地服务器与Mock服务
在前端工程化开发中,通过构建工具可以将代码进行编译,然后在浏览器中进行调试,但是在开发过程中源码的每次修改都需要执行一次构建,构建完成后才能在浏览器里运行,这对前端工程师来说无疑就是一种灾难。要完美的解决这个问题,可以使用本地服务器与构建工具结合,对源码进行监听并在修改之后触发动态构建,使用自动化构建的方式代替人工。这种动态构建是使用本地服务器解决开发层面上的问题。
Mock服务解决的是前后端协作开发的问题,前后端开发人员提前约定好规范,前端工程师通过本地服务器提供的Mock数据接口辅助前端逻辑的编写和功能模块的开发。如果项目中需要服务器端渲染(SSR),本地服务器还需要具备解析HTML模板的能力,同时Mock服务提供SSR所需的初始化数据。
前端工程师可以使用本地服务器提供的Mock数据接口,在后端人员开发的同时,进行前端逻辑的并行开发,等到后端真实接口开发完成后,将前端请求的地址从Mock服务迁移到服务器的生产环境即可。
-
自动化测试工具
使用自动化测试工具来编写和执行测试用例,保证代码的功能正确性和稳定性,提高代码的可信度。
常见的自动化测试工具有 Jest、Mocha、Cypress 等。
- Mocha:一套灵活的JavaScript测试框架,可以运行在Node.js和浏览器中,支持异步测试、钩子函数、断言库等功能,适用于编写单元测试和集成测试。
- Jasmine:一套基于行为驱动开发(BDD)的JavaScript测试框架,可以运行在Node.js和浏览器中,支持异步测试、模拟对象、自带断言库等功能,适用于编写单元测试和集成测试。
- Jest:一套基于Jasmine的JavaScript测试框架,可以运行在Node.js中,支持快照测试、模拟对象、代码覆盖率等功能,适用于编写React和Vue等框架的单元测试和集成测试。
- Karma:一套JavaScript测试执行器,可以在多个真实的浏览器中运行测试代码,支持与Mocha、Jasmine等框架配合使用,适用于编写跨浏览器的单元测试。
- Cypress:一套基于Chrome的端到端(E2E)测试框架,可以在浏览器中运行测试代码,并提供了可视化的界面和调试工具,支持网络模拟、自动重试等功能,适用于编写Web应用的UI测试。
-
项目部署流程化
站在前端开发的范畴来说,项目部署是指前端开发人员将构建产出的代码包部署到测试服务器的过程,而并非是将测试完成的代码发布到生产环境的过程。在部署过程中,要考虑目标服务器、路径信息是否与项目一一对应,并且可供负责部署到生产环境的开发人员进行配置,部署的操作流程应尽量简单。
在部署流程中,使用命令行取代工具执行(例如FTP)本地部署,能够极大的提高部署的速度和效率,但是这只是初级阶段的部署流程。考虑团队协作和安全方面的因素,最佳的方式应该是搭建一个可供严格审查、队列控制、操作简化的部署平台,并且有专人负责掌握流程进度。虽然这种搭建部署平台的方式在一定程度上减缓了整体的部署速度,但是加强了团队协作和安全保障。
常见的部署工具有:
- Jenkins:一套开源的持续集成和持续交付的工具,可以通过配置流水线来自动化执行构建、测试、部署等任务,支持插件扩展、分布式构建等功能,适用于多人协作的大型项目。
- Travis CI:一套基于云的持续集成和持续交付的工具,可以通过配置.travis.yml文件来自动化执行构建、测试、部署等任务,支持与GitHub集成、多语言支持等功能,适用于开源项目。
- GitHub Actions:一套基于云的自动化工作流的工具,可以通过配置.yml文件来自动化执行构建、测试、部署等任务,支持与GitHub集成、多平台支持等功能,适用于GitHub托管的项目。
- Netlify:一套基于云的静态网站托管和部署的工具,可以通过配置netlify.toml文件来自动化执行构建、测试、部署等任务,支持与GitHub集成、自定义域名、SSL证书等功能,适用于静态网站和单页面应用。
-
监控工具
使用监控工具来收集和分析应用的运行情况,例如错误日志、性能指标、用户行为等,帮助优化和改进应用。
常见的监控工具有:
- 阿里ARMS:一个全面的应用性能管理平台,可以监测前端、后端、移动端等各种应用的性能和错误,并提供实时分析和告警。
- FUNdebug:一个专注于前端和小程序的错误监控平台,可以捕获前端、小程序、React Native等各种环境下的错误信息,并提供实时通知和分析。
- sentry:一个开源的错误追踪和异常处理平台,可以捕获前端、后端、移动端等各种环境下的错误信息,并提供实时通知和分析。
- BombayJS:一个开源的前端监控SDK,可以监测前端、小程序等各种应用的性能和错误,并提供数据上报和可视化查询。
### 五、工程化不等于工具
现在有些工具非常强大,像 Webpack,以至于很多人认为前端工程化就是指 webpack。
实际上,工具不是工程化的核心,工程化的核心是对项目的整体规划和架构。工具只是实现这种规划和架构的一种手段。
一个项目的工程化,首先要规划出一个项目整体的工作流架构。
如文件组织结构(按作用分层、按业务分层)、代码开发范式(语法、规范、标准、语言,如 ES6+、TypeScript 等)、前后端分离方式(Ajax 和中间层)。 规划完工作流整体架构以后,再来考虑使用哪些工具来实现这套规划。 可以借鉴目前业界成熟的工程化方案中的思路,如 create-react-app、vue-cli、angluar-cli、gatsby-cli 等。
它们并不是一个简单的项目生成脚手架,而是工程化工具的集成。
### 六、工程化与 Node.js
目前前端的工程化很大程度上都归功于 Node.js。
Node.js 是继 Ajax 后有一次颠覆式的前端工业革命,没有 Node.js 就没有今天的前端。
目前绝大多数工程化工具都是由 Node.js 开发的,所以前端工程化是由 Node.js 强力驱动的。
总之,工程化是为了解决问题而存在的。