vite学习笔记

一、webpack问题

(1) 项目冷启动时必须递归打包整个项目的依赖树

(2)JavaScript 语言本身的性能限制,导致构建性能遇到瓶颈,直接影响开发效率(单线程

二、Vite

(1)Vite 在开发阶段基于浏览器原生 ESM 的支持实现了no-bundle服务 

  • 利用浏览器原生 ES 模块的支持,实现开发阶段的 Dev Server,进行模块的按需加载,而不是先整体打包再进行加载。相比 Webpack 这种必须打包再加载的传统构建模式,Vite 在开发阶段省略了繁琐且耗时的打包过程,这也是它为什么快的一个重要原因
  • no-bundle只是对于源代码而言,对于第三方依赖而言,Vite 还是选择 bundle(打包),并且使用速度极快的打包器 Esbuild 来完成这一过程,达到秒级的依赖编译速度
  • 一个 import 语句代表一个 HTTP 请求,而正是 Vite 的 Dev Server 来接收这些请求、进行文件转译以及返回浏览器可以运行的代码,从而让项目正常运行

(2)借助 Esbuild 超快的编译速度来做第三方库构建(预构建)和 TS/JSX 语法编译,从而能够有效提高开发效率

为什么需要预构建?

首先 Vite 是基于浏览器原生 ES 模块规范实现的 Dev Server,不论是应用代码,还是第三方依赖的代码,理应符合 ESM 规范才能够正常运行。

  • 将其他格式(如 UMD 和 CommonJS)的产物转换为 ESM 格式,使其在浏览器通过 <script type="module"><script>的方式正常加载
  • 打包第三方库的代码,将各个第三方库分散的文件合并到一起,减少 HTTP 请求数量,避免页面加载性能劣化(请求瀑布流问题:一个import 语句即代表一个 HTTP 请求)

(3)除了开发效率,在其他三个维度上, Vite 也表现不俗

  • 模块化方面,Vite 基于浏览器原生 ESM 的支持实现模块加载,并且无论是开发环境还是生产环境,都可以将其他格式的产物(如 CommonJS)转换为 ESM。
  • 语法转译方面,Vite 内置了对 TypeScript、JSX、Sass 等高级语法的支持,也能够加载各种各样的静态资源,如图片、Worker 等等。
  • 产物质量方面,Vite 基于成熟的打包工具 Rollup 实现生产环境打包,同时可以配合Terser、Babel等工具链,可以极大程度保证构建产物的质量

(4)vite架构图

三、ES6 Module

 (1)ES6 Module 也被称作 ES Module(或 ESM), 是由 ECMAScript 官方提出的模块化规范,作为一个官方提出的规范,ES Module 已经得到了现代浏览器的内置支持。

(2)在现代浏览器中,如果在 HTML 中加入含有type="module"属性的 script 标签,那么浏览器会按照 ES Module 规范来进行依赖加载和模块解析,这也是 Vite 在开发阶段实现 no-bundle 的原因,由于模块加载的任务交给了浏览器,即使不打包也可以顺利运行模块代码

(3)ES Module 的浏览器兼容性如今已经相当好了,覆盖了 90% 以上的浏览器份额

(4)一直以 CommonJS 作为模块标准的 Node.js 也紧跟 ES Module 的发展步伐,从 12.20 版本开始正式支持原生 ES Module。也就是说,如今 ES Module 能够同时在浏览器与 Node.js 环境中执行,拥有天然的跨平台能力。 

四、ESBuild(性能利器)

Esbuild 到底在 Vite 的构建体系中发挥了哪些作用

(1)依赖预构建——作为 Bundle 工具

 首先是开发阶段的依赖预构建阶段

1、主要作用: 

  • 将其他格式(如 UMD 和 CommonJS)的产物转换为 ESM 格式,使其在浏览器通过 <script type="module"><script>的方式正常加载
  • 打包第三方库的代码,将各个第三方库分散的文件合并到一起,减少 HTTP 请求数量,避免页面加载性能劣化(请求瀑布流问题:一个import 语句即代表一个 HTTP 请求)

2、 Vite 1.x 版本中使用 Rollup 来做这件事情,但 Esbuild 的性能实在是太恐怖了,Vite 2.x 果断采用 Esbuild 来完成第三方依赖的预构建,至于性能到底有多强,大家可以参照它与传统打包工具的性能对比图

3、Esbuild 作为打包工具也有一些缺点

  • 不支持降级到 ES5 的代码。这意味着在低端浏览器代码会跑不起来。
  • 不支持 const enum 等语法。这意味着单独使用这些语法在 esbuild 中会直接抛错。
  • 不提供操作打包产物的接口,像 Rollup 中灵活处理打包产物的能力(如renderChunk钩子)在 Esbuild 当中完全没有。
  • 不支持自定义 Code Splitting 策略。传统的 Webpack 和 Rollup 都提供了自定义拆包策略的 API,而 Esbuild 并未提供,从而降级了拆包优化的灵活性。  

4、尽管 Esbuild 作为一个社区新兴的明星项目,有如此多的局限性,但依然不妨碍 Vite 在开发阶段使用它成功启动项目并获得极致的性能提升,生产环境处于稳定性考虑当然是采用功能更加丰富、生态更加成熟的 Rollup 作为依赖打包工具了 

(2)单文件编译——作为 TS 和 JSX 编译工具

1、在 TS(X)/JS(X) 单文件编译上面,Vite 也使用 Esbuild 进行语法转译,也就是将 Esbuild 作为 Transformer 来用

 2、也就是说,Esbuild 转译 TS 或者 JSX 的能力通过 Vite 插件提供,这个 Vite 插件在开发环境和生产环境都会执行,因此,我们可以得出下面这个结论

Vite 已经将 Esbuild 的 Transformer 能力用到了生产环境。尽管如此,对于低端浏览器场景,Vite 仍然可以做到语法和 Polyfill 安全。

 3、这部分能力用来替换原先 Babel 或者 TSC 的功能,因为无论是 Babel 还是 TSC都有性能问题,大家对这两个工具普遍的认知都是: 慢,太慢了

4、虽然 Esbuild Transfomer 能带来巨大的性能提升,但其自身也有局限性最大的局限性就在于 TS 中的类型检查问题。这是因为 Esbuild 并没有实现 TS 的类型系统,在编译 TS(或者 TSX) 文件时仅仅抹掉了类型相关的代码,暂时没有能力实现类型检查。 因此初始化工程的构建脚本,vite build之前会先执行tsc命令,也就是借助 TS 官方的编译器进行类型检查。

(3)代码压缩——作为压缩工具

 Vite 从 2.6 版本开始,就官宣默认使用 Esbuild 来进行生产环境的代码压缩,包括 JS 代码和 CSS 代码。

1、从架构图中可以看到,在生产环境中 Esbuild 压缩器通过插件的形式融入到了 Rollup 的打包流程中:

2、 传统的方式都是使用 Terser 这种 JS 开发的压缩器来实现,在 Webpack 或者 Rollup 中作为一个 Plugin 来完成代码打包后的压缩混淆的工作。但 Terser 其实很慢,主要有 2 个原因

  • 压缩这项工作涉及大量 AST 操作,并且在传统的构建流程中,AST 在各个工具之间无法共享,比如 Terser 就无法与 Babel 共享同一个 AST,造成了很多重复解析的过程。
  • JS 本身属于解释性 + JIT(即时编译) 的语言,对于压缩这种 CPU 密集型的工作,其性能远远比不上 Golang 这种原生语言。

 3、因此,Esbuild 这种从头到尾共享 AST 以及原生语言编写的 Minifier 在性能上能够甩开传统工具的好几十倍

五、Rollup

Rollup 在 Vite 中的重要性一点也不亚于 Esbuild,它既是 Vite 用作生产环境打包的核心工具,也直接决定了 Vite 插件机制的设计。

(1)生产环境 Bundle

虽然 ESM 已经得到众多浏览器的原生支持,但生产环境做到完全no-bundle也不行,会有网络性能问题。为了在生产环境中也能取得优秀的产物性能,Vite 默认选择在生产环境中利用 Rollup 打包,并基于 Rollup 本身成熟的打包能力进行扩展和优化,主要包含 3 个方面:

1、CSS 代码分割。如果某个异步模块中引入了一些 CSS 代码,Vite 就会自动将这些 CSS 抽取出来生成单独的文件,提高线上产物的缓存复用率。

2、自动预加载。Vite 会自动为入口 chunk 的依赖自动生成预加载标签<link rel="moduelpreload">

<head>
  <!-- 省略其它内容 -->
  <!-- 入口 chunk -->
  <script type="module" crossorigin src="/assets/index.250e0340.js"></script>
  <!--  自动预加载入口 chunk 所依赖的 chunk-->
  <link rel="modulepreload" href="/assets/vendor.293dca09.js">
</head>

 这种适当预加载的做法会让浏览器提前下载好资源,优化页面性能。

3、异步 Chunk 加载优化。在异步引入的 Chunk 中,通常会有一些公用的模块,如现有两个异步引入的 Chunk: A 和 B,而且两者有一个公共依赖 C,如下图

一般情况下,Rollup 打包之后,会先请求 A,然后浏览器在加载 A 的过程中才决定请求和加载 C,但 Vite 进行优化之后,请求 A 的同时会自动预加载 C,通过优化 Rollup 产物依赖加载方式节省了不必要的网络开销。

(2)兼容插件机制

无论是开发阶段还是生产环境,Vite 都根植于 Rollup 的插件机制和生态,如下面的架构图所示:

 1、在开发阶段,Vite 借鉴了 WMR 的思路,自己实现了一个 Plugin Container,用来模拟 Rollup 调度各个 Vite 插件的执行逻辑,而 Vite 的插件写法完全兼容 Rollup,因此在生产环境中将所有的 Vite 插件传入 Rollup 也没有问题。

2、反过来说,Rollup 插件却不一定能完全兼容 Vite(这部分我们会在插件开发小节展开来说)。不过,目前仍然有不少 Rollup 插件可以直接复用到 Vite 中,你可以通过这个站点查看所有兼容 Vite 的 Rollup 插件: vite-rollup-plugins.patak.dev/ 。

3、Snowpack 自研了一套插件机制,类似 Rollup 的 Hook 机制,可以看出借鉴了 Rollup 的插件机制,但并不能兼容任何现有的打包工具。如果需要打包,只能调用其它打包工具的 API,自身不提供打包能力。

4、而 Vite 的做法是从头到尾根植于的 Rollup 的生态,设计了和 Rollup 非常吻合的插件机制,而 Rollup 作为一个非常成熟的打包方案,从诞生至今已经迭代了六年多的时间,npm 年下载量达到上亿次,产物质量和稳定性都经历过大规模的验证。某种程度上说,这种根植于已有成熟工具的思路也能打消或者降低用户内心的疑虑,更有利于工具的推广和发展。

六、为什么 Esbuild 性能极高

Esbuild 是由 Figma 的 CTO 「Evan Wallace」基于 Golang 开发的一款打包工具,相比传统的打包工具,主打性能优势,在构建速度上可以比传统工具快 10~100 倍。那么,它是如何达到这样超高的构建性能的呢?主要原因可以概括为 4 点。

  • 使用 Golang 开发,构建逻辑代码直接被编译为原生机器码,而不用像 JS 一样先代码解析为字节码,然后转换为机器码,大大节省了程序运行时间。
  • 多核并行。内部打包算法充分利用多核 CPU 优势,所有的步骤尽可能并行,这也是得益于 Go 当中多线程共享内存的优势。
  • 从零造轮子。 几乎没有使用任何第三方库,所有逻辑自己编写,大到 AST 解析,小到字符串的操作,保证极致的代码性能。
  • 高效的内存利用。Esbuild 中从头到尾尽可能地复用一份 AST 节点数据,而不用像 JS 打包工具中频繁地解析和传递 AST 数据(如 string -> TS -> JS -> string),造成内存的大量浪费。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值