Vite和webpack、rollup打包工具对比

发布于:2022-11-09 ⋅ 阅读:(856) ⋅ 点赞:(0)

Vite是什么

一种前端构建工具,由两部分组成:

  • 一个基于浏览器原生ES模块的开发服务器。利用浏览器去解析模块,在服务端按需编译返回,完全跳过了打包这个概念。同时另有Vue文件支持,还搞定了热更新,而且热更新的速度不会随着模块增加而变慢
  • 一套构建指令,它使用 Rollup 打包代码,并且是预配置的,可输出用于生产环境的高度优化过的静态资源。
  • 兼容性注意,Vite2 需要Node版本 >= 12.0.0。

rollup 、webpack、vite的区别

  • rollup更适合打包库,webpack更适合打包项目,vite基于rollup实现了热更新也适合打包项目。
  • rollup基于esm打包,打包生成的文件更小。Rollup不支持HMR(识别commonJs需要插件)
  • rollup原生支持tree-shaking,webpack2开始支持且消除效果不好。
  • webpack支持代码切割。(分包)
  • webpack支持HMR。(热更新)
  • vite在生产环境通过rollup进行打包(打包体积小),生成esm模块包。(快)
  • vite在开发环境时,基于浏览器支持esm,让浏览器解析模块,然后服务器按需编译返回。同时基于esbuild进行预构建(开发环境)打包不常变动的第三包,并进行缓存。(缓存+快)
  • Vite 同时利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情),源码模块的请求会根据 304 Not Modified 进行协商缓存,而依赖模块请求则会通过 Cache-Control: max-age,immutable 进行强缓存,因此一旦被缓存它们将不需要再次请求。
  • vite热更新,实现按需编译,按模块更新。webpack需要全部重新编译并更新。(快)

为什么选 Vite

webpack:分析各个模块之间的依赖,然后进行编译,打包后的代码在本地服务器渲染。随着模块增多,打包的体积变大,造成热更新速度变慢,文件修改后的效果也需要几秒钟甚至更长时间才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。

vite:vite启动的时候不需要分析各个模块之间的依赖关系、不需要打包编译。vite可按需动态编译缩减时间。当项目越复杂、模块越多的情况下,vite明显优于webpack。

webpack是基于nodejs构建,js是以毫秒计数。

vite是基于esbulid预构建依赖,可提高页面加载速度,esbulid是采用go语言编写的,go语言是纳秒级别的。

Vite采用了ES模块来实现模块的加载。目前基于web标准的ES模块已经覆盖了超过90%的浏览器。

Tree-shaking

tree shaking 是一个术语。通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。这个概念早在 1990 年就已经被提出,但直到ES6规范被提出之后,才被应用起来,因为它依赖于 ES6 模块语法的静态结构特性,例如 import和export,它会在运行过程中静态分析模块之间的导入导出,确定 ESM 模块中哪些导出值未曾被其它模块使用,并将其删除,以此实现打包产物的优化。

如下所示的代码是不被允许的:

if(condition) { import foo from "foo" } else { import bar from "bar" }

相反,我们必须在任何条件之外定义全局范围内的所有导入。

