第一次用 Vite 跑 Vue 项目,最直接的感受是开发环境不用等了

开发环境最烦人的地方从来不是"不能用",是"总得等一下":启动要等,切分支后重启要等,改个样式保存要等,偶尔热更新失效还要再等一次。手上这个 Vue 项目是典型的后台系统,页面不算花哨,但模块很多,表单、表格、弹窗、权限、图表、富文本都有,Vue CLI 配 Webpack 刚起步时跑得挺稳,做到现在这些等待累积起来,团队的抱怨越来越多。

这种等待单看都不算什么大事,可它分散在一天里几十次操作中,会把人磨得很烦。尤其是做页面微调的时候,明明只是想看个字号或间距变化,却得在保存和刷新之间反复停顿。

对"再换一个工具"没有特别强的热情

说实话,我对"再来一个新工具"这件事没有特别强的动力。从项目角度看,Webpack 并不差,配置已经成熟,生态足够全,Vue CLI 也顺手。问题不在功能,而在开发过程里的反馈速度——这是两码事,之前很多人把"要不要用新工具"和"现在的工具好不好"混在一起讨论,其实没说到点子上。

所以我看 Vite,不是想追新,而是它正好踩在这个痛点上。大家都在说它快,我想知道这到底是宣传里的快,还是一上手就能感受到的快。

从空项目开始,先摸一遍最基础的流程

试之前先不碰手上那个复杂后台,找一台干净的机器,从零开始走一遍最基础的搭建流程,看看官方脚手架给出来的东西长什么样:

1npm init vite-app vite-playground
2cd vite-playground
3npm install
4npm run dev

命令敲完到终端打出访问地址,中间几乎没有停顿感——不是"快了一点",是几乎感觉不到编译这个动作发生过。终端打印出类似这样的内容:

1Dev server running at:
2  > Local:    http://localhost:3000/
3  > Network:  http://192.168.1.12:3000/

打开浏览器,页面几乎是秒开。作为对照,同一台机器上跑一个用 Vue CLI 起的空项目,npm run serve 之后终端要先过一遍"Building for development..."的进度条,几秒到十几秒不等,页面才会跳出来。项目越大这个差距会越明显,但即便是这种啥都没有的空项目,两边的观感差异也已经很直接。

脚手架生成的目录结构很简单,一个 index.html 放在根目录(不是塞进 public 目录深处),一个 src/main.js,一个 App.vueindex.html 里能看到这么一行:

1<script type="module" src="/src/main.js"></script>

这行才是整个原理的入口——浏览器原生认得 type="module",会自己去请求这个文件,Vite 的开发服务器只是在旁边等着接这些请求。

手动搭一遍,把 vite.config.js 的骨架过一遍

脚手架生成的项目配置太干净,业务项目不可能这么裸。为了搞清楚基础配置项都在哪,干脆在一个已有的小页面里手动接入 Vite,不用脚手架,自己装依赖、自己写配置:

1npm install vite vite-plugin-vue2 --save-dev

(团队项目还是 Vue 2.6,Vite 对 Vue 2 的支持要靠这个社区插件,官方内置的 SFC 编译支持是给 Vue 3 用的。)

vite.config.js 写出来大致是这个骨架:

1// vite.config.js
2const { createVuePlugin } = require('vite-plugin-vue2');
3const path = require('path');
4
5module.exports = {
6  plugins: [createVuePlugin()],
7  resolve: {
8    alias: {
9      '@': path.resolve(__dirname, 'src'),
10    },
11  },
12  server: {
13    port: 3000,
14    proxy: {
15      '/api': {
16        target: 'http://test-api.internal.example.com',
17        changeOrigin: true,
18        rewrite: (p) => p.replace(/^\/api/, ''),
19      },
20    },
21  },
22};

跟以前写 vue.config.js 的感觉不太一样的地方是,这份配置几乎没有"黑盒"感——没有 chainWebpack,没有一层套一层的 webpack-merge,每一项配置基本能对应到它实际影响的那个环节,读起来更像是在描述"这几件事要怎么做",而不是在改一份别人预设好的巨大配置对象。

打开 Network 面板,才真正看懂"按需编译"是什么意思

光靠感觉说"快"不够有说服力,干脆打开浏览器的 Network 面板,边操作边看请求。

先看 Vite 这边冷启动之后的请求瀑布图:第一屏加载完,能看到一长串对 .vue.js 文件的独立请求,每个文件一条记录,路径基本就是源码里的真实路径,比如 /src/App.vue/src/components/UserTable.vue/src/utils/format.js。请求的时间线看起来是并行发出去的一大片短请求,单个请求的响应时间通常在几毫秒到几十毫秒,几乎看不出排队等待的痕迹。点开某一条 .vue 文件的请求,响应内容不是原始的单文件组件源码,而是已经被 Vite 编译服务器转换过的 JS 模块,<template> 编译成了渲染函数,<style> 部分被转成了一段注入样式的辅助代码。

对比着看同一个页面在 Vue CLI + Webpack 开发服务器下的瀑布图,完全是另一种形态:请求列表里只有寥寥几条,一个巨大的 app.js(或者按路由分出来的几个 chunk),外加一个 vendor.js,请求数量少,但每一条的体积和加载时间都明显更长,尤其是首次加载或者重启之后第一次访问,app.js 这条请求要等 bundler 把整个依赖图打包完才有响应,等待感全压在这一条请求上。

这两种瀑布图放在一起,是这次试跑里最直观的一次"原理落地":Webpack 把等待成本前置到一次性打包里,Vite 把编译动作拆碎,摊到每一次浏览器发起的独立请求上,只编译请求到的那一个模块,没被请求到的模块根本不会被处理。

依赖预构建:第三方包为什么会被单独放一个目录

Network 面板里还能看到另一类请求,路径类似 /node_modules/.vite/vue.js?v=... 或者 /@modules/lodash-es.js,这是 Vite 对第三方依赖做的预构建结果。业务代码里用的第三方库,很多还是用 CommonJS 或者 UMD 写的,浏览器原生 ESM 环境认不出 require,Vite 冷启动时会先把这些依赖转换、合并成浏览器能直接 import 的 ESM 格式,缓存进 node_modules/.vite 目录。这一步现在(1.x 版本)走的是 Rollup 做转换合并,不是打包应用代码那种全量 bundle,只是把散落的 CommonJS 依赖收拢成模块化的 ESM 产物,所以速度还能接受。

