Bakkac's Blog
本文目录

Why Vite

前提:

  • 浏览器只能理解原生的代码,无法理解各类现代前端框架的语法
  • 随着现代应用体积的增大,老一代构建工具webpack/Rollup/Parcel
    • 开发服务器启动非常缓慢,vite采用了现代语言工具打破JS性能瓶颈,不再处理ESM打包将工作流简化
    • HMR非常缓慢,vite在ESM上执行HMR,同时配合浏览器对结果进行缓存,让HMR速度飞快
    • 生产环境为了应用最终加载性能,仍然采用Rollup进行打包
  1. 作为构建工具,从源代码得出,浏览器可理解,高性能的代码
  2. 开发时体验优化,修改文件时热更新,快,SourceMap
  3. 部署时体验优化,打包结果要很小,删掉没用的代码,类型检查

开发环境和部署环境的差异,也就是常见的两个不同的命令,dev和build的行为差异

Vite就是法语中快的意思,发明出来就是为了性能优化

  • 开发服务器的架构设计,主要就是考虑大规模的项目依旧可以快速的HMR
  • 为了保持快速和灵活性的平衡
    • 对CPU重依赖的部分如转译,压缩,依赖解析等工作采用esbuild和SWC这样的,采用Go和Rust现代高性能语言的,现成的原生工具,自定义能力较差
    • 而其他对速度影响小的操作,如插件系统,热更新机制,配置逻辑,由Vite自身决定,使用JavaScript编写,自定义程度高
  • 部分esbuild无法编译的语法,Vite会选择性的使用babel补充
  • 构建阶段,vite不使用esbuild而是采用Rollup进行打包,因为Build时速度不那么重要,而打包产物的体积大小更重要,这一点上Rollup做的更好

Vite的功能

Vite 主要由两部分组成:

也就是开发和构建两个大的阶段,开发阶段为开发体验服务,构建阶段打包源码为浏览器可直接消费的代码,并进行一系列性能优化

项目开发阶段优化开发体验的功能,大概可以总结为两点:

另一个功能维度是文件的转译构建,而构建阶段,就是把开发者友好的代码,转译为浏览器友好的代码的过程,但这里其实开发服务器也显然需要经过转译,只不过,不同的阶段开发/生产工作负载是不同的,对于浏览器友好的标准也是不同的

Vite 的常见配置

配置的作用域:

  • 共享选项,所有作用域都会使用的通用配置
  • 服务器选项,用于开发服务器相关的配置
  • 构建选项,用于配置构建行为
  • 预览选项,用于配置预览行为
  • 依赖优化选项,用于配置依赖
  • SSR 选项,用于配置服务端渲染行为
  • Worker 选项,用于配置web worker
