自我的核心就在于独立之人格,自由之精神
👉 成熟的自我,就必然是孤独的
大家好,我是柒八九。
最近,本来是想写一篇关于Promise
的文章,在文章规划的时候,发现牵扯的东西有点多,需要再准备一下。
前几天,我们开辟了一个新的模块 前端工程化,今天我们继续来讨论和介绍这方面的知识。 因为,工程化的一些概念还是有些繁杂的,但是如果梳理得当,会对工程化有一个更深的认识。
那话不多说,开搞?!
文章概要
- 构建解决的问题
- 包管理工具
- 任务式构建工具
- 模块化:模块定义与模块化的构建工具
构建解决的问题
在Grunt
/Gulp
/ webpack
等前端工具出现之前,前端资源的构建需要借助于其他开发领域的工具实现,比如 Ant
(主要用 Java
项目的编译、构建、部署等), Make
(主要用于 言的项目构建) 。
甚至专业构建 js
和 css
具也需要特殊的编程语言执行环境,比如 YUI Composer
最初需要依赖 Java
运行环境。
然而,Node.js
的诞生和发展令前端工具生态不断壮大,目前我们所熟知的 Grunt
/Gulp
/webpack
等工具均是由 Node.js 为底层驱动平台的。
在 Node.js
诞生之前,对于前端资源的构建工作只是进行基本的压缩和打包,因为当时前端项目自身的复杂度并不高,没有模块化开发、规范转译、css 预编译等现在看来非常普遍的需求。
简单介绍一下远古时期的文件压缩和合并工具。
文件压缩与合并工具
为了获得更好的访问体验,开发者需要更少的资源连接数与更小的文件体积,这就分别对应了两类工具:
- 文件压缩工具
- 合并工具
压缩工具主要有
- JSMin
用于去除 JS 代码中的注释和空格 - YUI Compressor
基于 Java 的代码压缩工具 - Closure Compiler
提供了比YUI Compressor
更多的代码优化功能
并支持 Source Map 和多文件合并 - 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 改变时同步更新,这个功能通常被称为资源定位。
之所以将被引用资源进行上文所述的改动,是由于测试环境与生产环境的要借助部署策略应对。 构建在其中的作用可以理解为面向部署
构建需要解决的问题有三类
- 面向语言 (转译工作)
- 面向优化 (性能和用户体验)
- 面向部署 (不同环境下的资源定位)
包管理工具
在上文说到,在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年发布)
Grunt
和 Gulp
这两种任务式的构建工具的基本组成包括
- 核心的处理工具(
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. 之后也支持了类似CommonJS
的var a = require('a')
的写法
UMD
UMD 本质上是兼容
CommonJS
与AMD
这两种规范的代码语法糖
通过判断执行上下文中是否包含 define
或 module
来包装模块代码,适用于需要跨前后端的模块。
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 模块的打包 |
后记
分享是一种态度。
参考资料:效率工程化/前端工程化体系设计与实践
全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。