我正在参加「掘金·启航计划」
分析 Vite@3 源码实现之 vite/bin/vite.js
前置工作
下载源码
# --depth=1 只保留最后一次 commit 记录,加快克隆下载的速度
git clone http://github.com/vitejs/vite.git --depth=1
cd vite
pnpm i && code ./packages/vite
ViteJS 使用的方式是 vite ,那说明这个包一定包含了一个可执行的文件,在现有的 node npm package 体系中的体现则是它一定是有一个 bin 命令的,通过查看 package.json 可以看到入口是 bin/vite.js ,所以我们从它开始。
判断使用方式,收集 bin 参数
第一行的 #!/usr/bin/env node 表示从当前用户的可执行环境中查找 node 来作为这个文件的解析器,然后又引入了 import { performance } from 'node:perf_hooks' 依赖,performance 其实和浏览器中的 performance 类似,performance.now() 表示了从调用 node 开始到执行这一条语句中间间隔了多长时间,单位是毫秒。
下一行有一个 if 判断语句 if (!import.meta.url.includes('node_modules')),其中使用到了 import.meta.url ,这个属性保存的是当前模块在磁盘中的文件路径。需要注意的是,import.meta 属性只能在 es6 及以上的版本才可以使用,因为它只对 JavaScript Module 提供,所以你在 package.json 中也能找到一个 "type": "module" 属性,这是为了表示,vite 这个包是现代 ESModule 格式,同时 Vite 本身也是一个主打 "Next generation frontend tooling.(下一代前端工具。)" 的开发依赖,所以肯定还是会以现代浏览器的标准和兼容性来编写代码。回到代码中,会发现代码中判断了当前文件是否包含 node_modules ,这么做的目的是为了在使用 npx vite 的时候也能获取到 vite 对应的 sourcemap 以定位问题。同时这里还使用到了一个比较新的特性:`"top-level await",即:在模块的顶级使用 await 语句,而非将其包含在 async 函数中。
global.__vite_start_time = performance.now() 则是将服务启动时间保存到全局环境中,在 node 中,global 等同于 window ,也是所有的开始。
const debugIndex = process.argv.findIndex((arg) => /^(?:-d|--debug)$/.test(arg)) // 判断是否有设置 --debug || -d,并找到其对应的 index 位置
const filterIndex = process.argv.findIndex((arg) => /^(?:-f|--filter)$/.test(arg)) // 判断是否有设置 --filter || -f,并找到其对应的 index 位置
const profileIndex = process.argv.indexOf('--profile') // 判断是否有设置 --profile,并找到对应的索引位置
bin debug params
--debug/-d
{
"scripts": {
"dev": "vite --debug"
}
}
在项目的 package.json/scripts/dev 命令后添加 --debug 命令可以看到 vite 在此次运行时加载的所有配置项和服务启动后加载每个文件所需要的耗时。
vite:spa-fallback Rewriting GET / to /index.html +0ms
vite:time 25.28ms /index.html +0ms
vite:spa-fallback Rewriting GET / to /index.html +34ms
vite:time 2.92ms /index.html +15ms
vite:load 5.08ms [fs] /@vite/client +0ms
vite:load 5.21ms [fs] /src/main.js +4ms
vite:transform 4.10ms /src/main.js +0ms
vite:time 11.45ms /src/main.js +35ms
日志的类型多且杂,各个文件和各个阶段的操作日志都会输出出来,非常不方便查看,但同时,它还可以指定一个过滤属性,用于在打印日志时只输出某一类型的日志:--debug load :
vite:load 6.73ms [fs] /@vite/client +0ms
vite:load 7.78ms [fs] /src/main.js +5ms
vite:load 12.16ms [fs] /src/style.css +18ms
vite:load 13.69ms [fs] /src/App.vue +1ms
vite:load 0.18ms [plugin] /src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css +23ms
vite:load 0.79ms [plugin] plugin-vue:export-helper +1ms
vite:load 37.98ms [plugin] /node_modules/.vite/deps/vue.js?v=eb57b1a7 +1ms
vite:load 7.85ms [fs] /src/components/HelloWorld.vue +3ms
vite:load 0.04ms [plugin] /src/components/HelloWorld.vue?vue&type=style&index=0&scoped=e17ea971&lang.css +13ms
还可以指定多个类型:--debug load,resolve,transform
--filter/-f
{
"scripts": {
"dev": "vite -d -f /src/style.css"
}
}
vite:resolve 0.19ms ./style.css -> /Volumes/code/sourcecode/test/vite-process-test/src/style.css +0ms
vite:resolve 0.10ms /src/style.css -> /Volumes/code/sourcecode/test/vite-process-test/src/style.css +1ms
vite:load 10.10ms [fs] /src/style.css +0ms
vite:transform 72.46ms /src/style.css +0ms
vite:time 79.41ms /src/style.css +0ms
不过这个参数只支持对单个文件的过滤,不能同时设置对多个文件加载信息的过滤,也就是不能以 /src/style.css,/src/main.js 这种形式设置。
--profile
if (profileIndex > 0) {
console.log(process.argv)
process.argv.splice(profileIndex, 1)
const next = process.argv[profileIndex]
console.log(next)
if (next && !next.startsWith('-')) {
process.argv.splice(profileIndex, 1)
}
const inspector = await import('node:inspector').then((r) => r.default)
console.log('inspector', inspector)
const session = (global.__vite_profile_session = new inspector.Session())
console.log('session', session)
session.connect()
session.post('Profiler.enable', () => {
session.post('Profiler.start', start) // 在开始记录后启动服务器
})
} else {
start()
}