第一次冷启动,如果项目里依赖不少,能看到终端打一行提示,类似:

1[vite] optimizing dependencies...
2  vue, vue-router, axios, lodash-es, dayjs

这一步会有一次短暂的等待,肉眼可见比后续启动慢一点,但只发生在依赖变化或者第一次跑的时候。改完 package.json 装了新依赖再重启,也会重新触发一次这个过程;日常改业务代码,压根碰不到这一步,启动速度不受影响。这跟 Webpack 每次重启都要重新走一遍完整打包流程是本质区别——预构建只处理 node_modules 里的第三方依赖,业务代码完全不在这一步的处理范围内,这也是它能压得比整体打包短很多的原因。

翻了下 Vite 仓库的讨论区,看到已经有人在提能不能换一个更快的转换器来做这一步,提到的候选是当时刚出来不久、用 Go 写的 esbuild,说是编译速度比现有的 JS 工具链快一个量级。目前这条路 Vite 还没有真正采纳,停留在讨论阶段,实际用的还是 Rollup,这块以后会不会换,只能先观望。

预构建这一步顺带还解决了一个容易被忽视的问题:不少 CommonJS 包内部会出现循环引用或者深层嵌套的 require 调用,直接把 require 简单替换成 import 是不成立的,必须先经过打包工具分析出完整的依赖关系图,才能产出一份浏览器认得的 ESM。翻了几个预构建产物的源码确认过这点——node_modules/.vite 目录下缓存的文件不是原始依赖代码的简单包装,而是 Rollup 把整条 CommonJS 依赖链摊平之后重新生成的产物,一个第三方包在这里可能会被拆成好几个文件,也可能好几个互相依赖的小包被合并进同一个产物里,具体怎么切完全看 Rollup 的打包结果,不是原样对应源码目录结构。这也是为什么预构建缓存一旦失效,删掉重来的效果和第一次冷启动完全一样——它本质上是一次独立的构建产出,不是简单的文件复制。

改样式和改组件逻辑,热更新的表现不完全一样

热更新这块,光说"更快"还不够细,实际操作下来能感觉到两类改动的反馈是不一样的。

改一处纯 CSS,比如把 <style> 块里某个 .cardpadding12px 改成 16px,保存之后浏览器画面几乎是瞬间跳变,没有整页刷新的白屏闪一下,组件里表单填到一半的内容、弹窗的展开状态都还保留着——这是因为 Vite 对纯样式的改动走的是 CSS 热替换,只把新的样式内容通过 <style> 标签或者对应的模块重新注入,不牵动 JS 执行上下文,页面状态自然不会丢。

改组件的 <script> 部分,比如给某个方法加一行 console.log 或者改一段计算逻辑,保存之后浏览器控制台能看到类似这样的输出:

1[vite] hot updated: /src/components/UserTable.vue

这一类更新走的是模块级别的热替换,Vite 收到文件变化通知后,只让浏览器重新请求这一个模块,浏览器端配合运行时做替换。多数情况下组件内部的响应式数据状态还在,不需要整页刷新;但如果改动涉及组件的 data 初始值结构、或者改的是根组件、main.js 这类顶层入口文件,热更新有时候接不住,会退化成整页刷新,这点跟 Webpack 下 vue-loader 的热更新表现类似,不算 Vite 独有的短板。

对比 Webpack 那边的热更新,最大的差别不在"能不能热替换"(两边都能),而在于反馈链路长短:Webpack 收到文件变化后要先增量编译,把新模块和它依赖的那部分模块关系重新计算一遍,再推给浏览器;Vite 这边因为压根没有"打包"这个中间层,文件变化几乎是直接对应到"让浏览器重新请求这一个文件",中间步骤更短,改完保存到画面变化之间的间隔明显更紧凑。项目模块越多,这个差距体感上越大,改一个用得很深的公共组件时尤其明显。

翻文档的时候还看到一个之前没接触过的底层 API——import.meta.hot。Vite 的 HMR 边界判定和处理逻辑,本质上是通过这个对象暴露给业务代码的,.vue 文件的模块级热替换之所以能自动生效,是因为 vite-plugin-vue2 在编译产物里自动注入了一段调用 import.meta.hot.accept() 的样板代码,不需要业务代码自己关心。写一个纯 .js 工具模块(不是组件)改动之后想手动接管热替换该怎么处理,试了一下官方给的示例写法:

1// src/utils/format.js
2export function formatMoney(value) {
3  return `¥${value.toFixed(2)}`;
4}
5
6if (import.meta.hot) {
7  import.meta.hot.accept();
8}

加上这几行之后,改这个文件的逻辑,页面不会因为它被别的组件引用就整页刷新,而是走模块热替换;不加这几行,普通 .js 文件的改动默认会触发一次整页刷新,因为 Vite 没法确定这个模块的新旧版本能不能安全地被替换掉、会不会遗留旧的状态。这跟 .vue 组件不一样——组件天生有明确的重新渲染边界,普通工具模块没有这层天生的划分,需不需要热替换、能不能安全替换,只能由写代码的人自己判断。项目里大部分工具函数模块没有加这几行,改了之后触发整页刷新,虽然比组件级热替换慢一点,但胜在简单可靠,暂时没有必要为了这点热更新速度去逐个手动接管。

alias、环境变量、代理这几处基础配置

光看首屏能不能起来还不够,业务项目里离不开的几处基础配置也得挨个试一遍。@ 指向 src 这类路径别名,Vite 里对应 resolve.alias,写法和 Webpack 差不多,没什么难度。环境变量这块差异要大一些:Vue CLI 那套是靠 process.env.VUE_APP_XXX,Vite 走的是 import.meta.env.VITE_XXX,变量必须以 VITE_ 开头才会被暴露到客户端,这是刻意做的限制,避免不小心把不该给前端看到的变量打进产物里。试的时候顺手建了几个环境文件对照:

1.env                # 所有环境都会加载
2.env.development    # 只在开发环境加载
3.env.production      # 只在生产构建加载

.env.development 里写一行 VITE_API_BASE=/api,代码里用 import.meta.env.VITE_API_BASE 直接能读到,改完这个值不用重启也能生效,这点跟 Vue CLI 下改环境变量往往需要重启开发服务器不太一样。

开发环境代理到测试环境接口,用的是 server.proxy,配置项写法和 devServer.proxy 基本能对上,跑起来之后代理确实通了,请求头和路径重写也符合预期。打开 Network 面板看代理请求,返回的响应头、状态码跟直接打测试环境接口时一致,没有额外的延迟感。

