Vite知识体系|青训营笔记

80 阅读4分钟

Vite简介

Vite是一种新型的前端构建工具,基于ESBuild和Rollup。在开发环境依靠浏览器自身的ES Module编译功能去解析importdev server对模块进行编译,按需返回,完全去掉了打包的步骤。同时在开发环境拥有高效的模块热更新(HMR),且热更新的速度不会随着模块的增多而变慢,显著提升开发体验。

主要有两部分组成:

  • 开发构建:基于浏览器原生的ES Module能力来提供源文件,同时内置了高效的HMR。
  • 生产构建:生产环境使用Rollup进行打包,Vite提供指令来优化构建打包过程。

Vite特点

  • 快速的冷启动:No Bundle + ESBuild预构建。
  • 即时的模块热更新:基于ES Module的HMR,同时利用浏览器缓存策略提升速度。
  • 真正的按需加载:利用浏览器对ES Module的支持,实现真正的按需加载。

浏览器的支持

  • 开发环境:Vite需要在支持原生ES Module的导入的浏览器上使用。
  • 生产环境:浏览器需要支持通过script标签引入原生ES Module。可以通过官方插件  @vitejs/plugin-legacy支持旧浏览器。

image-20220320223455897

Vite主体流程

image-20220322000610880

Vite原理

当声明一个script标签类型为module时,比如:

xml
复制代码
 <script type="module" src="/src/main.js"></script>

浏览器解析资源的时候,会往当前域名发起一个HTTP请求,获取main.js文件。然后发现main.js内部含有import引入的模块,所以又会发起HTTP请求获取模块的内容。而Vite在启动项目阶段,会启动一个Koa dev server拦截浏览器对ES Module的请求,dev server对模块进行处理后,将模块加载到浏览器中即可。因此跳过了整体打包的阶段,而且是按需加载的。

请求拦截原理

当碰到import时,浏览器会发起一个HTTP请求获取模块内容。而dev server在收到请求之后,会拦截该请求,并对模块进行处理之后返回相应的结果。

重写模块路径

由于浏览器只能识别  ./、../  这样开头的相对路径以及  /  开头的绝对路径,而开发过程中经常会引入node_modules下的模块,浏览器无法识别和加载。因此Vite的dev server在请求拦截时通过es-module-lexer和magic-string这两个库对模块的路径进行重写。

HMR原理

Vite的热更新原理,其实就是在客户端与服务端之间建立了一个websocket链接监听文件的改变,当文件被修改时,dev server发送消息通知客户端修改相应的代码。

热更新主体流程:

  • 创建一个websocket服务端和client文件,启动服务。

    项目启动后Vite会在处理html时将client注入。

    image-20220323000909624

  • 通过chokidar的watch方法创建一个监听对象watcher,监听文件的变更。

    dart
    复制代码
       const watcher = chokidar.watch(path.resolve(root), {
         ignored: [
           '**/node_modules/**',
           '**/.git/**',
           ...(Array.isArray(ignored) ? ignored : [ignored])
         ],
         ignoreInitial: true,
         ignorePermissionErrors: true,
         disableGlobbing: true,
         ...watchOptions
       }) as FSWatcher
       ....
       watcher.on('change', async (file) => {
     ​
       })
       watcher.on('add', (file) => {
       })
       watcher.on('unlink', (file) => {
       })
    
  • 当代码变更后,服务端进行判断处理并推送到客户端。

    客户端根据推送消息的类型执行不同的更新操作。

client对推送消息类型的处理

image-20220323001117945

Vite的优化

依赖预构建

Vite会对依赖进行预构建有两个原因:

  • CommonJS和UMD兼容性:由于Vite是基于浏览器原生支持ES Module的能力去实现的,但是在开发阶段中,Vite将所有代码视为原生ES Module模块,因此Vite必须提前将CommonJS和UMD发布的依赖项转换为ES Module,并缓存入node_modules/.vite。
  • 减少模块和请求的数量:Vite将有许多内部模块的ESM依赖关系转换为单个模块,以此来提高页面的加载性能。一些包将他们的ES Module构建作为许多独立的文件相互导入。比如lodash-es有超过600个内置模块。当解析 import { debounce } from 'lodash-es' 时,浏览器同时发出 600 多个 HTTP 请求!这些请求会造成网络拥堵,影响页面的加载。因此Vite通过预构建将lodash-es构建为一个模块,所以只需要一个HTTP请求即可。

缓存

文件系统缓存

Vite会将预构建的依赖缓存到node_modules/.vite。它根据几个源来决定是否需要重新运行预构建步骤:

  • package.json中的dependencies列表。
  • 包管理器的 lockfile,例如 package-lock.jsonyarn.lock,或者 pnpm-lock.yaml
  • 可能在 vite.config.js 相关字段中配置过的。

只有在上述其中一项发生更改时,才需要重新运行预构建。

如果出于某些原因,你想要强制 Vite 重新构建依赖,你可以用 --force 命令行选项启动开发服务器,或者手动删除 node_modules/.vite 目录。

浏览器缓存

源码模块的请求会根据304 not Modified进行协商缓存,而依赖模块则会通过Cache-Control:max-age=3253600,immutable进行强制缓存,一旦被缓存它们将不再需要再次请求。

当前存在的问题

  • 对React的支持不如Vue全面。
  • 移动端浏览器的兼容性问题。
  • Vite生态不如webpack强大。