在当下的前端开发中,Vite 几乎已经成为构建工具的首选。无论是新项目的快速搭建,还是日常开发中的丝滑体验,Vite 都展现出极高的使用频率与重要性。如果能够深入理解其原理不仅能让我们更好地使用 Vite,还能提升整体的前端工程化能力。除此之外你还可以了解到其他的知识:
- HTTP 相关的知识。
- Nodejs 相关知识。
如果你使用过 Vite,你一定对 npm run dev 这条命令不陌生。只需在你的项目文件夹中打开终端输入这个命令,Vite 的开发服务器就能立即启动。这个命令看着简单,但背后却隐藏着一些值得探究的问题:npm 在其中究竟扮演了什么角色?为什么输入 npm run dev 就能直接启动 Vite?它又是如何准确找到 Vite 的启动入口文件的?
带着这些疑问,我们不妨从 npm run dev 开始,逐步揭开 Vite 背后的原理
npm run dev会先找到 package.json 文件的 scripts 字段中名为dev的脚本,然后执行脚本。如果在命令行直接输入npm run则会在命令行打印出 scripts 字段中定义的所有的脚本。
Vite 的安装
为了搞清楚 Vite 的安装细节,我们选择手动安装 Vite 包。其它安装方式详见官方文档的搭建第一个 Vite 项目 。
创建一个新的文件夹 vite-project,进入该文件夹,打开命令行,输入 npm install -D vite,稍等片刻,Vite 包就会被安装在 vite-project 下的 node_modules 文件夹中。在安装的过程中 npm 还会根据 Vite 包的 package.json 中的 bin 这个属性,在 vite-project 下的 node_modules/.bin 文件夹中创建三个名为 vite 的可执行文件。
vite-project/
└── node_modules/
├── .bin/
│ ├── vite
│ ├── vite.cmd
│ └── vite.pds
└── vite/
└── bin/
└── vite.js
这三个可执行文件分别在不同的环境下执行:
vite在 Linux / macOS 环境下执行vite.cmd在 Windows cmd 环境下执行vite.ps1在 PowerShell 环境下执行
npm 包全局安装时的一些细节
当使用 npm install -global vite 安装包时,包会被安装在 nodejs 安装目录(安装 nodejs 时选择的安装路径)的 node_modules 文件夹下,同时 npm 还会根据 Vite 包的 package.json 中的 bin 这个属性在 nodejs 安装目录创建和包同名的三个可执行文件。
nodejs/
├── node_modules/
│ └── vite
├── vite
├── vite.cmd
└── vite.ps1
在安装 nodejs 时,nodejs 的安装目录会被加入到 PATH 环境变量中,所以在命令行中输入vite 就可以直接调用 vite.cmd 这个可执行文件。
大部分情况下我们不会全局安装 Vite,这里只是为了说明局部安装和全局安装的区别。
npm run dev 做了什么
当你在命令行输入 npm run dev 时,vite 服务就启动了,看似简单的操作,实际上 npm 为我们做了一系列的操作。大致的过程如下面这张图:
npm 程序的第 3 步,关于临时将
vite_project/node_modules/.bin 加入到 Path 环境变量的具体解释看官方文档 npm run script
In addition to the shell's pre-existing
PATH,npm runaddsnode_modules/.binto thePATHprovided to scripts.
npm 程序的第 4 步会用到安装 vite 包时 vite_project/node_modules/.bin 下生成的可执行文件。下面是三个可执行文件的具体内容(只截取了主要部分)。
vite 文件:
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@" #用 nodejs 运行 js 文件
else
exec node "$basedir/../vite/bin/vite.js" "$@" #用 nodejs 运行 js 文件
fi
vite.cmd 文件
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\vite\bin\vite.js" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\vite\bin\vite.js" %*
)
vite.ps1
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../vite/bin/vite.js" $args #用 nodejs 运行 js 文件
} else {
& "$basedir/node$exe" "$basedir/../vite/bin/vite.js" $args #用 nodejs 运行 js 文件
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../vite/bin/vite.js" $args #用 nodejs 运行 js 文件
} else {
& "node$exe" "$basedir/../vite/bin/vite.js" $args #用 nodejs 运行 js 文件
}
$ret=$LASTEXITCODE
}
这三个文件都有一个共同特点,就是最后会调用 nodejs 来运行 vite.js 这个文件,我在相应的代码处做了注释。
所以当你在命令行输入 npm run dev,npm 会通过 vite_project/node_modules/bin/vite可执行文件来执行 vite_project/node_modules/vite/bin/index.js 这个 js 文件,从而启动 Vite 服务的。
用 nodejs 模拟 npm run dev
npm 的源码也是 nodejs 写的。那么根据前文,我们就可以用 nodejs 写一个脚本来模拟 npm run dev 的过程,从而加深我们对 npm run dev 过程的理解。
-
新建文件夹
npm_run,进入npm_run文件夹通过初始化命令npm init初始化项目,此时会在当前文件夹下生成一个package.json文件。 -
接着通过命令
npm install -D vite来安装Vite。 -
然后在
npm_run的根目录添加一个index.html文件。因为Vite在做开发服务器时会默认启动一个以当前文件夹为根目录的服务,所以如果能在浏览器中正常访问我们添加的html文件,则说明Vite启动成功了。 -
接着在
package.json文件中的scripts对象上添加dev: vite属性。 -
最后新建一个
npm.js文件,这个文件就是我们模拟npm run dev过程的 nodejs 脚本,最后会用命令node npm.js run dev来运行这个文件。
到这我们的准备工作已经做完,接下来开始写 nodejs 脚本。
首先我们需要用到 nodejs 的 fs 和 child_process 两个模块。fs模块的 readFileSync 方法用来读取 package.json 文件内容。child_process 模块的 exec 方法来执行命令。
引入模块:
const { readFileSync } = require("node:fs")
const { exec } = require('child_process');
通过 process 对象的 argv 来获取命令 node npm.js run dev 中的参数 dev:
// process.argv 值是这样的。
[
'/**/**/bin/node', // 运行 Node 的可执行文件路径
'/npm.js', // 正在执行的脚本文件路径
'run', // 第一个自定义参数
'dev' // 第二个自定义参数
]
const argv = process.argv;
const scriptKey = argv[3];
备注: 在后续文章中我们会详细了解
nodejs获取命令行参数。
读取 package.json 文件内容解析成 JSON 对象,根据字符串 "dev",找到对应的 script:
const packageString = readFileSync("package.json", "utf-8")
const packageObj = JSON.parse(packageString)
const scriptValue = packageObj.scripts[scriptKey]
将 **/npm_run/node_modules/bin 加入到 PATH 环境变量:
const Path = process.env.Path;
const tempPath = __dirname + "/node_modules/.bin"
process.env.Path = Path + ";" + tempPath
执行字符串 "dev" 对应的命令
exec(scriptValue, (err, stdout, stderr) => {
console.log(stderr)
})
脚本编写完成,现在在命令行中输入 node npm.js run dev,然后在浏览器中输入 localhost:8090/index.html ,能正常访问 HTML 页面,说明我们的脚本成功的模拟了 npm run dev 启动了 Vite 服务的过程。
总结
通过本文的分析,我们了解了 Vite 的安装过程,并且完整梳理了 npm run dev 背后的执行逻辑,也由此理解了 Vite 是如何被真正启动的。
从 npm 的脚本机制、PATH 环境变量的临时注入,到 .bin 目录下可执行文件的作用,再到最终由 Node.js 调用 vite.js 启动开发服务器,这一系列过程展示了前端工程化工具链在底层的运行原理。
理解这些细节,不仅能帮助我们更高效地使用 Vite,更重要的是,它揭示了前端构建工具之间的共通机制:npm 负责命令调度,Node 负责脚本执行,工具本身通过入口文件完成核心逻辑。
掌握这一机制,就能从容地阅读、调试或定制任何基于 Node.js 的构建工具,而不再只是“使用者”。
在后续的文章中,我们将进一步探讨 Vite 内部的启动流程,看看当 vite.js 被执行后,你在命令行输入的参数是如何被解析的,开发服务器是如何被创建、插件系统又是如何运作的,从而更深入地理解 Vite 的设计思想。