引入一个稍重一点的组件之后再试一次,热更新的反馈速度依然明显,没有随着项目变大明显掉下来。

.vue 文件在 Vite 下怎么被编译成浏览器能跑的东西

顺着请求瀑布图往下追,还搞清楚了一件事:一个 .vue 单文件组件请求到 Vite 开发服务器之后,具体是怎么被处理的。vite-plugin-vue2 这个插件接管了 .vue 文件的解析,大致分三步:先把文件拆成 <template><script><style> 三块;<template> 部分交给 Vue 2 的模板编译器转成渲染函数;<script> 部分该是什么就是什么,原样往下传(如果用了 lang="ts" 才会额外走一层转换,下面单说);<style> 部分则单独处理成一段自动注入 <style> 标签的辅助代码,如果用了 scoped,还会在这一步给选择器加上唯一的属性哈希。三块处理完之后拼成一个完整的 JS 模块,塞进对浏览器的响应里。整个过程只在这一个文件被请求到的时候才发生,没人访问的组件不会被编译,这也是为什么改动一个很少被用到的页面,重新请求验证时反而会有一次单独的、稍微能感觉到的编译延迟——那是它第一次被编译。

TypeScript 文件:Vite 只转语法,不做类型检查

项目里有几个文件已经是 .ts 后缀,试跑的时候验证了一下 Vite 对它们的处理方式。改一处明显的类型错误——把一个函数参数从 number 强行传成字符串——保存之后页面没有报错,控制台也没有类型相关的提示,程序照样能跑,直到运行时因为类型不对才在业务逻辑里出问题。

去翻了一下资料,这是设计上如此:Vite 开发环境下用 esbuild 处理 .ts 文件的语法转换(把 TS 特有的类型标注这些去掉,转成合法的 JS),不做类型检查,这也是转换速度快的原因之一,类型检查这一步本身很耗时,直接跳过了。想要类型报错,得单独跑一遍 tsc --noEmit,或者依赖编辑器里的 TS 语言服务实时提示,跟开发服务器本身没关系。这点跟 Vue CLI 下 ts-loader(或者 fork-ts-checker-webpack-plugin)编译时顺带做类型检查不一样,是拿了速度、放弃了这一层编译期保障,不能想当然以为控制台没报错就等于类型没问题。

旁观了一眼隔壁 React/JSX 生态的支持情况

顺带看了一下 Vite 对 JSX 的支持,倒不是打算往 React 上用,纯粹是好奇这个工具的通用性到哪一步。Vite 内置的 esbuild 转换本身支持 JSX 语法,写一个 .jsx 文件扔进去,不装额外插件也能跑起来。但要接完整的 React 开发体验——比如组件热替换(Fast Refresh)——现在还得靠社区插件,而且这块生态比 Vue 这边更早期,官方仓库里能看到的相关讨论也基本停留在"有人在探索方案"的阶段,没有形成大家默认会装的标准插件。Vue 这边能靠尤雨溪本人主导、vite-plugin-vue2 这类插件跟得紧,React 那边目前还是零散状态,这也是这次试跑顺带得到的一个印象。

生产构建产物形态

生产构建这块也单独跑了一遍:vite build 出来的产物结构看着和 Webpack 打出来的不完全一样,资源哈希、代码分割的切法有自己的风格。跑完之后 dist 目录里能看到类似这样的文件列表:

1dist/
2  index.html
3  assets/
4    index.3f2a91c4.js
5    index.8b1d7e02.css
6    vendor.6a4c50f1.js

部署到静态服务器上刷新访问,页面和接口都正常,index.html 里引用的资源路径也都对得上。开发环境下那种"按文件请求"的碎片化模块,在生产构建这一步会被重新合并、压缩,不会真的把几百个模块文件原样丢给用户去逐个请求——这点容易被"开发环境不打包"这句话带偏,误以为生产环境也是这样,实际生产构建这条路径走的是完全独立的一套流程。

代码分割这块专门对照了一下两边的切法。Webpack 4 下常见做法是配 optimization.splitChunks,按 node_modules 和业务代码的规则把公共依赖切进单独的 vendor chunk,具体切分粒度靠一堆配置项(minSizemaxInitialRequests 这些)去调。Vite 底层生产构建用的是 Rollup,默认策略要简单一些:动态 import() 引入的模块会被自动分离成独立的 chunk,这块跟 Webpack 的路由懒加载效果类似;但静态引入的第三方依赖,默认情况下不会像 Webpack 那样单独拆出一个 vendor.js,而是根据模块之间的引用关系由 Rollup 自己判断怎么合并最合理。跑了一下试跑项目的构建产物,确实观察到公共依赖被打进了和业务代码同一个 chunk 里,体积没有想象中理想,翻文档发现如果想要类似 vendor 分包的效果,得在 vite.config.js 里手动配 build.rollupOptions.output.manualChunks 去指定哪些包单独打包,不是开箱即用的默认行为。这算是这次生产构建验证里唯一一个跟直觉不太一样的地方,记下来提醒自己:换工具之后原来靠配置项达成的效果,不能想当然认为默认策略也一样。

冷启动之后,第二次启动为什么感觉更快

试的次数多了会发现一个细节:同一个项目,第一次 npm run dev 和关掉终端之后再跑一次,速度感受不完全一样。第一次因为要走依赖预构建,会有那么一两秒的等待;把开发服务器停掉重新起,如果依赖没变化,预构建的产物还缓存在 node_modules/.vite 里,直接复用,终端从敲命令到打印出访问地址的时间进一步缩短。手动把 node_modules/.vite 目录删掉之后再起服务验证过一次,明显又回到了"第一次冷启动"的等待感,终端里那行 optimizing dependencies... 又出现了一次,这也顺带解释了一件事:有同事反馈"Vite 有时候启动也不快",一问基本是刚拉完新分支、node_modules 跟着重装了一遍,缓存被清空,不算例外,是这套机制本身的正常表现。

改动依赖版本号,重新触发预构建时会发生什么

单独试了一次改依赖版本号的场景,因为这是团队日常协作里躲不开的操作。把 package.jsondayjs 的版本号从一个次版本挪到另一个次版本,重新执行一遍 npm install,回到已经在跑的开发服务器窗口,终端会自动感知到依赖变化,重新打印一次 optimizing dependencies... 的提示,浏览器这边的页面短暂显示一次"正在重新连接"的提示条,随后自动刷新,不需要手动重启命令。

