那些年之前端工程化走过的时光

587 阅读15分钟

目录

  • 什么是前端工程化
    • 先搞懂工程
    • 再搞懂构建工具
  • 第一代前端构建工具
  • 第二代前端构建工具
  • 第三代前端构建工具

什么是前端工程化

一说到前端工程化,有人自然而然就想到了前端构建工具,并将两者划上等号。

但是事实上,前端工程化 != 前端构建工具,前端构建工具只能说是前端工程化里面重要的一环,但是不能说完全划等号。

类似于下图的关系:

image-20211105170524989

先搞懂工程

那么,如果理解前端工程化这种抽象的名词呢?

其实,这个词一点都不难理解,我首先问大家,什么叫做工程?

中华文字,博大精深,当你看不懂一个词的时候,那你就来拆词,工程实际上可以拆分为“工作”和“程序”,这里的“工作”指的就是要做的事情,而“程序”不是指我们写的代表程序,而是指做事情的顺序或者步骤,比如我问你“你的 1 个亿遗产继承手续办完没有?”,你回答我“还在走程序”,这里的程序就和“工程”的第二个字“程”是一个意思。

那么“工程”这个词,最早并不是出现在我们 IT 行业,而是出现在建筑行业。比如修一个房子,里面包含了一系列繁杂的工作,并且每一项工作会有一个先后顺序,所以才出现了“建筑工程”的概念。

image-20211105171442843

后来,随着我们软件开发的规模越来越大,要做的事情也越来越多,所以借助了“建筑工程”的概念,出现了“软件工程”这个名词。现在很多大学的专业都直接就叫“软件工程”,别问我怎么知道的~

image-20211105171914156

OK,目前你应该就理解了“工程”的概念,再总结一次,“工程”指的是一堆有先后顺序的事情,而“软件工程”就是指的开发软件从设计到最后上线要做的一系列事情,如果哪天你发现你做一道菜要做的事情特别多,每一件事情又可以展开讲很多东西,那么你完全可以发明一个新的名词叫“做菜工程”。

再搞懂构建工具

明白了“工程”的概念后,“前端工程化”这个词你应该也就大致明白是什么意思了。

现在前端要做的事情,并且也是能做的事儿确实已经越来越多了。例如从一开始构建整个项目结构、然后编写代码、如果是用 less/sass/typescript 等工具写的代码还涉及到编译、之后是打包、测试...能罗列出来的事情有一大堆,毕竟如果前端做的事情只是和以前一样写一两个页面就完事儿,那么是无法称之为是一个工程的。

上面所说到的一大堆的事情中,实际上有一部工作是和业务逻辑无关的,例如:

  • 文件优化:压缩 JavaScriptCSSHTML 代码,压缩合并图片等。

  • 代码转换:将 TypeScript/ES 6 编译成 JavaScript、将 SCSS 编译成 CSS 等。

  • 代码优化:为 CSS 代码添加兼容性前缀等。

就拿代码压缩来讲,当我们项目要上线的时候,无论是从代码保密性的角度,还是缩小代码体积的角度来看,压缩代码都是绝对有必要的。例如下面是代码压缩前后的对比:

代码压缩前:

image-20211013113528633

代码压缩后:

image-20211013113501508

那到这里,问题就来了?

压缩这个工作怎么做?谁来做?

首先肯定不可能手工一个一个来敲,需要通过一定的工具来实现。我们也确实能够很容易的找到一些好用的压缩工具。

所以上面所列举的那些事情,什么文件压缩呀,代码转换呀,代码添加前缀呀,这些事情一般都是通过第三方工具来帮助我们完成的。

但是这里又涉及到另外一个问题,我需要将我写的项目先拖入到 A 工具进行处理,处理完成后拖到 B 工具进行处理,如果后面还需要用到工具 B、C、D、E...,得这样一个一个来操作么?

当然不是,并且我们也并不想。我们所期望的,是有那么一个工具,能够帮助我们把上面的那些无脑工作自动化的完成,于是乎,前端构建工具就诞生了。

总的来讲,既然名字都叫做构建工具,那么“构建”二字一定是重点。那么这个工具究竟构建个啥?

实际上就是将我们开发环境下的项目代码构建成为能够部署上线的代码

所以说,**前端构建工具是来解放我们的,而不是来给我们添麻烦的。**我们需要有这么一个工具,自动的帮助我们构建出生产环境的代码,而不用我们一个一个文件来进行手工的转换、压缩...

那么为什么一说到前端构建工具,有的人就开始头皮发麻呢?

这其实是因为前端构建工具经过长期的发展,涌现出了一大堆让人眼光缭乱的构建工具:

image-20211013115255450

这对于初学者来讲简直是灾难,因为直接就把初学者劝退了~

但是之所以有那么多,是因为随着我们前端技术的发展,老的前端构建工具往往力不从心,或者存在一些这样那样的问题,所以才会出现新一代的前端构建工具。

整体上来讲,我将前端构建工具分为了 3 代,每一代前端构建工具都是为了解决自己所在时期出现的的问题。

