【打包工具】- Vite 和 webpack 原理、优缺点对比

4,824 阅读7分钟

前言

  • 整个系列分成三篇文章,此文章为第一篇,简单的从原理方面分析下 Vite 和 webpack,分析一下他们各自的优缺点;
  • 接下来两篇会从源码层面分析它们是如何各自做到自己的功能。

打包工具

  • 打包工具诞生的原因,前端工程化、模块化的处理
  • 浏览器 对 ES Modules 有兼容问题
  • 随着前端项目的日渐复杂,模块越来越多,请求的数量也逐渐增多

webpack

  • webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。
  • 当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容

webpack 优点

  • 首屏加载、懒加载
    • 由于 dev 启动过程中已经完成整个打包操作,直接将构建好的首屏内容发送给浏览器,不存在性能问题;
    • 也是由于进行了打包操作,所以的依赖在 dev 构建过程中都得以处理,懒加载也不存在问题

webpack 缺点

  • 复杂应用的情况下:本地启动时间会比较长、热更新的反应速度比较慢
    1. 本地启动时间长:由于本地开发环境,webpack 也会先进行打包,然后再在服务器运行项目,所以如果项目庞大就会出现启动慢的情况
      • webpack 的打包机制,webpack 在 dev 启动的时候,会将项目中各种类型的源文件,转化供浏览器识别的 js、css、img 等文件,并且建立源文件之间的依赖关系,合并为少量的几个文件输出
      • webpack 在构建的过程中,会涉及大量的文件 IO、源文件文件转换等操作;
      • 并且在分解依赖图的时候,还需要进行遍历操作、文件操作;
      • 所以如果项目越大,所需要花费的时间也自然就多,暂且无需多言,下一篇文章会详情分析
    2. 热更新慢
      • 一些打包器的开发服务器将构建内容存入内存,这样它们只需要在文件更改时使模块图的一部分失活,但它也仍需要整个重新构建并重载页面。
      • 这样代价很高,并且重新加载页面会消除应用的当前状态,所以打包器支持了动态模块热替换(HMR):允许一个模块 “热替换” 它自己,而不会影响页面其余部分。这大大改进了开发体验
      • 然而,在实践中我们发现,即使采用了 HMR 模式,其热更新速度也会随着应用规模的增长而显著下降。

Vite

  • Vite 的原理是借助了浏览器对 ESM 规范的支持

  • Vite 通过一开始将应用中中的模块区分为 依赖源码 两类,改进了开发服务器启动时间

    • 依赖:大多为在开发时不会变动的纯 JavaScript。一些较大的依赖(例如有上百个模块的组件库)处理的代价也很高。依赖也通常会存在多种模块化格式(例如 ESM 或者 CommonJS);
    • Vite 将会使用 esbuild 预构建依赖。esbuild 使用 Go 编写,并且比以 JavaScript 编写(比如 webpack 使用 node 编写)的打包器预构建依赖快 10-100 倍。
    • 源码:通常包含需要被转换的文件,例如 JSX,CSS 或者 Vue/Svelte 组件;Vite 以 原生 ESM 方式提供源码,让浏览器接管打包程序的部分工作,Vite 只需要在浏览器请求源码的时候进行转换并且按需提供源码。

Vite 优点

  • Vite 启动项目快、热更新快

    1. 启动快

      • 只启动一台静态页面的服务器,对文件代码不打包;
      • Vite 的原理是借助了浏览器对 ESM 规范的支持;
      • Vite 无需进行 bundle 操作,Vite 项目,源文件之间的依赖关系通过浏览器对 ESM 规范的支持来解析;
      • 所以在启动过程中,只需要进行一些初始化的操作,其余全部交由浏览器处理,所以项目启动非常之快;
      • 所以即使是启动大型项目,也不会出现卡顿的现象。
    2. 热更新快

      • 本地开发环境,在监听到文件变化以后,直接通过 ws 连接,通知浏览器去重新加载变化的文件;
      • 在 Vite 中,HMR 是在原生 ESM 上执行的;
      • 编辑一个文件时,Vite 只需要精确地使已编辑的模块与其最近的 HMR 边界之间的链失活(大多数时候只是模块本身),使得无论应用大小如何,HMR 始终能保持快速更新;
      • 源码缓存:Vite 同时利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):源码模块的请求会根据 304 Not Modified 进行协商缓存,
      • 依赖模块缓存:解析后的依赖请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存,因此一旦被缓存它们将不需要再次请求。

Vite 缺点

  • 首屏加载慢、懒加载慢

    1. 首屏加载

      • 没有对文件进行 bundle 操作,会导致大量的 http 请求
      • dev 服务运行期间会对源文件做转换操作,需要时间
      • 尽管预构建很快,但是也会阻塞首屏的加载
      • Vite 需要把 webpack dev 启动完成的工作,移接到了 dev 响应浏览器的过程中,时间加长
      • 但是由于缓存的存在,当第一次加载完成之后,再次 reload 的时候性能会有所提升
    2. 懒加载

      • 和首屏加载一样,动态加载的文件需要对源文件进行转换操作
      • 还可能会有大量的 http 请求,懒加载的性能同样会受到影响

Vite 生产环境

  • 需要打包:Vite 生产环境还是需要进行打包的,为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)
  • HTTP 请求过多:尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)
  • Vite 提供开箱即用命令:要确保开发服务器和生产环境构建之间的最优输出和行为一致并不容易。所以 Vite 附带了一套 构建优化 的 构建命令,开箱即用
  • 使用 Rollup 打包
  • 虽然 esbuild 快得惊人,并且已经是一个在构建库方面比较出色的工具,但一些针对构建 应用 的重要功能仍然还在持续开发中 —— 特别是代码分割和 CSS 处理方面。
  • 就目前来说,Rollup 在应用打包方面更加成熟和灵活。
  • 尽管如此,当未来这些功能稳定后,也不排除使用 esbuild 作为生产构建器的可能

总结

构建理念不同

  • Vite:依赖预构建,开发期间启动一个服务器,index.html 作为入口文件,所以 index.html 中需要有 <script type="module" src="./src/main.tsx"></script> ;index.html 为源码和模块图的一部分。

  • webpack:会在内部构建依赖图。webpack 会默认将 ./src/index.js 作为构建的开始;进入入口后,webpack 找出有哪些模块和库是入口起点(直接和间接)依赖的。

Vite

  • 非打包构建,不必打包所有内容
  • 基于路由的代码拆分知道代码需要实际加载哪些部分,浏览器请求源文件时,再进行转换
  • 开发构建:分为依赖和源码
  • 依赖是第三方依赖,从 node_modules 文件夹中导入的 JavaScript 模块,使用 esbuild 处理
  • 源码即是业务源代码
  • 现代浏览器中的原生 ES 模块支持,浏览器在开发中承担打包工作

webpack

  • 识别入口文件
  • 逐级递归识别依赖,构建依赖图谱
  • 分析代码、转换代码、编译代码、输出代码
  • 输出打包后的代码

后续

  • 后续会分别对 webpackVite源码进行解读,关注作者不迷路哦!