vite原理浅析-dev开发环境(对比webpack)

vite原理浅析-dev开发环境(对比webpack)


vite webpack 构建工具

首先 vite 不是打包工具,可以理解为一个项目开发启动器和打包工具启动器,分别对应 dev 的开发环境和 prd 的生产环境。

  • 开发环境的特点是能够解析模块依赖,sourcemap,快速热更新。
  • 生产环境的特点是丑化压缩依赖(尽可能减小体积),按需加载,浏览器兼容,将多个本地文件打包成一个 bundle 或多个第三方依赖打包成一个 vender(减少请求及更快的解析),及一些优化项等等

可以发现,dev 和 prd 环境 其实功能差异很大,所以分为两篇

首先 vite 之前的 vue、react 脚手架都一样,底层是基于 webpack 实现的,dev 对应 webpack-dev-server,prd 对应 webpack

webpack-dev-server 如何让浏览器能够解析模块依赖呢?

问题背景:比如你在源代码内写的 require(‘xx’),浏览器是不认识 commonjs 语法的。即使浏览器比较新可以解析 esm,但浏览器不知道要去 node_modules 里去找依赖,比如这种import vue from 'vue' 的写法。所以解析 cjs 或 esm 的语法 都是 webpack 来帮我们处理的,最终打包成一个 bundle,用 script 标签来加载,这样就可以解决兼容问题了

查看一个 webpack-dev-server 打包后的一个 vue 应用的入口 index.html

  • 可以发现做法是:把所有依赖(包括第三方库)打包成一个 bundle(main.cb1fd56.js,项目不算大,体积 11Mb),然后通过 script 标签来访问
  • main.cb1fd56.js 里面有很多 webpack 的 api 及代码注入
<!DOCTYPE html>
<html>
  <head>
    ...
  </head>
  <body>
    <div id="app"></div>
    <script type="text/javascript" src="/js/main.cb1fd56.js"></script>
  </body>
</html>

sourcemap 方面

因为是打包过的 多个文件合成了 1 个,所以 sourcemap 需要额外处理,才方便定位到具体的文件的具体行数,所以 webpack 的文件打包出来会非常大, 此处 vite 会简单很多

热更新方面

当项目变大后,热更新会越来越慢(因为每次都要重新打包),项目的启动速度也会越来越慢。vite 在热更新方面有极大的提升

进入正题,浅析 vite 原理

准备以下几个方面入手

  1. vite 基本原理-浏览器原生支持 esm
  2. vite 热更新和 dev 构建为什么快?
  3. vite 热更新是如何处理的?
  4. sourcemap 是如何处理的?
  5. vite 对源文件做的转换和处理?(比如让浏览器知道去 node_modules 里面去找依赖)
  6. vite 第一次构建为什么会慢?
  7. 为什么需要做依赖预构建?
  8. 公共依赖部分的处理?(比如 a、b、c 3 个文件 同时依赖一个 x 包)
  9. 一些 webpack 用习惯的功能该如何处理?(比如某些 webpack api,loader,plugin)

vite 基本原理-浏览器原生支持 esm

vite 基本原理是-浏览器原生支持 esm 好处:可以不用打包(热更新快的原理)浏览器直接可以识别 esm 模块(import 导入 export 导出)

  • 注意点:不能识别 commonjs(require、module.exports)。所以源代码内都要用 esm 开发
  • esm 的好处是可以做 tree shaking,让源代码的体积变得更小,也是 js 未来的主流。
    • 过去的年代,js 只能用 script 标签引入模块,后面有了一些“hack”包,可以支持类似 require 的写法,比如 amd 模块。在后来 commonjs 标准借助 node 诞生,commonjs 还是有缺点,是运行时的,动态的,不支持 tree shaking

vite 热更新和 dev 构建为什么快?

首先快慢是看和谁对比,此处一般指的是和 webpack 的 dev-server 对比。

webpack-dev-server 慢的原因是,每次热更新都要重新打包。当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。我们开始遇到性能瓶颈 —— 使用 JavaScript 开发的工具通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用 HMR,文件修改后的效果也需要几秒钟才能在浏览器中反映出来。

vite 不用打包,只需处理一些源代码的路径问题(后面会讲)和预构建(后面会讲),所以几乎可以秒开 和 快速热更新,并且项目热更新的速度不会随项目变大而变慢,因为 vite 不用重新打包,只需更新对应的文件即可

