从一个白屏问题到Vite工作原理

88 阅读4分钟

有一天测试告诉我系统白屏了,经过排查发现是aws-sdk的锅,然后发现官方的issue里也有人提出类似的问题,并且反馈的人还不少。

image.png 在翻阅了不少资料后,发现其实就是aws-sdk是一个CommonJS的包,在本地开发的时候vite会自动转换成ES模块,但是在线上环境vite用的是rollup打包而无法自动转成ES模块。就导致代码到线上环境后,浏览器无法解析CommonJS模块,页面白屏了。后面使用的解决方案是将aws-sdk.js通过cdn的方式引入到项目中来解决了这个问题。

当然,我这里说到的白屏问题只是抛砖引玉的一个案例,重点还是去探索vite的工作原理。

vite 工作原理

在vite之前,大多数的前端项目选用的是webpack来构建,但是随着项目越来越庞大和复杂时,webpack项目的本地服务器启动时间也越来越长,热更新速度也变得更慢。

其根本原因是,webpack项目在启动服务前,会将项目中所有用到的模块都进行一次编译和打包,下图表示的是所有的绿色模块都会打包生成bundle,然后再启动开发服务器。即便是热更新也会将改动到的模块及相关依赖全部编译出来。

image.png vite的出现就解决了本地开发服务器启动、热更新缓慢的问题,vite在开发阶段不需要打包,浏览器直接请求所需模块并进行实时编译,能提供极速的开发响应。

vite的工作原理我们可以从开发环境和生产环境两个方面来看,因为它在两种环境下的工作特点与原理也是有一定差异的。

开发环境

依赖预构建

Vite 会对应用中的模块区分为 依赖 和 源码 两类,它对这两种类型的模块也有不同的处理。

  • 依赖:指的是第三方依赖,也就是项目中node_modules下的JavaScript模块

Vite使用esbuild对依赖进行预构建,将以 CommonJS 或 UMD 形式提供的依赖项转换为 ES Module。esbuild 是通过 Go语言编写的,它比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。

image.png

  • 源码:指的是项目中的源代码,也就是业务代码,项目中一些.jsx、.vue、.scss等文件

在项目启动前,并不是所有的源码都需要同时被加载的。因此,Vite在浏览器请求页面时,对对应模块的源码进行转换并返回给浏览器,做到按需加载。如下图所示:

image.png

浏览器缓存技术

在开发环境下,vite还会利用 HTTP 头来加速页面的重新加载:

  • 源码模块的请求会根据 304 Not Modified 进行协商缓存;
  • 依赖模块的请求会通过 Cache-Control: max-age=31536000,immutable 进行强缓存;

因此一旦被缓存它们将不需要再次请求,以此来提高页面性能。

生产环境

尽管esbuild的速度如此之快,但是在生产环境下,vite还是使用了rollup来进行打包,主要是因为:

  • 嵌套 import 会导致发送大量网络请求,即使使用 HTTP/2.0 ,直接使用未打包的 ESM 仍然效率低下;
  • 为了在生产环境中获得最佳的加载性能,最好还是将代码打包一遍,结合 tree-shaking、懒加载和 chunk 分割等技术;
  • esbuild在代码分割、CSS处理方面仍旧有短板, rollup 在应用打包方面更加成熟和灵活。

总结

最后,总结一下,在开发和生产环境下,vite有下述不同:

开发环境生产环境
构建工具esbuildrollup
模块兼容性自动将CommonJS、UMD的模块处理为 ES Module需要自行兼容和处理
模块打包利用新一代浏览器的ESM能力,无需打包,直接请求所需模块并实时编译结合 tree-shaking、懒加载和 chunk 分割等技术,将代码打包

最后一个小坑

在我做一个面向c端用户的sdk时,错误的选择了vite。导致部分用户无法加载TAT。虽然现代浏览器都支持esm了,但是依然有那么一些用户的浏览器还停留在低版本chrome。幸亏不是啥重点需求......