vite 底层解析

vite

目前大多数框架的前端构建工具都已经被vite取代,相信你已经使用过vite了。可是在使用过程中,vite对我来说一直是模糊的,现在就来一探究竟,为啥它更好?

接下来我将为从以下几点出发,究其原理

一、原生ES模块

1、Common JS

  • 文件即模块,每一个文件都是一个 Module 实例,所有代码都运行带模块作用域,不会污染全局作用域(因为CommonJS 输出的是值的拷贝)
  • 所有文件运行时同步加载,模块加载的顺序是按照其在代码中出现的顺序,加载完再执行。
  • 每个模块加载一次后就会被缓存。后续require时,①先检查缓存中是否存在,②如果缓存中没有,检查是否是核心模块,如果是就直接加载,③如果不是核心模块,检查是否是文件模块,解析路径,根据解出的路径定位文件,然后执行,④如果以上都不是,沿当前路径向上级逐级递归,直到根目录的 node_modules 目录
  • CommonJS 模块直接放在浏览器中是无法执行的,所以要想在浏览器中运行,需要使用额外的工具(browserify

2、ES Module

  • 浏览器支持: ES6 Module 也被成为 ES Module,是由ESMAScript 官方提出的模块化规范。目前已经得到了现代浏览器的支持。如果在HTML中加入type ="module"属性的 script 标签,那么浏览器会按照 ES Module 规范来进行以来加载和模块解析,这也是 vite 在开发阶段实现 no-bundle 的原因。由于浏览器可以加载模块,所以即使不打包可以顺利运行模块代码。
  • 动态导入,按需加载
js 复制代码
<script type="module">
 (async () => {
   const moduleSpecifier = './lib.mjs';
   const {repeat, shout} = await import(moduleSpecifier);
   repeat('hello');
   // → 'hello hello'
   shout('Dynamic import in action');
   // → 'DYNAMIC IMPORT IN ACTION!'
 })();
</script>
  • import map:支持绝对路径(www.baidi.com)、相对路径(./module),同时支持直接写一个第三方包名,如 react、lodash。前两种都是浏览器原生支持的,但是对于第三中,放在浏览器中是无法直接执行的,ESM 解决了这个问题,这里不详细解释,可自行搜索

vite 利用浏览器对原生ES模块的支持,采用按需加载的方式,每次请求一个模块,Vite仅仅返回该模块的内容,而不是整个应用的捆绑文件。

但是!webpack是通过捆绑的方式,将所有模块打包成一个或多个文件,这意味着什么,意味着我们在启动项目是需要对整个应用进行打包,会导致长时间的等待

二、预构建

1、预构建解决了什么问题

  • 将其他格式(例如 UMD 、CommonJS)的产物转换为 ESM 格式,使其在浏览器通过 <script type = "module"><script/> 的方式正常加载。举个例子:大名鼎鼎的 React 就是基于 CommonJS ,而这种格式在vite中是无法直接运行的,但是Vite 没有办法限制第三方的规范,所以改变不了别人,那就改变自己,内部转换
  • 打包第三方库的代码,将各个第三方库分散的文件合并在一起,减少 HTTP 请求,避免页面加载性能劣化。举个例子:loadsh-es 是ES 版本,vite可以直接运行,但实际上,在它加载时会发出特别多的请求,导致页面加载的前几秒几乎处于卡顿状态。每次 import 都会触发一个新的文件请求,如果不加控制,会触发成百上千个网络请求,而 Chrome 对同一个域名在只能同时支持 6 个 HTTP 并发请求,导致页面加载十分缓慢。在加入依赖预构建之后,loadsh-es 的代码被打包成一个文件,这样请求的数量会减少很多,减轻浏览器压力,页面加载速度变快。

三、双引擎(EsBuild 和 Rollup)

1、EsBuild 预构建

esbuild 在 Vite 中的作用集中在提升构建速度、优化依赖处理和简化开发体验

  • 快速构建
    esbuild 是用 go 编写的构建工具,具有极快的编译速度。使得 Vite 在开发环境中可以快速响应文件变更,提供及时反馈
  • 多核并存
    js 是单线程串行,esbuild 直接新开一个线程 ,多线程并行,充分发挥多核优势
  • 代码压缩,减少内存
    esbuild 提供了一个 minify 配置允许用户去压缩代码体积

2、Rollup 打包

vite 选择在生产环境中利用Rollup打包,并基于Rollup 本身成熟的打包能力进行扩展和优化

  • css代码分割。如果某个异步模块中引入了一些 CSS 代码,Vite 就会自动将这些 CSS 抽取出来生成单独的文件,提高线上产物的缓存复用率。
  • 自动预加载。Vite 会自动为入口 chunk 的依赖自动生成预加载标签。这种适当预加载的做法会让浏览器提前下载好资源,优化页面性能。
  • 异步Chunks 加载优化。在异步引入的 Chunk 中,通常会有一些公用的模块,如现有两个异步引入的 Chunk: A 和 B,而且两者有一个公共依赖 C一般情况下,Rollup 打包之后,会先请求 A,然后浏览器在加载 A 的过程中才决定请求和加载 C,但 Vite 进行优化之后,请求 A的同时会自动预加载 C,通过优化 Rollup 产物依赖加载方式节省了不必要的网络开销。
  • 多产物配置。同一个入口文件,打包出多种格式的产物
javascript 复制代码
const buildOptions = {
  input: ["src/index.js"],
  // 将 output 改造成一个数组
  output: [
    {
      dir: "dist/es",
      format: "esm",
    },
    {
      dir: "dist/cjs",
      format: "cjs",
    },
  ],
};

export default buildOptions;
  • 多入口配置,将 input 设置为一个数组或者一个对象:
javascript 复制代码
{
  input: ["src/index.js", "src/util.js"]
}
// 或者
{
  input: {
    index: "src/index.js",
    util: "src/util.js",
  },
}

四、热模块更新(HMR)

vite 热更新过程

  1. 创建一个 websocket 服务端和 client 文件,启动服务
  2. 通过 chokidar 监听文件变更
  3. 当代码变更后,服务端进行判断并推送到客户端
  4. 客户端根据推荐的信息执行不同操作的更新

webpack 热更新过程

  1. 使用 webpack-dev-server (后面简称 WDS)托管静态资源,同时以 Runtime 方式注入一段处理 HMR 逻辑的客户端代码;
  2. 浏览器加载页面后,与 WDS 建立 WebSocket 连接;
    Webpack 监听到文件变化后,增量构建发生变更的模块,
  3. 通过 WebSocket 发送 hash 事件;
  4. 浏览器接收到 hash 事件后,请求 manifest 资源文件,确认增量变更范围;
  5. 浏览器加载发生变更的增量模块;
  6. Webpack 运行时触发变更模块的 module.hot.accept 回调,执行代码变更逻辑;
  7. done。
相关推荐
王小金Ryan3 天前
开发一个Vite插件,给所有DOM节点插入自定义属性
vue.js·vite
前端霸王防脱发洗发水5 天前
Vite常用插件配置
javascript·vue.js·vite
friend_ship8 天前
Vue3.0都有哪些新特性及优化点
vue.js·vite·vue3.0·es6新特性·proxy响应式对象
jason_yang8 天前
vue3复习-源码-迷你版vite
vue.js·vite
jason_yang8 天前
vue3复习-源码-编译原理-自定义vite插件
vue.js·vite
小霖家的混江龙8 天前
Vite 打包 H5 如何注入版本号
前端·vite
web_code9 天前
vite依赖预构建(源码分析)
前端·面试·vite
yinshimoshen17 天前
基于vite实现基本的浏览器兼容解决方案
前端·vite
applebomb21 天前
vite server正则表达式
正则·vite·proxy·regexp·转发·server
o翔哥o24 天前
我把大型团队项目从 vite 前端迁移到了 rsbuild,收益如何?
前端·vite·前端工程化