这跟以前 Webpack 下改依赖版本、然后手动 Ctrl+C 杀掉进程再重新跑一遍的操作习惯不太一样——Vite 这边把"感知依赖变化、重新预构建、通知浏览器刷新"这一整套流程做成了自动衔接,中间不需要人为干预。第一次遇到这个自动刷新提示条的时候还愣了一下,以为是网络断了,后来才确认这是它主动做的事。

模块级别的热替换是按照"哪个文件变了就重新请求哪个文件对应的模块"来处理的,不需要从根组件重新走一遍完整的组件树解析,深层组件改动只影响它自己这一个模块的请求和替换,跟它在组件树里嵌了几层没有直接关系。往表格容器里套了几层——展开面板、面板里再嵌一个图表组件——改最深那层的渲染逻辑试过一次,反馈速度确实没有随嵌套加深而变慢,跟以前用 Webpack 时"改得越深、影响面越小、应该越快"的直觉不是一回事,Vite 这边的更新粒度按文件对应模块来算,层级深浅不是它关心的维度。

同时开着两个终端窗口对照的具体数字

为了避免"体感"这种主观描述显得空对空,专门用了一个笨办法:两个终端窗口并排开着,一个跑 Vite 版本的页面,一个跑 Vue CLI 版本的页面,两边内容尽量对齐(同样的路由数量、同样引入的几个组件库),拿手机秒表掐了几组时间,只是粗略参考,不是严格的性能基准测试。

冷启动到浏览器能看到首屏内容:Vite 版本大概 1 秒出头,Vue CLI 版本在 6~8 秒之间浮动,具体看当天机器负载。改一行按钮文案保存到浏览器画面变化:Vite 版本几乎感觉不到间隔,Vue CLI 版本大概 1~2 秒。改一处涉及多个组件共用的工具函数:Vite 版本还是很快,Vue CLI 版本这次会明显变慢,能到 3 秒左右,因为这类改动牵动的依赖关系更广,Webpack 那边要重新计算的模块范围更大。

这几个数字不是拿来做严谨基准对照的,机器负载、缓存状态都会影响具体数值,放在这里只是想留一个比"很快""明显更快"更具体一点的参照。

日常开发里,切分支重新拉起开发服务器的频率不比第一次冷启动少。切到一个改动量不小、涉及好几个新增依赖的分支,重新执行 npm run dev,因为 package.json 有变化会触发一次依赖预构建,等待感介于"纯冷启动"和"日常保存热更新"之间;切到没有新增依赖、只是业务代码不同的分支,重新拉起服务几乎是秒开。这也印证了前面的结论:Vite 的启动耗时主要跟依赖预构建是否需要重新跑有关,跟业务代码本身的体量关系不大。

终端输出和报错信息的直观感受

除了速度,试跑过程中还留意了一下终端信息本身给人的感觉。Vite 的终端输出比较克制,正常情况下只有几行——服务地址、偶尔的预构建提示,改动文件之后也就打一行 hot updated 之类的确认信息,不会像有的 Webpack 配置那样刷一大屏进度条和警告。

故意写错一处语法,比如漏了一个闭合括号,保存之后浏览器上直接弹出一个悬浮的错误提示层,把报错信息、出错文件和大致的代码位置显示在页面正中间,不用切回终端就能看到问题所在;同时终端里也会打印对应的堆栈。这种直接把错误摊在浏览器页面上的做法,跟以前 Webpack 热更新失败后页面本身没反应、只能回头翻终端日志的习惯不太一样,第一次看到这个错误浮层还愣了一下,后来发现这样定位问题反而更快,不用来回切窗口。

依赖体积大小对启动感受的影响

试跑的时候做了个对照:一个只装了 vuevue-router 两个依赖的极简项目,和一个把公司常用的一整套依赖(element-uiaxiosdayjslodash-es、一个图表库)都装上的项目,分别看冷启动的等待感有多大差别。极简项目那次预构建几乎一闪而过;装了一整套依赖之后,预构建能感觉到明显变慢,尤其是图表库这种体积比较大、内部又依赖不少子模块的库,处理它要花的时间比处理 dayjs 这种轻量库长出不少,等待时间从"一闪而过"变成能明显数出一两秒钟。即便如此,跟同样依赖量级下 Vue CLI 冷启动动辄十几秒的等待比起来,还是要快出一大截——预构建的耗时是跟依赖本身的体积、数量挂钩的,不是一个跟项目大小无关的固定值,但离 Webpack 那种"每次启动都要打包一遍全部业务代码加依赖"的量级还差得远。

日常写代码手比较快,经常是敲几个字符就按一次保存,连续快速保存三四次(每次间隔不到一秒)没有出现更新错乱或者页面卡住的情况,每次保存后浏览器画面都能对应上最新的改动。这跟 Vite 的热更新机制有关——用 chokidar 监听文件系统变化,每次文件变化都触发对这个模块的独立请求,浏览器端拿到最新内容直接替换,不存在"排队等前一次更新完成"这种依赖关系。以前用 Webpack 时遇到过短时间内连续保存导致热更新偶尔"卡壳"、要手动刷新一次才恢复正常的情况,这次对比着试没有复现类似的问题。

有一次手误,改代码时多留了一对没用到的花括号,导致组件渲染函数编译报错。浏览器上弹出的错误浮层直接标出来是哪个文件、大概第几行出的问题,报错信息里甚至能看到编译器尝试解析模板时具体停在哪个位置。以前在 Webpack 下遇到类似语法错误,报错经常会带着一长串 loader 调用栈,得从底下的原始报错信息里自己找出真正相关的那一行,噪音信息明显更多——Vite 这边没有 loader 链这种中间层,一个文件出错之后报错信息更贴近"这个文件本身出了什么问题"。

命令行参数和常用脚本写法

package.jsonscripts 那块也顺手对照了一遍。以前 Vue CLI 项目里常见的写法大致是:

1{
2  "scripts": {
3    "serve": "vue-cli-service serve",
4    "build": "vue-cli-service build",
5    "lint": "vue-cli-service lint"
6  }
7}

换到手动接入 Vite 的项目,对应写法要简单一些:

1{
2  "scripts": {
3    "dev": "vite",
4    "build": "vite build",
5    "serve": "vite preview"
6  }
7}