第一代前端构建工具

第一代前端构建工具是以 Npm ScriptsGrunt 以及 Gulp 为代表的构建工具。

image-20211013121029747

该时期要做的事情相对来讲比较少,就是我们前面所提到的代码转换和压缩。

其中 gulp 相比 grunt 来讲是后起之秀,它吸取了 grunt 的优点,拥有更简便的写法,通过流(Stream)的概念来简化多任务之间的配置和输出,让任务更加简洁和容易上手。

相比于 grunt 的频繁的 IO 操作,Gulp 的流操作,能更快地完成构建。另外学习曲线也比较平滑。比 grunt 速度更快、配置更少。

这里我们可以简单的来了解一下 gulp 的使用。

第二代前端构建工具

前面两种工具,从功能上进行分类的话,实际上是属于任务流工具

也就是说,我们定义一系列的任务,例如“转换”、“压缩”,把这些任务定义好之后,构建工具就会按照我们所定义的任务一个一个执行。在之前的时代背景下,是完全 OK 的,并且也是完全合格的一个构建工具。

但是,随着前端技术的发展,Web 应用从以前的多页网站逐渐变化成了单页应用。整个应用 html 文件只有一个,要呈现不同的视图内容靠的是 Ajax 请求内容然后动态的渲染视图。这必然导致 JavaScript 的代码量陡增,所以代码量一大人们自然而然就会想到分模块。

整个前端的模块化发展也是比较曲折的,不过好在最终前端模块化被纳入了 ECMAScript 标准,迎来了统一。

但是,随着前端模块化的兴起,我们的前端开发者们遇到了一个新的问题,那就是 JavaScript 文件的模块过多,这在开发阶段虽然没啥问题,但是如果这样的项目部署上线,每多一个文件,在客户端上进行传输时就会多一次 http 请求,这显然是不可取的。

因此,我们对构建工具在构建项目发行版本时,就多出来了一个要求,那就是能够把多个 JavaScript 模块打包成一个模块。在这样的需求背景下,就出现了第二代前端构建工具,也被称之为叫做模块打包工具

下图是比较常见的第二代前端构建工具:

image-20211013134110968

下面我们可以对每一个二代构建工具做一个简单了解,不要求每个都要去用一遍,但是至少能做到知道有这么个东西,并且大致知道它的特点有些啥。

browserify

browserify 是最早期的模块打包工具,也是模块打包中的先驱者。

image-20211106150959816

那个时期 ES Module 还没成为 ECMA Script 的统一规范,所以该构建工具最大的特点就是能让开发人员能够使用 CommonJS 规范来做代码的模块化,那个时期非常流行的构建方式就是 browserify + glup 的构建方式,glup 负责将文件转码、压缩,browserify 负责进行打包。

webpack

webpack 是后起之秀,它支持了 AMDCommonJS 类型,是一个为前端模块打包构建而生的工具。

image-20211106151050538

它既吸取了大量已有方案的优点与教训,也解决了很多前端开发过程中已存在的痛点,如代码的拆分与异步加载、对非 JavaScript 资源的支持等。强大的 loader 设计使得它更像是一个构建平台,而不只是一个打包工具。

webpack 的优点就不用说了,现在说一下 2 个缺点:

  • 配置复杂
  • 大型项目构建慢

针对 webpack 配置复杂这一点,同学们应该是深有体会的,在大型项目中超过 500 行的配置并不罕见。

配置复杂这一块一直是 webpack 被吐槽的一点,主要还是过重的插件系统,复杂的插件配置,插件文档也不清晰,更新过快插件没跟上或者文档没跟上等问题。

比如现在 webpack 已经到 5 了网上一搜全都是 webpack3 的文章,往往是新增一个功能,按照文档配置完后,发现有报错,然后网上一顿查,这里拷贝一段,那里拷贝一段,又来几个报错,又经过一顿搞后终于可以运行。

另外大型项目构建慢这一点,凡是使用 webpack 来开发过大型项目的同学,也应该是历历在目的。

经常是 npm run dev 后,要等待许久才能打开构建后的项目,并且这种情况会随着项目的规模增大,越来越明显。

关于 webpack 的相关知识,这里就不再赘述了。前端现代框架无论是 vue 还是 react 都提供了脚手架工具以帮助我们快速的搭建一个对应技术栈的项目,而搭建的这个项目就是基于 webpack 的。

有兴趣的同学,也可以自己尝试使用 webpack 来搭建一个 vue 的项目。

parcel

前面说过,webpack 有两个痛点:

  • 配置复杂
  • 大型项目构建慢

parcel 的出现,就是为了解决这两个痛点的。

2021-11-06 15.26.01

parcel 主打极速零配置

内置了 html、babel、typescript、less、sass、vue 等功能,无需配置,并且不同于 webpack 只能将 js 文件作为入口,在 parcel 中万物皆资源,所以 html 文件 css 文件都可以作为入口来打包。

