前端工程化之构建工具

大家好,我是「柒八九」

最近,本来是想写一篇关于Promise的文章,在文章规划的时候,发现牵扯的东西有点多,需要再准备一下。

前几天,我们开辟了一个新的模块 前端工程化,今天我们继续来讨论和介绍这方面的知识。因为,工程化的一些概念还是有些繁杂的,但是如果梳理得当,会对工程化有一个更深的认识。

那话不多说,开搞?!

文章概要

  1. 构建解决的问题
  2. 包管理工具
  3. 任务式构建工具
  4. 模块化:模块定义与模块化的构建工具

构建解决的问题

Grunt /Gulp/ webpack 等前端工具出现之前,前端资源的构建需要借助于其他开发领域的工具实现,比如 Ant(主要用 Java 项目的编译、构建、部署等), Make(主要用于C++的项目构建) 。

甚至专业构建 jscss 具也需要特殊的编程语言执行环境,比如 YUI Composer 最初需要依赖 Java 运行环境。

「然而」Node.js 的诞生和发展令前端工具生态不断壮大,目前我们所熟知的 Grunt /Gulp/webpack 等工具均是「由 Node.js 为底层驱动平台的」

「在 Node.js 诞生之前」,对于前端资源的构建工作只是进行「基本的压缩和打包」,因为当时前端项目自身的复杂度并不高,没有模块化开发、规范转译、css 预编译等现在看来非常普遍的需求。

简单介绍一下远古时期的文件压缩和合并工具。

文件压缩与合并工具

为了获得更好的访问体验,开发者需要「更少的资源连接数」「更小的文件体积」,这就分别对应了两类工具:

  • 文件压缩工具
  • 合并工具

「压缩工具」主要有

  1. 「JSMin」 用于去除 JS 代码中的注释和空格
  2. 「YUI Compressor」 基于 Java 的代码压缩工具
  3. 「Closure Compiler」 提供了比 YUI Compressor更多的代码优化功能 并支持 Source Map 和多文件合并
  4. 「UglifyJS」

「从上到下,压缩与优化的性能不断完善」

「合并工具」主要从「图片资源」「代码层面」两方面进行处理。

  • 「图片合并」CSS Sprite 技术的提出解决了网页中大量素材图片的加载性能问题
  • 「代码文件的合并」: 利用Closure Compiler 工具中将「多个文件」合并为一个

而随着前端工程「复杂度」的不断提升,对网站中资源也不仅仅停留在压缩和打包上。需要利用构建工具,「将源代码转化为宿主浏览器可执行的代码」「核心是资源的管理」

前端的产出资源包括js/ css /HTML 等,分别对应的「源代码」则是

  • 「领先」于浏览器实现的 ECMAScript 规范编写的 JS 代码
  • LESS/SASS 「预编译语」法编写的 css 代码
  • Jade/EJS/Mustache「模板语法」编写 HTML 代码

以上源代码是无法在浏览器环境下运行的,构建工作的核心便是将其「转化为宿主可执行代码」,分别对应:

  • ECMAScript 规范的转译
  • css 预编译语法转译
  • HTML 模板渲染

这些功能可以说是为了「弥补浏览器自身功能的缺陷和不足」,可以理解为「面向语言的」

除了语言本身,前端资源的构建处理还需要要考虑Web应用的「性能因素」

比如开发阶段使用「模块化开发」「每个模块有独立 JS/CSS/ 图片等文件」。如果不做处理将每个文件独立上线, 无疑会增加客户端 HTTP 请求的数量 ,从而影响 Web 应用性能和用户体验。

我们可以从以下几点来窥探,构建阶段在性能方面的工作方向

  • 「依赖打包」 1. 分析「文件依赖关系」,将「同步依赖」的文件打包在一起 2.「减少 HTTP 请求数量」
  • 「资源嵌入」 1. 比如小于 10KB 的图片编译为 base6 格式嵌入文档 2. 「减少HTTP 请求」
  • 「文件压缩」 1. 减小文件体积 2. 「缩短请求时间」
  • 「hash 指纹」 1. 通过给文件名加入 hash 指纹 2. 利用「浏览器缓存策略」

这些功能的目的是为了提高 Web 应用的「性能和用户体验」,可以理解为「面向优化的」

html 文件与js/ css /图片等资源是「引用与被引用」关系。被引用的资源经过构建后通常有以下改动

  • 「域名/路径改变」 1. 开发环境与线上环境的域名肯定是不同的 2. 「不同类型」的资源部署于不同的 CDN 服务器上
  • 「文件名改变」 1. 经过构建之后文件名被加上 hash 指纹 2. 内容的改动导致 hash 的改变

以上的改动最终会影响 html 文件对被引用资源的 URL 改变。所以对于 html 件的构建工作需要注意在其引用资源 URL 改变时「同步更新」,这个功能通常被称为「资源定位」

之所以将被引用资源进行上文所述的改动,是由于测试环境与生产环境的要借助「部署策略应对」。构建在其中的作用可以理解为「面向部署」

❝构建需要解决的问题有三类

  1. 「面向语言」 (转译工作)
  2. 「面向优化」 (性能和用户体验)
  3. 「面向部署」 (不同环境下的资源定位)


包管理工具

在上文说到,在Node.js出现之前,前端针对资源的处理,只局限在「压缩」「文件合并」上,更悲惨的是,有时候还需要低三下四的去借用别的语言的运行时环境(runtime)。

随着Node.js 的发布,许多原先基于其他语言开发的工具包如今可以通过 Node.js 来实现,并通过 npm(Node Package Manager,即 node 包管理器)来安装使用。