多出来的 vite preview 这条命令是个之前没接触过的东西,用来在本地起一个静态服务器,直接预览 vite build 打出来的产物,不用自己再装一个 serve 或者 http-server 之类的工具去手动起服务。跑了一下 npm run build && npm run serve,本地就能直接看到生产构建之后的页面效果,省了一步手动操作,算是一个不起眼但用起来挺顺手的细节。

命令行参数这块也简单试了下,vite --port 4000 能直接覆盖配置文件里写的端口,vite --host 能让开发服务器监听所有网卡地址,方便同事拿手机或者另一台电脑通过局域网 IP 访问验证移动端页面效果,这两个参数跟 Webpack dev server 对应的用法差不多,没有额外的学习成本。

浏览器兼容性这块要盯紧一点

因为 Vite 开发环境完全依赖浏览器原生 ESM,试跑期间特意用了一个稍旧版本的浏览器打开页面看看什么反应。原生 ESM 这个特性主流浏览器基本都在前几年陆续跟上了,现代版本的 Chrome、Firefox、Edge、Safari 打开开发环境跑的页面都没问题;但如果拿一个明显落后几个大版本的浏览器打开(内部测试用的一台旧机器上装的浏览器版本偏老),页面直接空白,控制台报出无法解析 <script type="module"> 的错误,没有任何兜底提示。

Vite 的开发环境本身不打算兼容旧浏览器,这是刻意的设计取舍,用旧浏览器环境的开发和联调这条路径目前走不通。好在这只是开发环境的限制,生产构建产物是走 Rollup 单独打包出来的,兼容性策略是另外一套,不受开发环境这条限制影响。这条限制值得单独记一下,避免以后有人拿老旧设备联调时才发现打不开。

modeNODE_ENV 不是一回事,这点容易搞混

环境变量这块还有一处细节容易踩空,专门花时间理清楚了。Vue CLI 下判断环境基本靠 process.env.NODE_ENV,值不外乎 developmentproductiontest 这几种,vue-cli-service servebuild 会分别把它设成对应的值,业务代码里写 if (process.env.NODE_ENV === 'production') 这种判断已经是肌肉记忆。

Vite 这边多了一个独立的概念叫 mode,默认情况下 vite 命令对应 development 模式,vite build 对应 production 模式,这点看起来和 Webpack 类似;但 modeNODE_ENV 在 Vite 里是两个不完全绑定的东西——mode 决定的是加载哪一份 .env.[mode] 文件、暴露哪些 VITE_ 前缀的变量,NODE_ENV 更多是 Node.js 生态里其他工具(比如某些依赖库内部判断环境的逻辑)依赖的老概念,Vite 会尽量把两者的取值对齐,但如果通过命令行参数手动指定了一个自定义 mode(比如 vite build --mode staging),NODE_ENV 不会自动跟着变成 staging,这时候如果业务代码里还在用 process.env.NODE_ENV 做判断,就会出现和预期不一致的行为。试的时候特意加了个 .env.staging 文件、跑了一次 vite build --mode staging,构建产物里 import.meta.env.MODE 正确显示成了 staging,但代码里一处遗留的 process.env.NODE_ENV === 'production' 判断依然按 production 那套逻辑走,两边没对齐,排查了一会儿才想明白是这个原因。这条经验记下来是提醒自己:迁移到 Vite 之后,业务代码里凡是直接判断 process.env.NODE_ENV 的地方,最好都过一遍,看是不是该换成 import.meta.env.MODE 或者 import.meta.env.PROD/import.meta.env.DEV 这几个 Vite 提供的布尔值。

编辑器那边的联动体验

顺带看了一下编辑器插件这块能不能配合上。VS Code 里装的 Vetur 插件(当时给 Vue 2 用的主流插件)对 .vue 文件里的语法高亮、模板里的属性提示照常生效,跟用不用 Vite 没有直接关系,这块本来就是编辑器插件自己在解析源码,不依赖开发服务器。真正跟 Vite 有一点点联动的地方是保存文件之后触发热更新的响应速度——因为编辑器保存文件这个动作和 Vite 监听文件变化几乎是同步的,从按下保存快捷键到浏览器画面变化,中间那个可感知的间隔比以前更短,形成了一种"编辑器和浏览器好像连得更紧"的错觉,实际上背后还是热更新链路本身变短了,不是编辑器和 Vite 之间有什么特殊协议。

试着塞进去一个动态 SVG 图标方案

后台系统里图标用得不少,之前 Webpack 下常见的做法是拿 svg-sprite-loader 把一堆 SVG 文件打成一个雪碧图,组件里通过 <use xlink:href="#icon-name"> 引用。这次试跑顺手看了下 Vite 下有没有等价方案能接上。

社区当时已经有对应的插件在做类似的事,跑起来之后行为上跟 Webpack 那套差不多——批量导入某个目录下所有 SVG,运行时拼出一个雪碧图插入页面,业务代码里照样用 <use> 引用。区别主要在插件本身的成熟度:Vue CLI 生态下这类需求已经有过好几年打磨,各种边界情况(比如 SVG 内部有 id 冲突、颜色继承)都有对应的处理经验;Vite 这边同类插件刚出来不久,试的时候确实碰上一次颜色不跟随 currentColor 变化的小问题,翻了下插件仓库的 issue,发现已经有人报过同样的现象,等对应版本更新之后才解决。这算是这次试跑里没有一次跑通、需要等上游修复的地方。

断网状态下试过一次继续写代码——因为依赖预构建产物已经缓存在本地 node_modules/.vite 目录,只要不触发重新预构建,断网期间照常改代码、照常热更新,浏览器和本地开发服务器之间走的是 localhost,本来就不需要外网,这点和 Webpack 开发服务器没什么差别,只是顺手确认一下没有因为"强依赖浏览器原生能力"而生出额外的网络依赖。

另外强制禁用浏览器缓存再刷新试过一次,看 Vite 那种逐个模块请求的方式会不会因为缓存被清空暴露出性能问题。实测下来禁用缓存后首次加载确实慢一点,网络面板里瀑布图变长了一些,但整体耗时依然在能接受的范围内——现代浏览器对同域名下的并发请求数量支持得不错,单个模块文件体积通常也很小,不走缓存也能较快并行完成。这也提醒了一件事:日常开发不会特意禁用浏览器缓存,平时感受到的启动速度本身也叠加了浏览器对未变化模块文件的缓存效果,不完全是"裸的"服务器响应速度。

引入 Element UI 之后再感受一次完整体感