vite 热更新是如何处理的?

大致过程和 webpack-dev-server 的差不多,但是 vite 可以不用打包,只需要更新修改过的文件即可

过程:

  • 起 2 个服务:客户端服务,websocket 服务,
  • 监听本地文件变更,区分变更的类型,然后做相应的处理,通过 ws 通知客户端服务,客户端服务在去加载新的资源或刷新网页
  • 热更新时,不同的文件有不同的处理方式
    • 当你只修改了 script 里的内容时:不会刷新,Vue 组件重新加载(会重新走生命周期)
    • 当你只修改了 template 里的内容时: 不会刷新,Vue 组件重新渲染(不会重新走生命周期)
    • 样式更新,样式移除时:不会刷新,直接更新样式,覆盖原来的
    • js 文件更新时:网页重刷新

sourcemap 是如何处理的?

分为 2 块

  1. 用户写的源代码,vite 不会对进行打包,所以 vite 的 sourcemap 要简单很多
  • dev 环境下 vite 解析出来的单个.vue 文件 几乎和原代码一样大,而 webpack 因为要注入代码和 sourcemap 和热更新代码,所以会很大。一个几 kb 的.vue 文件,webpack dev 环境去访问,至少会有 70 多 kb 的大小!
  1. 第三方依赖的话,在预构建阶段,借助 esbuild 打包好(sourcemap 有独立开源库处理)

vite 对源文件做的转换和处理(比如让浏览器知道去 node_modules 里面去找依赖)

贴一段 vite 解析后的 main.js 的代码,就会很清楚

  • vite 会去转换依赖的路径,让浏览器可以通过文件路径访问到
// vite处理前
import { createApp } from 'vue'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/antd.less'
import './assets/css/common2.less'
import './permission'
import { i18n } from './i18n'
import App from './App.vue'
...

// vite处理后
import { createApp } from '/node_modules/.vite/vue.js?v=9c1d7fbb'
import Antd from '/node_modules/.vite/ant-design-vue.js?v=9c1d7fbb'
import '/node_modules/_ant-design-vue@3.0.0-beta.5@ant-design-vue/dist/antd.less'
import '/src/assets/css/common2.less'
import '/src/permission.js'
import { i18n } from '/src/i18n.js'
import App from '/src/App.vue'
...

vite 第一次构建为什么会慢?碰到大模块怎么处理?(例如有上百个模块的组件库)

第三方依赖多一点的项目的小伙伴,应该能发现,vite 的第一次构建,其实是不快的。但第一次之后,以后重启项目都会很快,1s 内就可以完成。

  • 第一次之后快的原因:
    • 因为有缓存,缓存是放在/node_modules/.vite 文件内的。
    • 如果某些时候碰到 依赖不更新,可以在 vite 命令后,加上—force,会自动删除 node_modules 的.vite 文件,然后重新构建
  • 第一次构建慢的原因:vite 需要做 依赖预构建

为什么需要做依赖预构建?

以下 2 点来源于 vite 原文:

  1. 需要兼容 CommonJS 和 UMD:
    开发阶段中,Vite 的开发服务器将所有代码视为原生 ES 模块。因此,Vite 必须先将作为 CommonJS 或 UMD 发布的依赖项转换为 ESM。

    当转换 CommonJS 依赖时,Vite 会执行智能导入分析,这样即使导出是动态分配的(如 React),按名导入也会符合预期效果:

// 符合预期
import React, { useState } from "react";
  1. 性能: Vite 将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。

    一些包将它们的 ES 模块构建作为许多单独的文件相互导入。例如,lodash-es 有超过 600 个内置模块!当我们执行 import { debounce } from 'lodash-es' 时,浏览器同时发出 600 多个 HTTP 请求!尽管服务器在处理这些请求时没有问题,但大量的请求会在浏览器端造成网络拥塞,导致页面的加载速度相当慢。

    通过预构建 lodash-es 成为一个模块,我们就只需要一个 HTTP 请求了!

简单总结成 2 点:

  1. 碰到大模块怎么处理?(例如有上百个模块的组件库)vite 需要做预解析,打包成一个 bundle(减少请求,否则要请求上百个模块)
    • 预构建是使用 esbuild 打包的, esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍。
  2. 依赖也通常会存在多种模块化格式(例如 CommonJS 或者 UMD),需要通过 esbuild 将依赖打包为 ESM(浏览器只能识别 ESM 模块)

