深入 create-vite:Vite 官方脚手架 CLI 从一条命令到可运行项目的完整解析
create-vite 是 Vite 官方提供的项目脚手架工具(位于 packages/create-vite),一条 npm create vite@latest 即可在当前目录生成一个选定框架的完整工程。本文基于仓库内 README 与主源码 src/index.ts,完整梳理它的调用方式、全部 CLI 参数、18 个内置模板、交互式流程的每一步判定逻辑,以及模板复制、文件名重命名、React Compiler / ESLint 后置改造等底层实现,帮助读者既能正确上手,也能理解“脚手架到底替你做了什么”。
一、环境要求与安装调用方式
README 明确标注了兼容性前提:
Vite 要求 Node.js 20.19+ 或 22.12+;部分模板可能要求更高的 Node.js 版本,若包管理器给出告警请升级。
这一约束在 package.json 的 engines 字段中得到印证:"node": "^20.19.0 || >=22.12.0",当前仓库中的版本为 9.2.0。
不同包管理器的入口命令如下(与 README 完全一致):
# NPM
npm create vite@latest
# Yarn
yarn create vite
# PNPM
pnpm create vite
# Bun
bun create vite
# Deno
deno init --npm vite
不带参数运行时,CLI 在 TTY 下会进入交互式模式(源码中通过 process.stdin.isTTY 判断,见 src/index.ts#L456),依次询问项目名称、框架、变体、Linter 与是否立即安装启动。
直接指定项目名与模板
也可以跳过交互,用命令行参数直接指定项目名和模板。以生成 Vite + Vue 项目为例(注意 README 特别指出 npm 7+ 需要多一个双横线 -- 才能把参数透传给脚手架):
# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-app -- --template vue
# yarn
yarn create vite my-vue-app --template vue
# pnpm
pnpm create vite my-vue-app --template vue
# Bun
bun create vite my-vue-app --template vue
# Deno
deno init --npm vite my-vue-app --template vue
另外,项目名可以用 . 表示“在当前目录搭建”,这在已有仓库目录里做二次初始化时很常用。
二、CLI 参数全集
--help(或 -h)输出的帮助信息定义在 src/index.ts#L40-L63,参数解析使用 mri 完成(src/index.ts#L25-L36)。整理如下:
| 参数 | 别名 | 类型 | 说明 |
|---|---|---|---|
DIRECTORY |
— | 位置参数 | 目标目录;. 表示当前目录 |
-t, --template NAME |
-t |
字符串 | 直接使用指定模板,跳过框架选择 |
-i, --immediate / --no-immediate |
-i |
布尔 | 搭建完成后立即安装依赖并启动 dev server |
--eslint / --no-eslint |
— | 布尔 | React 模板改用 ESLint 而非默认的 Oxlint |
--overwrite |
— | 布尔 | 目标目录非空时移除已有文件后继续 |
--interactive / --no-interactive |
— | 布尔 | 强制交互 / 非交互模式 |
-h, --help |
-h |
布尔 | 打印帮助信息 |
几个从源码可以确认的行为细节:
- 模板名会被规范化:
react-compiler/react-compiler-ts在解析时会剥掉-compiler后缀,实际落盘模板对应react/react-ts,只是额外做一层编译器改造(src/index.ts#L606-L611)。 - 传入无效模板名时,交互模式会提示
"xxx" isn't a valid template. Please choose from below:并回到框架选择菜单;非交互模式则直接落到默认模板(src/index.ts#L557-L601)。 - 非交互模式(无 TTY 且未传
--interactive)下的默认值是:目标目录vite-project、模板vanilla-ts、不立即安装启动(src/index.ts#L486、L599、L664-L666)。这意味着 create-vite 天然适合被脚本或 Agent 以--no-interactive --template xxx的方式一次性调用。
三、内置模板与交互式两级选择
README 列出的内置模板预设共 18 个:
vanilla、vanilla-ts、vue、vue-ts、react、react-compiler、react-ts、react-compiler-ts、preact、preact-ts、lit、lit-ts、svelte、svelte-ts、solid、solid-ts、qwik、qwik-ts。
这些模板以 template-<name> 目录的形式随包发布——package.json 的 files 字段显式包含了 template-* 与 dist。仓库中可以逐一查看每个模板的完整内容,例如 template-react、template-vue、template-svelte-ts。
交互式模式下,选择分两级,对应 FRAMEWORKS 数据结构:
- Select a framework:Vanilla、Vue、React、Preact、Lit、Svelte、Solid、Ember、Qwik、Angular、Marko、Others;
- Select a variant:TypeScript / JavaScript,以及各框架的“外部脚手架变体”(如 Vue 下的
Official Vue Starter、Nuxt、Vike,React 下的RSC、React Router v7、TanStack Router、RedwoodSDK等)。
值得注意:Ember、Angular、Marko 以及 Others 组下的所有选项都没有对应的 template-* 目录,它们携带的是 customCommand 字段(src/index.ts#L293-L386)。选中后 CLI 不会复制本地模板,而是直接把 TARGET_DIR 占位符替换成你的目标目录,用 spawn.sync 调起外部命令,例如 Nuxt 的 npm exec nuxi init TARGET_DIR、SvelteKit 的 npm exec sv create TARGET_DIR、Angular 的 npm exec @angular/cli@latest new TARGET_DIR(src/index.ts#L613-L628)。
包管理器自适应
外部命令在生成前会经过 getFullCustomCommand 改写,以适配当前使用的包管理器:npm create 在 bun 下变成 bun x create-、在 deno 下变成 deno run -A npm:create-;npm exec 在 pnpm 下变成 pnpm dlx、在 yarn 下变成 yarn dlx。这个判断依据来自 npm_config_user_agent 环境变量(pkgFromUserAgent),解析不出时回退为 npm。
四、脚手架落盘:复制、重命名与内容替换
当所选模板是内置模板时,核心流程在 init 的第 5~6 步之后完成,具体做了四件事:
1. 确定模板源目录。 以 CLI 自身所在包目录为基准解析 template-<template>(src/index.ts#L673-L677)。
2. 逐文件复制,并做点文件重命名。 模板目录中所有非 package.json 文件通过递归 copy 写入目标目录,其中下划线前缀文件会被改写为点前缀:
const renameFiles: Record<string, string | undefined> = {
_gitignore: '.gitignore',
'_oxlintrc.json': '.oxlintrc.json',
}
(src/index.ts#L394-L397)之所以在模板里用 _gitignore 而不是 .gitignore,是因为部分包管理器和发布流程对点文件支持不佳。这一点在测试文件 cli.spec.ts#L35-L39 中也有同名映射来校验生成的文件清单与模板目录完全一致。
3. 替换 index.html 标题与 package.json 名称。 write 函数对 index.html 做特殊处理——用正则替换 <title> 为包名(src/index.ts#L679-L694),测试用例 “sets index.html title to project name”(cli.spec.ts#L299-L309)验证了 <title>test-app</title> 的生成结果。package.json 则不直接复制,而是读取模板版本、仅改写 name 字段后重新序列化写出(src/index.ts#L701-L707)。
4. 目录名 → 包名的合法性处理。 包名默认取目标目录的 basename,经 isValidPackageName 正则校验;不合法时交互模式下会追问,非交互模式则自动执行 toValidPackageName 的转换:去空格转连字符、小写化、去掉开头的 ./_。同样,目录名本身也会经过 formatTargetDir 清洗,剥离 <>:"\|?* 等非法字符和结尾斜杠。
目标目录非空时的三种处置
若目标目录已存在且非空(isEmpty 认为只有 .git 也算空目录),CLI 提供三个选项(src/index.ts#L490-L533):
Cancel operation:取消;Remove existing files and continue:执行 emptyDir 清空目录——注意它会保留.git,方便在已有 git 仓库内重新初始化;Ignore files and continue:不清空,直接覆盖同名文件。
非交互模式下若未显式传 --overwrite,默认按“取消”处理;传了 --overwrite 则等价于选“移除已有文件”。测试用例 “asks to overwrite non-empty target directory”(cli.spec.ts#L108-L130)覆盖了普通子目录、嵌套子目录和当前目录三种场景。
五、React 模板的后置改造:React Compiler 与 Linter 选择
这是 create-vite 中最复杂的部分,两个变体都有独立开关与实现函数。
React Compiler(react-compiler / react-compiler-ts 模板)
选中后,setupReactCompiler 会对落盘项目做三处修改:
- 向
devDependencies注入@rolldown/plugin-babel、babel-plugin-react-compiler、@babel/core(TS 版再加@types/babel__core); - 改写
vite.config.ts/js:引入reactCompilerPreset与babel插件,把plugins: [react()]替换为:
plugins: [
react(),
babel({ presets: [reactCompilerPreset()] })
],
- 更新生成项目的 README,说明已启用 React Compiler 并提示其会“impact Vite dev & build performances”,同时提到可在 plugin-react 选项中尝试实验性的原生
compiler: true支持。
对应测试 “successfully scaffolds a project based on react-compiler-ts starter template”(cli.spec.ts#L175-L194)断言了配置文件中出现 reactCompilerPreset、package.json 出现 babel-plugin-react-compiler、README 出现启用提示。
Linter:默认 Oxlint,可选 ESLint(仅 React 模板)
当前版本的 React 模板默认使用 Oxlint:模板目录自带 _oxlintrc.json,脚手架将其重命名为 .oxlintrc.json,lint 脚本为 oxlint。测试 “scaffolds react-ts with Oxlint by default”(cli.spec.ts#L196-L213)验证了默认产物中 .oxlintrc.json 存在、eslint.config.js 不存在、依赖中无 eslint。
传 --eslint 则进入 setupEslint:删除 .oxlintrc.json,写入 flat-config 风格的 eslint.config.js(TS 版含 typescript-eslint recommended,JS 版含 jsx parserOptions 与 eslint-plugin-react-refresh),把 lint 脚本改写为 eslint .,并同步替换依赖与生成项目 README 中“Expanding the Oxlint configuration”章节为对应的 ESLint 章节。若对非 React 模板误传 --eslint,CLI 会打印警告 “--eslint is only supported for React templates and will be ignored” 并忽略该参数(src/index.ts#L630-L638),行为由 cli.spec.ts#L230-L245 保证。
六、--immediate:自动安装并启动
脚手架结束后是否立即安装依赖并启动 dev server,交互模式下会询问 “Install with <pkgManager> and start now?”;--immediate / --no-immediate 可跳过询问(默认 false)。选择立即执行时:
- install 按包管理器生成安装命令——yarn 是裸
yarn,其余是<pm> install,以stdio: 'inherit'在目标目录同步执行; - start 调用 getRunCommand:yarn / pnpm / bun 直接
<pm> dev,deno 为deno task dev,其余(npm)为npm run dev。
非交互且未选 immediate 时,CLI 输出收尾提示,按当前包管理器拼好后续三步命令(cd、安装、dev),例如:
Done. Now run:
cd my-vue-app
npm install
npm run dev
测试 “accepts immediate flag” 与 “accepts immediate flag and skips install prompt”(cli.spec.ts#L311-L327)分别验证了两种路径。
七、面向 Agent 的友好性与测试策略
有两点设计值得工程视角关注:
AI Agent 检测。 CLI 通过 @vercel/detect-agent 探测运行环境是否为 Agent(src/index.ts#L458-L464)。若是,且在交互式 TTY 中,会主动打印一行提示,推荐改用一次性命令:
create-vite <DIRECTORY> --no-interactive --template <TEMPLATE>
这与第二节所述的非交互默认值设计配合,使脚本化调用结果确定、可预测。
测试如何跑真实 CLI。 cli.spec.ts 通过 execaCommandSync 以子进程方式执行 node <CLI_PATH> <args>,并设置环境变量 _VITE_TEST_CLI=true。源码中的 install 与 start 检测到该变量后会跳过真实安装/启动、仅打印 (skipped in test)(src/index.ts#L414-L438),从而让 20 余个端到端用例(交互提示、无效模板、目录冲突、文件清单比对、标题替换等)能在 CI 中稳定运行。
构建产物。 包的可执行入口是 index.js(仅两行,import './dist/index.js'),由 tsdown.config.ts 以 node20 为目标从 src/index.ts 构建,且构建时会把各模板目录的 CC0 许可证文本合入 LICENSE 说明。包同时注册了 create-vite 与 cva 两个 bin 名。
八、社区模板:tiged 用法
README 指出 create-vite 的定位是“为流行框架快速起步的基础模板工具”;对于其他工具链或框架,官方建议参考 Awesome Vite 社区的社区维护模板清单,并使用 tiged 这类工具按 user/project 形式拉取:
npx tiged user/project my-project
cd my-project
npm install
npm run dev
这一方式与 create-vite 内部对外部模板(如 RSC starter)的处理思路一致——本质上都是“按仓库模板复制 + 后续安装启动”。
小结:一条命令背后的完整链路
综合源码可以总结出 create-vite 的完整链路:mri 解析参数 → TTY / --interactive 决定交互级别 → Agent 环境提示 → 目录与包名规范化(非法字符清洗、合法性校验)→ 非空目录三选一处置(保留 .git)→ 内置模板目录复制(点文件重命名 + index.html 标题与 package.json 名称替换)→ 变体后置改造(React Compiler 插件注入 / ESLint 平替 Oxlint)→ 按包管理器执行安装与 dev。
理解这条链路后,你在日常使用中可以更放心地:用 -t 精确指定模板、用 . 在现有目录内重搭、用 --overwrite 处理非空目录、用 --no-interactive 写进脚本或交给 Agent 执行,并通过 packages/create-vite/tests/cli.spec.ts 中的断言核对任何一步的预期行为。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00