前面几轮试跑都是拿轻量组件在验证细节,最后干脆把团队常用的 Element UI 整个引进这个试跑项目,尽量还原一个业务页面的真实分量——列表、分页、弹窗表单、下拉选择器都用上,再感受一遍从冷启动到日常改动的整体流程。

装上 Element UI 之后第一次冷启动,预构建那一步的等待感比之前只装 vuevue-router 的版本明显长了一截,毕竟 Element UI 本身模块不少,esbuild 要处理的内容变多了。但即便如此,这个等待也就是几秒钟内的事,跟同等依赖体量下 Vue CLI 项目冷启动动辄十几秒相比,依然是有明显优势的。日常开发阶段,改一个用了 Element UI 表格组件的页面,热更新表现跟之前试的轻量组件没有什么两样,该多快还是多快,说明 Element UI 这类体积较大的第三方库一旦完成过一次预构建,后续就不再是影响开发反馈速度的因素了,真正决定日常反馈速度的还是"改的是哪个文件、这个文件本身编译起来快不快",跟项目里装了多少第三方依赖关系不大。

这轮试跑也顺带确认了 Element UI(当时还是给 Vue 2 用的版本)在 Vite 下没有出现明显的兼容性问题,按需引入组件、全局样式引入这些常见用法都能正常工作,没有出现因为它是老牌 Webpack 生态组件库而在 Vite 下水土不服的情况——这点跟前面提到的"很多 CommonJS 写法的库在 Vite 下可能要费点周折"不完全一样,Element UI 这次试下来算是比较顺利的一个例外,也可能是因为它本身对模块格式的处理已经比较规范。

移动端调试这块顺手也看了一眼

后台系统虽然主要面向 PC,但偶尔也要在手机浏览器上核对个别页面的响应式表现,试跑期间顺手验证了一下这块的联调体验。前面提到过 vite --host 能让开发服务器监听局域网地址,拿手机连上同一个 Wi-Fi,浏览器里输入电脑局域网 IP 加端口号,页面能正常打开,跟在电脑浏览器上访问没有区别。改一行样式,手机这边刷新(因为移动端浏览器对 WebSocket 连接的热更新推送有时候不太稳定,没有像电脑浏览器那样每次都自动生效,手动刷新一下更保险)也能看到最新效果,跟以前用 Webpack 的 --host 参数配合手机联调的操作习惯基本一致,没有额外要适应的地方。这块观察比较简单,没有深入到移动端专属的坑,只是确认了基本联调这条路是通的。

一次不算成功的尝试:接入一个内部埋点 SDK

试跑过程里也不是每次都顺利。团队内部有一个埋点上报的小 SDK,之前一直是按 CommonJS 方式发布的,module.exports = { track, init } 这种写法。直接在 Vite 项目里 import { track } from '@internal/tracker',开发环境下能跑,但控制台会打印一个提示,说这个模块被当成默认导出处理,解构出来的 track 实际上是 undefined

这跟前面验证 TypeScript、Element UI 时都顺风顺水不太一样,属于这次试跑里少数几个没能一次跑通的例子。琢磨了一下原因:这个 SDK 用的是纯 CommonJS 写法,且没有走一层 Babel 转译加兼容处理,esbuild 在预构建阶段对这种"既不是标准 ESM、又缺少明确默认导出标记"的模块,处理策略跟 Webpack 那套成熟的兼容逻辑不完全一样。折腾了一会儿,最后改成先用 import tracker from '@internal/tracker' 拿到整个模块对象、再从上面取 tracker.track 这种写法绕过去,能用,但明显没有原来那种直接解构的写法顺手。

这次没跑通的经历倒是让这次整体偏正面的试跑体验多了一点参照——不是所有东西接上去都毫无阻力,尤其是团队内部这种没有专门为 ESM 环境适配过的老代码,仍然可能遇到需要手动绕一下的情况,只是这类情况在这轮试跑里只碰到了这一处,不算多。

把试跑过程中记下的几处细节归拢一下

前后折腾了小半周,把这次试跑里零散记下来的观察归拢一下,主要是这几类:

启动和热更新这块最直接,冷启动、依赖变化后的重新预构建、日常改动的模块级热替换,是这次体感差异最大的三种场景,背后原因也都能对应到"浏览器原生 ESM + esbuild 预构建"这套跟 Webpack 完全不同的实现思路上。基础配置这块,alias、环境变量、代理这三处业务项目离不开的东西,Vite 都有对应写法,形态跟 Webpack 接近但细节不完全一样,尤其是环境变量的前缀和暴露规则,得留意别直接照抄。资源处理这块,静态资源引用、SVG 图标方案基本能找到对应方案,但插件成熟度参差不齐,偶尔会碰上需要等上游修复的小问题。生态兼容性这块是这次唯一留了个问号的地方——像内部埋点 SDK 这种老 CommonJS 代码,不一定能无缝接上,需要手动绕一下。

这几类观察放在一起看,会发现这次试跑的收获不只是"确认了它快",更多是搞清楚了"它为什么快、快在哪个环节、哪些地方这套新思路还没完全填平",这些细节比一句笼统的"体验好"更有参照价值。

顺手记一下按需引入组件库这条路径的差异

Element UI 这类组件库,业务里通常不会全量引入,而是按需加载用到的那几个组件,减小最终包体积。Webpack 下这条路径靠的是 babel-plugin-component 这类 Babel 插件,在编译阶段把 import { Button } from 'element-ui' 这种写法转成按路径单独引入对应组件和样式文件的写法。

试跑期间验证了一下这条路径在 Vite 下的情况,因为 Vite 开发环境不经过 Babel 转译这一层(用的是 esbuild 做语法转换),这类基于 Babel 插件做代码转换的按需引入方案,在 Vite 里没法直接照搬,得找专门适配 Vite 的按需引入方案,或者干脆手动一个个写完整路径引入。这次试跑时间有限,没有深入折腾出一条完整的按需引入路径,只是记下这个差异——凡是原来依赖 Babel 插件在编译期做"魔法"的方案,换到 Vite 下大概率都得重新找一遍等价方案,不能想当然地认为工具换了、写法能照抄。

结束这轮试跑前,把这些页面截图存了个对照

试跑收尾前,把冷启动等待的终端截图、Network 面板里两种瀑布图的对比截图、还有那次报错浮层的截图都单独存了一份,放在这次试跑用的项目仓库里当参考。倒不是要拿这些截图去做什么正式汇报,只是这类"当下体感"过一阵子容易记混,留几张截图,以后再遇到类似的工具评估,能直接翻出来对照,比单凭记忆复述更准确。