现在主流的包管理器,主要有npm/yarn/pnpm

  • npm: 1. 在 npm 安装依赖的过程中会引入大量的子包 2. 在「早期版本」npm 3 之前)中会产生相同依赖包的大量重复拷贝 产生node_modules hell
  • yarn:和 npm 相比,Yarn 的主要优点有 1. 「安装速度」: Yarn 在安装依赖时采用的是「并行操作」,「它在初次与重复安装依赖时,普遍都会比 npm 更快」 2. 「稳定性」:npm5 引入的 package-lock 文件,在每次执行 npm install 时仍然会检查更新符合语义规则的依赖包版本,yarn.lock 则会「严格保证版本的稳定性」 3. 「Plug'n'Play」PnP):Yarn 2.0 发布了 PnP的功能, PnP 方案具有提升「项目安装与解析依赖」的速度,以及多项目共享缓存(与普通缓存相比,免去了读写 node_modules 的大量 I/O 操作),节省占用空间等优势
  • pnpm: 1. 「节约磁盘空间」「提升安装速度」

2. 创建「非扁平化」的 node_modules 文件夹

时序图


任务式构建工具

使用「自动化」「任务式构建工具」来替代手工执行各种处理命令。

任务式构建工具主要有两类:

  • 「Grunt」: 「基于任务」的构建工具(2012年发布)
  • 「Gulp」: 「流式」构建工具(2013年发布)

GruntGulp 这两种任务式的构建工具的基本组成包括

  • 核心的处理工具(grunt-cli/gulp-cli
  • 配置文件(Gruntfile/Gulpfile
  • 一系列常用的任务插件
    • Clean
    • Watch
    • Copy
    • Concat
    • Uglify

在项目里通过编写配置文件,就可以定义工作流程中的各种自动化构建处理。

Grunt vs Gulp

  • 「读写速度」 1. Gulp 在处理任务的过程中基于 NodeJS 的数据流,「本质上是读写内存」 2. Grunt 则是基于临时文件, 读写速度上 Gulp 要快于 Grunt
  • 社区使用规模 在 npmjs.com 的周下载量方面,Gulp 大约是 Grunt 的两倍
  • 「配置文件的易用性」 相比描述不同插件配置信息的 Gruntfile 而言,使用 pipe 函数描述任务处理过程的方式通常更易于阅读

❝任务式构建工具的出现解决了开发流程中「自动化执行预设任务」的问题,但「不能解决项目中代码如何组织成不同功能的代码包、不同代码之间如何相互依赖」等问题 ❞


模块化:模块定义与模块化的构建工具

我们简单来描述下,从前端的莽荒时代,到现在ESModule一统天下,都经历了哪些模块化解决方案。

模块化的不同规范

CommonJS

CommonJS 出现之前,一个 JS 类库只能通过「暴露全局对象」的方式,供其他 JS 文件使用,这种处理方式,极易「造成变量污染」

CommonJS 作为「非浏览器端」「JS 规范」,从几个方面来描述该规范。

  • 「模块定义」 「一个模块即是一个 JS 文件」,代码中module指向当前模块对象
  • 「模块引用」 1. 通过引用 require() 函数来实现模块的引用 2. 参数可以是相对路径也可以是绝对路径 3. 在「绝对路径的情况下」,会按照 node_modules 规则「递归查找」,在解析失败的情况下,会抛出异常
  • 「模块加载」: 1. require() 的执行过程是「同步的」 2. 执行时即进入到被依赖模块的执行上下文中,「执行完毕后再执行依赖模块的后续代码」

AMD

❝适用于「浏览器端」「异步加载」模块化规范 ❞

  • 「模块定义」 1. 通过define(id?, dependencies?, factory) 函数定义模块 2. id 为模块标识 3. dependencies 为依赖的模块 4. factory 为工厂函数
  • 「模块引用」 1. 最早需要通过 require([id], callback) 方式引用 2. 之后也支持了类似 CommonJSvar a = require('a') 的写法

UMD

❝UMD 本质上是兼容 CommonJSAMD 这两种规范的代码「语法糖」

通过判断「执行上下文中」是否包含 definemodule 来包装模块代码,适用于需要「跨前后端的模块」

ES Module

「ES6版本中提出JS模块化」

  • 「模块定义」
    • 通过 export 关键字导出「任意个数」的变量
    • 通过 export default 导出 「一个模块中只能包含一个 default 的导出类型」
    • 模块内支持两种导出方式
  • 「模块引用」 1. 通过 import 关键字引用其他模块
    • 「静态引用」: 静态引用格式为import importClause from ModuleSpecifier import 表达式需要写在文件「最外层上下文中」
    • 「动态引用」 动态引用的方式则是 import(),返回 promise 对象
    • 引用方式分为

模块化的构建工具

构建工具

作用&目标

RequireJS

支持 AMD 风格的模块化代码运行

Browserify

让 CommonJS 风格的代码也「运行在浏览器端」 除了提供语法糖外还提供了一些经过处理后且在浏览器端运行的 NodeJS 的核心模块

Babel

定位一直是 Transformer:即「语法转换器」承担着将 ES6、JSX 等语法转换为 ES5 语法的核心功能

SystemJS

兼容各种模块化规范的「运行时工具」

「Webpack」

1. 一方面兼容各种模块化规范的标识方法2. 另一方面将模块化的概念延伸到「其他类型的文件中」, 创造性地打造了一种「完全基于模块的新的构建体系」

Rollup

1. 率先实现了 「Tree Shaking 功能」 2. 天然支持 ES6 模块的打包

后记

「分享是一种态度」

参考资料:效率工程化/前端工程化体系设计与实践

原文地址:https://cloud.tencent.com/developer/article/2081772

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340