export default defineConfig(() => {
 return {
  plugins: [...],
  base:"/",
  resolve: {
   extensions: [...],
   dedupe: [...],
   alias: [...],
  },
  cacheDir: "node_modules/.vite-xxxx",
  
  build: {
      rollupOptions: {...},
  },
  server: {
      port: 5177,
      strictPort: true,
      host: "0.0.0.0",
      // Pre-transform requested modules more aggressively in dev.
      preTransformRequests: true,
    },
    optimizeDeps: {
      include: [...],
    },
 }
}

“作用域”先分清:plugins / resolve / cacheDir 是共享项,server 只管开发,build 只管生产构建,optimizeDeps 只管开发期依赖预打包。

  • defineConfig(() => …):函数式配置,方便按环境/命令动态返回配置。

  • plugins:Vite 插件链,负责代码转换、解析、HMR、开发服务器中间件等,顺序有影响。

  • resolve.extensions:导入时不写扩展名,Vite 按这个顺序去试。你们这里把 .ts/.tsx 放前面,是为了让“表面上像 .js 的导入”优先落到源码实现。

  • resolve.dedupe:强制这些依赖只解析到同一份实例,避免 monorepo/linked package 里出现两份 React 之类导致的上下文或 hook 问题。

  • resolve.alias:路径别名替换。@、app、api 这类是源码路径映射;@ffmpeg/* 那种是把包强行指到指定入口。

  • cacheDir:Vite 缓存目录,主要放依赖预构建结果。自定义目录通常是为了隔离不同配置/分支的缓存。

  • build.rollupOptions:构建时底层打包器参数。Vite 8 里它是 build.rolldownOptions 的别名/旧名,作用还是自定义输出、分包、外部依赖等。

  • server.port:开发服务器端口。

  • server.strictPort:端口被占用时直接失败,不自动换下一个端口。

  • server.host:监听地址;0.0.0.0 表示监听所有网卡,方便局域网/容器/WSL 访问。

  • server.preTransformRequests:它是让 dev 时更积极地预转译被请求的模块,减少冷启动瀑布。

    • 为何要用:在大型 monorepo、含较大第三方包(如 pixi、ffmpeg.wasm)或按需加载场景,默认按需优化有时会导致第一次请求超时、504 或卡顿;开启此项可改善开发体验(更快拿到可执行模块)。
    • 让 dev server 在响应模块请求前先把模块做一次预处理(transform),避免每次按需加载时触发较慢的按需优化路径。

如何编写插件

插件的生命周期:

image.png Vite 插件的生命周期钩子(Hooks)可以分为两类:Vite 专属钩子和Rollup/Rolldown 通用构建钩子。

一、 Vite 插件的生命周期(执行顺序)

  1. 初始化阶段(服务启动或打包开始时)
  • config(config, env) (Vite 专属):在解析 Vite 配置之前调用。你可以用它来修改、覆盖或者深度合并用户的 vite.config.js 配置。
  • configResolved(config) (Vite 专属):配置解析完成。此时你可以获取到最终的合并配置,通常用来把配置存到闭包里,供后续的钩子使用。
  • options(options) (通用):获取 Rollup 的基础配置。
  • buildStart(options) (通用):构建真正开始。通常在这里做一些初始化任务(比如启动一个子进程、预处理某些文件)。
  1. 开发服务器专属阶段(仅在 vite dev 模式下)
  • configureServer(server) (Vite 专属):非常核心。配置开发服务器,你可以拿到 Vite 内部的 Connect 服务器实例,用于添加自定义的后端中间件(比如写 Mock 接口、代理请求等)。
  • configurePreviewServer(server) (Vite 专属):类似于上一个,但是作用于 vite preview 启动的预览服务器。
  • handleHotUpdate(ctx) (Vite 专属):当监控的文件发生变化时触发。你可以获取到变化的模块,并自定义热更新(HMR)逻辑。比如,如果是某个特殊的 JSON 变了,你不想让整个页面刷新,而是只派发一个自定义事件给客户端。
  1. 文件请求与处理阶段(核心流水线,每个文件都会经历)

当浏览器发起一个请求,或者打包工具解析到一个模块依赖时执行:

  • transformIndexHtml(html) (Vite 专属):仅针对入口 HTML 文件。可以在这里往 HTML 里动态注入 <script>、<meta> 或者替换内容。
  • resolveId(source, importer) (通用):路径解析。告诉 Vite 这个模块名对应的真实物理路径是什么(比如把 import 'vue' 解析到 node_modules/...)。也可以用来拦截和生成“虚拟模块”。
  • load(id) (通用):加载文件。根据上一步解析出的真实路径,读取并返回文件的内容(代码字符串)。
  • transform(code, id) (通用):最频繁使用的钩子。接收上一步返回的源码,进行转换处理(比如把 TypeScript 转成 JavaScript,把 Sass 转成 CSS)。
  1. 构建结束与生成产物阶段(主要在 vite build 模式下)
  • buildEnd(error) (通用):Vite/Rollup 完成了所有模块的解析和构建。
  • (以下为 Rollup 专属的输出阶段钩子,Vite 打包时也会执行):
    • renderChunk:处理每个单独生成的 chunk(代码块)。
    • generateBundle:在把最终代码写入磁盘前调用,可以在这里删除某些输出文件,或者修改文件名。
    • writeBundle:文件已经完全写入磁盘。
  • closeBundle (通用):整个构建进程结束。

二、 Vite 插件的常见用途

“能不能在 Vite 打包的时候,顺便帮我做点什么”时,你就可以考虑写一个 Vite 插件,对照上面的生命周期,找到对应的钩子插进去即可,以下是 Vite 插件的常见用途:

  1. 框架与语法编译 (Code Transformation)

这是最基础的用途。浏览器只认识标准的 JS、CSS 和 HTML。

  • 用途:把特定的语法(如 Vue 单文件组件、React JSX、Svelte、Markdown)编译成标准的 JavaScript。
  • 代表插件:@vitejs/plugin-vue, @vitejs/plugin-react。
  • 使用钩子:主要使用 transform。
  1. 创建“虚拟模块” (Virtual Modules)

你可以在代码里 import 一个根本不存在的文件,由插件在构建时动态生成内容。

  • 用途:
    • 自动收集文件系统目录,生成路由(如 vite-plugin-pages)。
    • 将构建时间、Git Commit Hash 等环境变量注入到代码中。
    • 提取所有的 SVG 图标,合并成一个雪碧图注入到应用中(如 vite-plugin-svg-icons)。
  • 使用钩子:结合使用 resolveId(拦截不存在的路径名)和 load(返回动态生成的代码)。
  1. 开发服务器拦截与 Mock API (Mock & Dev Proxy)
  • 用途:在前端开发阶段,后端接口还没准备好,前端需要模拟数据。可以通过插件直接在 Vite 开发服务器上拦截指定的请求,返回 JSON 假数据。
  • 使用钩子:configureServer。
  1. HTML 动态注入 (HTML Manipulation)
  • 用途:
    • 根据环境变量(开发/生产),在 <head> 里注入不同的统计代码(如 Google Analytics、百度统计)。
    • 做 SEO 优化,动态生成 <meta> 标签。
    • 压缩 HTML 缩小体积。
  • 代表插件:vite-plugin-html。
  • 使用钩子:transformIndexHtml。
  1. 自动化构建增强 (Automated Workflow)
  • 用途:
    • 按需自动导入:再也不用手动写 import { ref, reactive } from 'vue',由插件在代码编译前自动帮你加上。
    • 组件自动注册:UI 库组件按需引入。
  • 代表插件:unplugin-auto-import, unplugin-vue-components。
  • 使用钩子:transform。
  1. 代码压缩与清理 (Optimization & Cleanup)
  • 用途:
    • 在生产环境打包时,自动清除代码里的 console.log 或 debugger。
    • 对打包生成的图片进行无损压缩。
    • 分析打包产物大小(生成包体可视化报告)。
  • 使用钩子:transform (清理代码),generateBundle 或 closeBundle (产物分析和图片压缩)。