一点关于 .vue 文件里 <script> 写多个导出的小验证

顺手还验证了一处不算常见但业务里偶尔会遇到的写法:一个 .vue 文件的 <script> 部分除了默认导出组件对象,还额外具名导出了一个工具函数,供别的文件单独引用这个函数而不引入整个组件。这种写法在 Webpack + vue-loader 下是支持的,换到 Vite + vite-plugin-vue2 下同样能正常工作,具名导出和默认导出都能被正确识别、正确热更新。项目里组件文件混合导出这个写法有那么几处在用,确认没有坑就放心了。

内存占用这块顺手瞄了一眼

试跑期间还顺手打开系统的活动监视器,对照着看了下两边开发服务器进程本身的内存占用。跑起来之后静置不动,Vite 的开发服务器进程占用的内存明显比 Webpack dev server 要小一些——这点倒是能理解,Webpack 需要把整个依赖图和模块编译结果都维护在内存里,而 Vite 的开发服务器本身不需要保有这么大一份"全量编译结果",只需要按需处理请求到的模块,加上依赖预构建产物本身也是写到磁盘缓存目录里而不是全部常驻内存。这块观察比较粗略,没有做严谨的内存监控,只是电脑同时开着好几个项目、风扇声音比平时小了一点这种直观感受,顺手记一笔,不算是这次试跑重点关注的方向。

再补一句关于 CSS 预处理器的直接体感

前面提过样式热更新走的是单独的替换路径,这里再补一点关于 Sass 的直接使用体感。手动接入 Vite 的项目里装了 sass 依赖,<style lang="scss"> 直接就能用,不需要额外配置 loader 链,这点跟 Vue CLI 下要装 sass-loadernode-sass(或者 dart-sass)搭配使用相比,步骤上少了一环。改一处嵌套的 scss 选择器规则,保存后热更新的表现跟改纯 CSS 完全一致,没有因为多了一层预处理器编译而感觉变慢,这层编译本身很快,肉眼感觉不到额外的延迟。

CSS 里的 @import 语句也单独确认了一下行为。业务项目里有个全局变量文件是用 @import './variables.scss'; 这种写法在多个组件之间共享的,这在 Webpack + sass-loader 下走的是编译期静态引入,最终会被内联进同一份 CSS 产物。Vite 开发环境下同样能正确解析这类 @import,行为上感觉不到差异,但看了一下网络请求才明白背后不完全一样:Vite 在开发环境下对 Sass/Less 这类预处理器文件是整体交给对应的编译器(sassless 这些库本身)处理完再返回,@import 在这一步已经被预处理器自己解析掉了,不会产生额外的浏览器请求;而如果是原生 CSS 文件里写 @import url(...),走的则是 PostCSS 处理链,能覆盖到的场景跟预处理器的 @import 不是一回事,这点容易在写法上搞混。另外还试了一下 PostCSS 插件的接入,vite.config.js 里可以直接配置 css.postcss 选项传入插件数组,写法上跟 Webpack 下配 postcss.config.js 类似,只是挂载的位置换了个地方,autoprefixer 装上之后照常生效,没有出现兼容性问题。

最后又试了一次冷启动,纯粹是想确认第一印象没有夸大

整轮试跑快收尾的时候,专门把电脑重启了一次,清空各种系统级缓存,再拿这个折腾了小半周的项目重新冷启动一遍,想确认最开始那种"这也太快了"的第一印象是不是被新鲜感放大了。重新跑一遍下来,感受跟第一次几乎没有差别,终端打出访问地址到浏览器能看到页面这段时间依然很短,热更新的反馈同样干脆。这次复核倒不是多此一举——工具刚上手时容易因为新鲜感把体验拔高,隔了几天带着更平常的心态再试一次,结论没有变,这点让人更放心把"确实快"这个判断落到文字里。

顺手记一句关于 public 目录里 favicon 之类文件的处理

试跑期间还留意了一下 public 目录里那些不需要经过编译、直接引用的文件,比如 favicon.ico、一份 robots.txt。这些文件在 Vite 下的处理方式很直接——放进 public 目录,index.html 里用根路径直接引用即可,开发环境下能直接访问到,生产构建时原样拷贝到产物根目录,跟 Vue CLI 里 public 目录的用法基本一致,没有额外要适应的地方,算是这次试跑里少数几个"跟以前完全一样、不用重新学"的角落。

一处容易被忽略的默认端口冲突

试跑过程中还遇到过一次很小的插曲:电脑上同时开着好几个项目,Vite 默认监听 3000 端口,跟另一个已经在跑的服务撞了端口。终端很直接地提示端口被占用,并没有像有的工具那样自动跳到下一个可用端口,而是直接报错退出,得手动在 server.port 里改一下,或者带 --port 参数重新启动。这个处理方式一开始觉得不如自动跳端口顺手,后来想想也能理解——自动跳端口有时候反而让人搞不清楚服务到底跑在哪个地址上,明确报错、让人自己决定,至少不会出现"以为服务没起来,其实是端口变了"这种误会。

关掉终端再重新打开,服务状态是否残留

顺带试了一下 Ctrl+C 结束 Vite 开发服务器进程之后,端口和缓存状态是否干净。结束进程后原端口立刻能被其他程序占用,没有出现进程没退干净、端口被僵尸进程占着不放的情况,重新执行 npm run dev 也能顺利拿到同一个端口。这块细节虽然琐碎,但也是日常高频操作里会反复遇到的场景,跑得干净利落,不需要额外记住"这个工具关闭时容易留尾巴、得手动杀进程"这类使用禁忌。

一个小结:这一路验证下来最踏实的感受

把上面这些零零散散的验证串起来看,最踏实的感受不是某一个孤立的"快",而是这种快在不同场景下都能站得住——冷启动快、日常改动快、切分支重启也没有想象中慢,即便是接了 Element UI 这种体量不小的依赖,日常反馈速度依然没有掉下来。少数几处不顺(内部埋点 SDK、SVG 图标插件的小问题)也都不是这套原理本身的缺陷,更像是生态还年轻、个别适配没跟上,跟"开发环境到底快不快"这个核心问题是两回事。

注意力被打断的次数比等待时长更让人在意

工具链慢不是只浪费几秒钟,真正烦的是思路被打断。调一个交互细节的时候,脑子里有一条很细的线:按钮高度是不是差一点,弹窗进场是不是太慢,表单提示是不是该往上挪。每次保存后等一下,这条线就会被切开一次。Vite 这次试下来最直接的价值,就是减少这种切开——如果一个工具每天都在消耗这么多次注意力,它本身就是工程成本,不是能不能忍的问题。