import foo from "foo" import bar from "bar" if(condition) { // foo() } else { // bar() }

这种语法可以确定导入后使用的任何代码,而无需先运行这些代码。(可以有效的 Tree Shaking)

为什么需要 Tree Shaking

当今的 Web 应用越来越大,浏览器处理 JavasSript 是非常耗费资源(耗时)的,如果我们能将其中的无用代码去掉,仅提供有效代码给浏览器处理,无疑会减少浏览器的负担,而 Tree Shaking 正是解决了这一痛点。

从这个角度看,Tree Shaking 属于性能优化的范畴。

通过减少 Javascript 中的无用代码,来减少文件体积,浏览器加载资源的时间就会降低,进而减少用户打开页面所需的等待时间(增强用户体验)

在 Webpack 中,启动 Tree Shaking 功能必须同时满足三个条件:

  • 使用 ESM 规范编写模块代码
  • 配置 optimization.usedExports 为 true,启动标记功能
  • 启动代码优化功能,可以通过如下方式实现:

// webpack.config.js module.exports = { entry: "./src/index", mode: "production", devtool: false, optimization: { usedExports: true, }, };

webpack5已经自带了这个功能,当打包环境为production时,默认开启tree-shaking功能。

热更新时webpack做了什么

总的来说,webpack的热更新就是,当我们对代码做修改并保存后,webpack会对修改的代码块以及该模块的依赖重新编译打包,并将新的模块发送至浏览器端,浏览器用新的模块代替旧的模块,从而实现了在不刷新浏览器的前提下更新页面。相比起直接刷新页面的方案,HMR的优点是可以保存应用的状态。当然,随着项目体积的增长,热更新的速度也会随之下降。

其中,使用webpack冷启动项目的流程是1 -> 2 -> A -> B,热更新的流程是1 -> 2 -> 3 -> 4 -> 5。热更新的大致流程如下:

  • 编辑文件并保存后,webpack就会调用Webpack-complier对文件进行编译;
  • 编译完后传输给HMR Server,HMR得知某个模块发生变化后,就会通知HMR Runtime;
  • HMR Runtime就会加载要更新的模块,从而让浏览器实现更新并不刷新的效果。

热更新时vite做了什么

热更新主要与项目编写的源码有关。前面提到,对于源码,vite使用原生esm方式去处理,在浏览器请求源码文件时,对文件进行处理后返回转换后的源码。vite对于热更新的实现,大致可以分为三步:

a. 监听文件变动

b. 读取文件内容

c. 通知浏览器做相应的更新

vite热更新的实现原理

  • 创建一个websocket服务端。vite执行createWebSocketServer函数,调用ws库创建ws服务端。
  • 创建一个ws客户端来接收ws服务端的信息。vite首先会创建一个ws client文件,然后在处理入口文件index.html时,把对ws client文件的引入注入到index.html文件中。当浏览器访问index.html时,就会加载ws client文件并执行,创建一个客户端ws,从而接收ws服务端的信息。
  • 服务端监听文件变化,发送websocket消息,通知客户端。
  • 服务端调用handleHMRUpdate函数,该函数会根据此次修改文件的类型,通知客户端是要刷新还是重新加载文件。
  • 一个小细节:vite对于node_modules的文件做了强缓存,而对我们编写的源码做了协商缓存。

总结:vite为什么比webpack快

构建速度快:Webpack 会先将代码打包,然后启动开发服务器,请求服务器时返回打包后的结果;而 Vite 是直接启动开发服务器,请求哪个模块再对该模块进行实时编译,省去了打包的过程。

热更新快:相比起webpack会对修改的模块以及该模块的依赖重新编译,vite会让浏览器帮忙做更多的事情。vite 采用立即编译当前修改文件的办法,当改动了一个模块后,仅需让浏览器重新请求该模块即可。同时 vite 还会使用缓存机制( http 缓存、 vite 内置缓存 ),加载更新后的文件内容。

Esbuild

Esbuild 是一个非常新的模块打包工具,它提供了与 Webpack、Rollup、Parcel 等工具相似的资源打包能力,却有着高的离谱的性能优势:

从上到下,耗时逐步上升达到数百倍的差异,这个巨大的性能优势使得 Esbuild 在一众基于 Node 的构建工具中迅速蹿红,特别是 Vite 2.0 宣布使用 Esbuild 预构建依赖后,前端社区关于它的讨论热度逐渐上升。

esbuild 重点提到的就是构建速度方面,为什么会比 webpack 快呢?而且不在同一个数量级。

关键因素:

语言优势

JavaScript 本质上依然是一门解释型语言,JavaScript 程序每次执行都需要先由解释器一边将源码翻译成机器语言,一边调度执行;而 Go 是一种编译型语言,在编译阶段就已经将源码转译为机器码,启动时只需要直接执行这些机器码即可。也就意味着,Go 语言编写的程序比 JavaScript 少了一个动态解释的过程。

多线程优势

Go 天生具有多线程运行能力,而 JavaScript 本质上是一门单线程语言,直到引入 WebWorker 规范之后才有可能在浏览器、Node 中实现多线程操作。

它的实现算法经过非常精心的设计,尽可能饱和地使用各个 CPU 核,特别是打包过程的解析、代码生成阶段已经实现完全并行处理。

除了 CPU 指令运行层面的并行外,Go 语言多个线程之间还能共享相同的内存空间,而 JavaScript 的每个线程都有自己独有的内存堆。这意味着 Go 中有多个处理单元,例如解析资源 A 的线程,可以直接读取资源 B 线程的运行结果,而在 JavaScript 中相同的操作需要调用通讯接口 woker.postMessage 在线程间复制数据,所以在运行时层面,Go 拥有天然的多线程能力,更高效的内存使用率,也就意味着更高的运行性能。

节制

对,没错,节制!

Esbuild 并不是另一个 Webpack,它仅仅提供了构建一个现代 Web 应用所需的最小功能集合,未来也不会大规模加入我们业已熟悉的各类构建特性。最新版本 Esbuild 的主要功能特性有:

  • 支持 js、ts、jsx、css、json、文本、图片等资源
  • 增量更新
  • Sourcemap
  • 开发服务器支持
  • 代码压缩
  • Code split
  • Tree shaking
  • 插件支持

可以看到,这份列表中支持的资源类型、工程化特性非常少,甚至并不足以支撑一个大型项目的开发需求。在这之外,官网明确声明未来没有计划支持如下特性:

  • Elm, Svelte, Vue, Angular 等代码文件格式
  • Ts 类型检查
  • AST 相关操作 API
  • Hot Module Replace
  • Module Federation(Webpack5推出的新功能)

而且,Esbuild 所设计的插件系统也无意覆盖以上这些场景,这就意味着第三方开发者无法通过插件这种无侵入的方式实现上述功能,可以预见未来可能会出现很多魔改版本。

Esbuild 只解决一部分问题,所以它的架构复杂度相对较小,相对地编码复杂度也会小很多,相对于 Webpack、Rollup 等大一统的工具,也自然更容易把性能做到极致。节制的功能设计还能带来另外一个好处:完全为性能定制的各种附加工具。

定制

回顾一下,在 Webpack、Rollup 这类工具中,我们不得不使用很多额外的第三方插件来解决各种工程需求,比如:

  • 使用 babel 实现 ES 版本转译
  • 使用 eslint 实现代码检查
  • 使用 TSC 实现 ts 代码转译与代码检查
  • 使用 less、stylus、sass 等 css 预处理工具

Esbuild 选择完全重写整套编译流程所需要用到的所有工具!这意味着它需要重写 js、ts、jsx、json 等资源文件的加载、解析、链接、代码生成逻辑。

开发成本很高,而且可能被动陷入封闭的风险,但收益也是巨大的,它可以一路贯彻原则,以性能为最高优先级定制编译的各个阶段,比如说:

  • 重写 ts 转译工具,完全抛弃 ts 类型检查,只做代码转换
  • 大多数打包工具把词法分析、语法分析、符号声明等步骤拆解为多个高内聚低耦合的处理单元,各个模块职责分明,可读性、可维护性较高。而 Esbuild 则坚持性能第一原则,不惜采用反直觉的设计模式,将多个处理算法混合在一起降低编译过程数据流转所带来的性能损耗

这种深度定制一方面降低了设计成本,能够保持编译链条的架构一致性;一方面能够贯彻性能第一的原则,确保每个环节以及环节之间交互性能的最优。虽然伴随着功能、可读性、可维护性层面的的牺牲,但在编译性能方面几乎做到了极致。

结构一致性

Esbuild 重写包括 js、ts、jsx、css 等语言在内的转译工具,所以它更能保证源代码在编译步骤之间的结构一致性,比如在 Webpack 中使用 babel-loader 处理 JavaScript 代码时,可能需要经过多次数据转换:

  • Webpack 读入源码,此时为字符串形式
  • Babel 解析源码,转换为 AST 形式
  • Babel 将源码 AST 转换为低版本 AST
  • Babel 将低版本 AST generate 为低版本源码,字符串形式
  • Webpack 解析低版本源码
  • Webpack 将多个模块打包成最终产物

源码需要经历 string => AST => AST => string => AST => string ,在字符串与 AST 之间反复横跳。

而 Esbuild 重写大多数转译工具之后,能够在多个编译阶段共用相似的 AST 结构,尽可能减少字符串到 AST 的结构转换,提升内存使用效率。

总结

单纯从编译性能的维度看,Esbuild 确实完胜世面上所有打包框架,差距甚至能在百倍之大:

耗时

性能差异

速度

产物大小

Esbuild

0.11s

1x

1198.5 kloc/s

0.97mb

Esbuild (1 thread)

0.40s

4x

329.6 kloc/s

0.97mb

webpack 4

19.14s

174x

6.9 kloc/s

1.26mb

parcel 1

22.41s

204x

5.9 kloc/s

1.56mb

webpack 5

25.61s

233x

5.1 kloc/s

1.26mb

parcel 2

31.39s

285x

4.2 kloc/s

0.97mb

Vite3新特性

  • 模板的变更, 这个更新后,使用Vite创建的Vue模板的主题与Vite的文档保持一致,也支持暗色与亮色模式,且icon从Vue的logo换成了Vite的logo。

  • Vite CLI在命令行中的样式也进行了优化

左边是Vite3.0,右边是Vite2.0,在视觉上明显3.0比2.0要好看。

除了外观之外,我们可以看到默认的端口号也发生了变化,从3000变成了5173;Local的地址从localhost变成了127.0.0.1。

  • import.meta.glob API的变化,import.meta.globAPI可以动态的导入文件,在Vite3中允许import.meta.glob被重写,具体可以参照Glob导入形式
  • Vite修复了400+issuse,整体体积变小;
  • 兼容性做了调整,Vite3 不再支持 Node 12 / 13 / 15,因为上述版本已经进入了 EOL (End-of-lif)阶段。现在你必须使用 Node 14.18+ / 16+ 版本

查看vscode扩展加载时间

ctrl+shift+p

注:学习笔记,纯分享

引用链接

本文含有隐藏内容,请 开通VIP 后查看