公共依赖部分的处理(比如 a、b、c 3 个文件 同时依赖一个 x 包)

  • 如果是 esm 的依赖包,dev 环境中,浏览器会自动处理,公共的 x 包只会下载和解析一次
  • 如果是 cjs 或 umd 的包,vite 会有一个预构建的过程,会先把他们转成 esm 包,然后同上

一些 webpack 用习惯的功能该如何处理?(比如某些 webpack 提供的 api,loader,plugin)

比如有小伙伴想迁移之前 webpack 的项目到 vite,需要注意的点:

  1. webpack 自身提供的 api(https://webpack.docschina.org/api/module-methods/#require), vite 肯定是不支持的
  2. vite 中不支持 cjs(不支持 require 等模块导入导出的方法),还有 AMD 的 define 语法,需要转成 esm.(第三方依赖的话,vite 会自动转成 esm)
  3. 浏览器已经原生支持 import 动态导入(能支持() => import('xx')),所以统一把 webpack 中可能有的写法 require.ensure 都改成 import():https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import
    • 此处有一点区别,webpack 提供的 import() 除了能加载 esm 外,还能加载非标准 esm(比如 cjs,umd 等),是 webpack 自己做的处理。vite 则用的是原生的 import() 只支持标准 esm 模块 (2 者都支持代码拆分,prd 是 rollup 来支持)
    • 导入多个模块的话:vite 有一个import.meta.glob的方法,可以从文件系统导入多个模块 :https://cn.vitejs.dev/guide/features.html#glob-import (webpack 会用自己实现的 api,这里要换成 vite 支持的 api)

loader 方面:

  • vite 没有 loader 的接口,基本功能都内置好了
  • css 的处理 和 预处理器部分,都内置好了,如果是要支持预处理器的话 需要安装对应依赖
# .scss and .sass
npm install -D sass

# .less
npm install -D less

# .styl and .stylus
npm install -D stylus

如果是用的是单文件组件,可以通过`<style lang="sass">`(或其他预处理器)自动开启。

Vite Sass Less 改进了`@import`解析,以保证 Vite 别名也能被使用。另外,`url()`中的相对路径引用的,与根文件不同目录中的 Sass/Less 文件会自动变基以保证正确性。
  • dev 的 css,最终会做为 style 标签,放在 head 内
  • 图片和字体部分,作为静态资源处理。类似 file-loader,处理链接就好了
    • 比如本地的图片资源,源代码内,写的是相对路径(../assets/image/hll.png)或别名(@/assets/image/hll.png),最终会被 vite 解析成 服务器可以找到的 准确的路径,如:<img src="/src/assets/image/hll.png">
  • 更加定制化的需求
    • 可能暂时 webpack 更合适,比如,你想支持 .myVue 或 .myReact 的文件,这种情况,可以写 webpack 的 loader 的来支持。

plugin 方面:

  • dev 是开发环境,不需要做打包优化,所以 dev 几乎不需要写 plugin。更多的需求在 prd 环境用 rollup 打包,vite 能支持插件的编写:https://cn.vitejs.dev/guide/api-plugin.html ,也可以写 rollup 插件 (此部分会重点在 下一篇在讲)

总结

本篇描述的是 dev 模式下的 vite

  1. vite 基本原理是无需打包,依赖浏览器原生支持 esm 去解析模块。
  • 不过需要高版本浏览器,chorme 都要 61 以上,支持动态 import 要 chorme63 以上
  1. 构建速度和热更新速度都非常非常快,热更新速度不会随项目变大而变慢
  • 但第一次构建还是需要时间,之后就很快。第一次会做依赖预构建,用的 esbuild(速度很快)

为什么 webpack 做不到,webpack 必须得打包,并且打包出来的结果很丑,结构很乱 有很多注入代码?

  • 因为 webpack 诞生在 es6 出来之前,当时没有 esm 的概念,webpack 只能自己实现 类似 require 的功能函数,并且利用 iife + 函数作用域,确保模块之前的作用域不会冲突

本文转载自 https://juejin.cn/post/7050293652739850271

© 2025 Niko Xie