静态资源怎么引用,跟以前的习惯不完全一样

试跑过程中还专门验证了一遍图片、字体这类静态资源在 Vite 下的引用方式,因为这块跟 Webpack 下的习惯有一些细节出入,容易踩空。

在组件里直接用相对路径引入一张图片:

1import logoUrl from './assets/logo.png';

这行代码在 Vite 下能正常工作,编译时会把它转成一个可访问的 URL 字符串,跟 Webpack 下 file-loaderurl-loader 处理出来的效果类似。但 Vite 对小体积资源的内联阈值、命名规则跟 Webpack 默认配置不完全一样,有一张几 KB 的小图标,之前在 Webpack 下会被自动转成 base64 内联进 CSS,换到 Vite 下同一张图默认还是走的是文件引用而不是内联,读了下文档才发现这个内联阈值是可以在 vite.config.js 里通过 build.assetsInlineLimit 单独配的,默认值和 Webpack 的默认值本身就不一样,不是哪边坏了,只是两边的默认策略不同。

另外还试了一下直接在 <template> 里用相对路径写 <img src="./assets/logo.png">,这种写法在 Vite 下同样能被正确处理并转成正确的资源路径,跟 Vue CLI 下的行为基本一致,没有额外要注意的地方。放在 public 目录下的静态资源,则是原样拷贝到构建产物里,不经过编译这一层,这点两边工具的处理思路是一致的,没有让人意外的地方。

多个页面之间来回跳转,路由切换的观感

后台系统免不了要在多个路由页面之间跳转,特意在这套试跑的小项目里接了 vue-router,做了三个路由:列表页、详情页、一个简单的设置页,来回跳转看看观感有没有什么不一样。

跳转过程本身跟 Webpack 下的体验差不太多,毕竟这部分是 Vue Router 自己的逻辑,跟底层用什么开发服务器关系不大。真正有差别的地方还是回到"改代码之后要不要等"这件事上——在详情页这边改一行展示逻辑,跳转到详情页验证效果之前不需要等待重新编译,因为改动只影响这一个路由对应的模块文件,其他页面完全不受影响;如果是用了路由懒加载(() => import('./views/Detail.vue'))的写法,Vite 处理动态 import() 的方式跟静态 import 类似,同样是按需请求对应模块,跳转到对应路由时才会去请求这个文件,跟提前把所有路由对应的代码都打进一个 chunk 里、切路由只是运行时切换执行哪一段的 Webpack 思路是两种不同的路径。

团队里另一位同事上手的第一反应

小范围试跑之后,找团队里另一位对构建工具关注不多的同事也试了一遍,只给了他前面写好的 vite.config.js 和几句口头说明,让他自己跑一遍看看第一反应是什么。

他的第一反应跟我差不多——"这也太快了",尤其是改了一行文案保存之后几乎看不出停顿这件事,让他觉得比较意外。倒是配置这块他提了一个疑问:server.proxy 这些配置项跟 devServer.proxy 长得很像,会不会两边配置写法混着记、以后弄混。这个疑问倒是提醒了我,两边配置项名字接近但不完全一样,resolve.alias 和 Vue CLI 下 configureWebpack.resolve.alias 也是类似的情况——写法接近,但挂载的位置和触发机制并不相同,属于那种"看着眼熟但背后不是一回事"的配置,需要留意别直接照抄。

他还顺手问了一句代码里写错语法 ESLint 会不会在开发服务器里实时提示,这个问题正好之前没验证过,当场试了一下。Vue CLI 下 vue-cli-service serve 默认会跑一份 eslint-loader(或者新一点的项目用 eslint-webpack-plugin),保存文件时终端和浏览器控制台都会打印 lint 报错,属于开箱即用的行为。Vite 这边因为没有 loader 链,eslint-loader 这套机制没有直接对应物,保存文件之后终端和浏览器都不会自动跑 lint 检查,得单独配一个 Vite 插件去接,或者干脆依赖编辑器插件在写代码的时候就实时提示、外加 CI 流程里单独跑一遍 eslint 命令兜底。这算是这次试跑里又一处"原来默认带的能力,换了工具得自己找回来"的例子,跟前面 TypeScript 类型检查的情况是同一类问题。

为什么这次是从 Vue 项目切入去关注它

Vite 是尤雨溪发起的项目,这个出身自然会让 Vue 社区更早关注到它,而且今年九月 Vue 3 正式发布之后,Vite 和 Vue 3 的关系更紧一些——Vite 对 Vue 3 单文件组件的支持是内置的,用在 Vue 2 项目上则要靠 vite-plugin-vue2 这类社区插件补上去。手上这个后台系统还是 Vue 2.6,短期内不会因为 Vite 去动技术栈,试跑更多是想弄清楚,如果以后要接触 Vue 3,这套配套的开发环境体验能有多顺。

这次试跑下来,改完代码能更快看到结果,一天里因为等待被打断的次数确实少了很多。它没有立刻替掉手上的构建工具链,但至少让人确认了一件事:开发环境的等待感不是只能照单全收的现实,是可以被认真解决掉的一个具体问题。

短期内会怎么用它,现在的想法比较克制:先保留这个试跑项目当沙盒,把接下来遇到的新库、新写法都先扔进去验证一遍兼容性,攒够足够多的把握之后,再考虑要不要在某个范围清楚、影响面小的内部工具项目上正式用起来。手上这个后台系统体量不小,牵涉的三方依赖和历史配置也多,短期内没有把主力项目往上迁的打算,生态没追上来之前,稳妥比快更重要。

这次前后加起来试了小半周,从最开始的空项目起步,到手动搭建配置、逐项验证基础功能,再到接入 Element UI 之类的真实依赖去还原业务场景的分量,覆盖的场景比最初设想的要多一些。留下来的这些验证细节,以后团队里再有人问起"Vite 到底是个什么体验",也算是有具体的东西可以拿出来说,而不是只能回答一句"挺快的"。

这套沙盒项目也没打算就此收起来,接下来但凡有新的三方库要引入、或者遇到什么值得记录的 Vite 版本更新,都打算继续拿它验证一遍,权当是给团队攒一份随时能查的实测记录。

这份记录眼下就先放在这里,等下次版本更新或者有新的验证结果,再补进来。