所以不同于 webpack 的复杂配置,只需要一个 parcel index.html 命令就可以直接起一个自带热更新的 server 来开发 vue/react 项目。

另外,针对 webpack 大型项目构建慢这一点,parcel 也做了很多优化,例如 parcel 使用 worker 进程去启用多核编译,并且使用文件来缓存编译后的模块。

以下是 parcel 官方的一个数据,基于一个合理大小的应用,包含 1726 个模块,6.5M 未压缩大小。

在一台有 4 个物理核心 CPU2016 MacBook Pro 上构建。

打包工具时间
browserify22.98s
webpack20.71s
parcel9.98s
parcel - with cache2.64s

看起来好像 webpack 的那两个痛点都解决了,但是 parcel 也有自身的缺点:

  • 0 配置虽然好,但是也有一定的代价,那就是无法轻松的对构建工作进行定制化,如果想要配置一些复杂的构建功能就会非常麻烦。
  • 生态相比于 webpack 比较小众,如果遇到错误查找解决方案比较麻烦。

这里我们可以简单的来了解一下 Parcel 的使用。

rollup

2015 年,前端的 ES module 发布后,rollup 应声而出。

image-20211106171924034

rollup 可以编译 ES6 模块,提出了 Tree-shaking,根据 ES module 静态语法特性,删除未被实际使用的代码,支持导出多种规范语法,并且导出的代码非常简洁,所以这可以算是 rollup 的一个最大特点。

另外,相比于 browserifyCommonJsrollup 专注于 ES module,并且相比于 webpack 大而全的前端工程化,rollup 更加专注于纯 javascript 的打包工作,所以 rollup 大多被用作打包 tool 工具或 library 库。

第三代前端构建工具

我们的前端构建工具,在经历了第一代和第二代的发展后,目前迎来了第三代前端构建工具。

回顾一下前面两代:

  • 第一代:构建工具主要解决代码压缩、转换等问题,让这些工作可以自动化执行。
  • 第二代:前端模块化时代的到来,构建工具主要解决模块打包的问题,并且提供的功能越来越完整,包含代码分割、tree-shakingsource-map 等一系列的功能。

那么第三代还有什么问题要处理呢?

还记不记得前面我们所提到的 webpack2 个问题,一个是配置复杂,另一个是大型项目构建慢。大家在使用 webpack 时往往会有这样的感受,当一个项目越来越大,所涉及的模块越来越多时,启动速度会非常慢。

为什么会这样呢?

究其原因,是因为在启动 webpack 搭建的项目时,webpack 会先进行打包,然后运行的是打包后的文件,然而整个打包就是非常耗时的一个操作,需要根据文件间的依赖关系对其进行静态分析,然后将这些模块按指定规则生成静态资源,当 webpack 处理程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

当然,这对于项目实际上线没有任何影响,项目上线后使用的肯定是打包后的文件。但是这对于开发人员是极其痛苦的,每次都要等待漫长的打包结束后才能看到项目效果,甚至有些大型项目时,程序员早上到公司后先把项目进行打包构建,然后去开会上厕所啥的,弄完之后回到自己的工位,项目总算跑起来了...

所以第三代前端构建工具,主要是解决开发者的烦恼,让我们开发人员的开发体验更加的好。

目前比较有名的第三代前端构建工具有 SnowpackVite,其中 vite 是随着 vue3.0 的到来一起发布的。

image-20211013145418168

Vite 一个基于浏览器原生 ES Module 的开发服务器。

利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用

Vite 中,script 标签里声明 typemodule

<script type="module" src="main.js"></script>

当声明一个 script 标签类型为 module 时,浏览器就会像服务器发起一个 GET http://localhost:3000/src/main.js 来请求 main.js 文件,该文件的代码如下:

// /src/main.js:
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

浏览器请求到了 main.js 文件,检测到内部含有 import 引入的包,又会对其内部的 import 引用发起 HTTP 请求获取模块的内容文件 如: GET http://localhost:3000/@modules/vue.js 如: GET http://localhost:3000/src/App.vue

有的人可能会有这样的疑问,这个开发服务器获取模块是通过发送网络请求来获取的,发送网络请求不是也挺慢的么?

但是你想想,这个服务器就在你本地呀,所以相当于是直接从本地获取 JavaScript 模块,速度当然也就比 webpack 要打包后才执行要快得多。

当然,获取到的模块也不是说直接丢给浏览器就可以执行,别忘了浏览器只认识 HTML、CSSJS,所以对于像 .vue 这样的模块文件,还是要进行处理的。其处理方法就是 Vite 通过劫持浏览器的这些请求,将 .vue 文件转为 JavaScript 文件再返回给浏览器进行页面的渲染。

总之,Vite 的整个过程中都没有对文件进行打包编译,所以其运行速度比原始的 webpack 开发编译速度快太多,而且也不会随着项目规模的变化而导致速度有所降低,无论你项目有多大,使用 Vite 运行项目的速度都是不变的。

关于 Vite 的更多知识,可以参阅后面 Vite 相关的文档《Vite 教程》。

-EOF-