This is the script of my talk "Contributing to Vite+ Inside Stories" at Vue Fes Japan 2026. I gave the talk in Japanese. This is an English translation, with links to the Issues and PRs that I talk about.
Introduction
Hello, everyone!
My slides are in English, but I will speak in Japanese today, because I am still learning English.
I hope you enjoy my talk!
Today, I want to look back on the contributions that I made to Vite+.
I will show the Issues and PRs that I worked on, and tell you what I was thinking at that time.
This is not a difficult technical talk. It is a look back at what I did, so please relax and listen.
At first, I worked on Vite+ as an individual contributor. After I joined the Core Team, my work changed. I started to investigate Issues and review PRs.
Today, I will also talk about how my way of thinking about open source software (OSS) changed during that time.
Let me introduce myself. My name is Naoki Haba.
In my free time, I work on Vite+ every day as a Core Team member.
What is Vite+?
Before the main topic, let me introduce Vite+.
Vite+ is a unified toolchain. It puts many tools for web development into one package.
In daily development, you start a development server, run tests, and lint and format your code.
To do this, you usually install many tools, and each tool has its own configuration and commands.
Vite+ puts these tools together, so you can use all of them from one CLI, vp.
You probably know many of the tools inside it.
It uses Vite for the development server and application builds, Vitest for tests, Oxlint for linting, and Oxfmt for formatting.
You run them with commands such as vp dev, vp test, and vp lint.
vp pack builds libraries, and it uses tsdown. I will talk about tsdown again later, in the migration part.
Now, let me tell you about my first contribution.
My journey as a contributor
My first contribution: vp create
The first problem that I worked on was in vp create.
vp create makes a new project from a template.
When I used it, I typed the name of a template that did not exist. The command showed the pnpm 404 error as it was.
With that error, it was hard to see what I did wrong.
I looked on GitHub, and someone had already reported the same problem as an Issue.
Incorrect registry causing project creation to fail #828
Describe the bug
When I want to create a new Vite Plus using the vp create <name>, the CLI fetches from https://registry.npmjs.org/<name> which returns error 404.
ERR_PNPM_FETCH_404 GET https://registry.npmjs.org/create-vite-plus-app: Not Found - 404
Reproduction
none
Steps to reproduce
Run vp create <name>
System Info
Environment: Version 24.14.0 Source lts Tool Paths: node /Users/azizbecha/.vite-plus/js_runtime/node/24.14.0/bin/node npm /Users/azizbecha/.vite-plus/js_runtime/node/24.14.0/bin/npm npx /Users/azizbecha/.vite-plus/js_runtime/node/24.14.0/bin/npx vp v0.1.11 Local vite-plus: vite-plus Not found Tools: vite Not found rolldown Not found vitest Not found oxfmt Not found oxlint Not found oxlint-tsgolint Not found tsdown Not found Used Package Manager
pnpm
Logs
Validations
- [x] Read the Contributing Guidelines.
- [x] Check that there isn't already an issue for the same bug.
- [x] Confirm this is a Vite+ issue and not an upstream issue (Vite, Vitest, tsdown, Rolldown, or Oxc).
- [x] The provided reproduction is a minimal reproducible example.
So I read the code and followed the steps that it ran.
I found that the command ran the package manager without first checking if the package existed on npm.
Because of this, when it did not find the template, it showed the pnpm error.
This is the PR that fixed it.
fix: add troubleshooting tips for npm template not found error #1055
Linked issue
resolves #828
Description
The behavior when specifying an invalid template name (e.g., vite-plus-app) with the vp create command has been improved.
With this update, the CLI now checks the npm registry before executing the package manager. If the template is not found (HTTP 404 error), it will display an error message and terminate the process immediately.
Before it runs the package manager, the command now asks the npm registry if the package exists.
If the registry returns 404, the command shows that it did not find the template, and then it stops.
The message also tells you that vp create --list shows the available templates.
This was my first PR to Vite+.
It was a small change. But I read the real code and fixed a problem that I had when I used the tool.
After that, I started to look at other Issues, a little at a time.
Migrating Node.js version settings
At about the same time, I wanted to use Vite+ at work.
But existing projects already have a Node.js version setting and configuration for many tools.
I wanted to know how to move those settings to Vite+.
So I looked at vp migrate.
This command migrates an existing project to Vite+.
Rewriting configuration files sounds easy. But if the migration changes the Node.js version that the project uses, that is a problem.
So I had to think about two things: how to read the old settings, and how to make the project work the same way after the migration.
My first task here was to migrate the Node.js version setting.
`vp migrate` handles migration from `.nvmrc` #1158
Description
As a dev using Vite+, I want my old nvm files being migrated over to the Vite+ way.
Suggested solution
Ask them if they want to migrate them over when being used.
Alternative
Also adhere to .nvmrc (meh)
Additional context
Same for other node version managers!
Validations
- [x] Read the Contributing Guidelines.
- [x] Confirm this request is for Vite+ itself and not for Vite, Vitest, tsdown, Rolldown, or Oxc.
- [x] Check that there isn't already an issue requesting the same feature.
Two files can set the Node.js version for a project: .nvmrc and .node-version.
Vite+ uses .node-version, but projects that use nvm often have a .nvmrc file.
So I changed vp migrate. When it finds .nvmrc, it migrates the file to .node-version.
feat(migrate): migrate .nvmrc to .node-version during `vp migrate` #1159
🔗 Related Issues
Resolves: #1158
📚 Description
Vite+ previously retrieved the Node.js version from .node-version, but projects using nvm typically use .nvmrc.
With this change, automatic detection of .nvmrc and a migration feature to .node-version have been added as part of the vp migrate command execution process.
During migration, if a v prefix is present at the beginning, it is removed, while aliases such as lts/* are carried over as is.
Unit tests have confirmed that it works correctly regardless of the presence of the v prefix.
On the other hand, if unsupported aliases like node or stable are included, the system is designed to display a warning and skip the migration rather than failing.
Manual Test: .nvmrc Migration
$ mkdir /tmp/vp-migrate-test $ echo '{ "name": "test-migrate", "version": "1.0.0" }' > package.json $ echo 'v20.5.0' > .nvmrc Command
$ cd /tmp/vp-migrate-test && vp migrate --no-interactive Output (Excerpt)
.nvmrc detected. Auto-migrating to .node-version... ... • Node version manager file migrated to .node-version Final State
$ cat /tmp/vp-migrate-test/.node-version 20.5.0 - .nvmrc (v20.5.0) -> Deleted
- .node-version -> 20.5.0 (With the v prefix removed)
For example, if the file contains a version number such as 20.11.0, the command copies that value.
If the value starts with v, the command removes the v.
Up to here, the command only cleans up the value and copies it. But .nvmrc can contain values that are not version numbers.
I was not sure how to handle those values.
For example, .nvmrc can contain node instead of a version number.
node means "use the latest release of Node.js".
lts/* means "use the latest LTS release".
These two look similar, but they do not always give the same version.
So, at first, when the file contained node, the command showed a warning and stopped the migration.
If the command changed node to LTS without asking, the project could get an older Node.js version. I did not think that was correct.
But when I opened the PR, a reviewer suggested that converting it to LTS might be fine.
At about that time, I had also read an announcement. From Node.js 27, every major version moves to LTS after its release.
Because of this, I changed the command to convert node to lts/* and continue the migration.
But the new value does not mean the same thing as the old one, so the command writes a log message about the conversion.
At first, I thought this task was only about copying the contents of a configuration file.
But when I did it, I found cases where the command could not copy the old setting as it was.
I gave priority to keeping the meaning of the old setting. The reviewer gave priority to letting the migration continue.
Neither view was wrong. We had to decide which behavior was best for Vite+.
Talking about the direction with other members, before and after writing code, is one thing that makes OSS contribution interesting for me.
Up to here, I worked with Node.js, which I knew well. Next, I worked in a different area.
Adding Nushell support
That was support for Nushell.
Nushell is a shell, like Bash or Zsh. You run commands in a terminal with it.
Vite+ has a feature, vp env, that switches the Node.js version. The Issue asked to make this feature work in Nushell.
nushell env wrapper #1144
Description
As a developer using Vite+ I want to be able to source vp into nushell so that I can still use my main shell.
This includes wrapping the env use command and completions for vp.
Suggested solution
I've already implemented a env.nu that can be sourced in nushell like so 'source ~/.vite-plus/env.nu'
# Custom completer for the vp wrapper using fish completions def "nu-complete vp" [context: string] { let fish_comp_path = ($env.HOME | path join ".vite-plus/completion/vp.fish") let fish_cmd = $"source '($fish_comp_path)'; complete '--do-complete=($context)'" fish --command $fish_cmd | from tsv --flexible --noheaders --no-infer | rename value description | update value {|row| let value = $row.value let need_quote = ['\' ',' '[' ']' '(' ')' ' ' '\t' "'" '"' "`"] | any {$in in $value} if ($need_quote and ($value | path exists)) { let expanded_path = if ($value starts-with ~) {$value | path expand --no-symlink} else {$value} $'"($expanded_path | str replace --all "\"" "\\\"")"' } else {$value} } } export def --env --wrapped vp [...args: string@"nu-complete vp"] { match $args { ["env", "use", ..] => { let out = (with-env { VITE_PLUS_ENV_USE_EVAL_ENABLE: "1" } { ^vp ...$args }) let out_lines = ($out | lines) let exports = ( $out_lines | find -r '^export ' | parse -r '^export (?P<key>\w+)=["'']?(?P<value>[^"'']+)["'']?' | reduce -f {} {|it, acc| $acc | insert $it.key $it.value} ) if not ($exports | is-empty) { load-env $exports } let unsets = ( $out_lines | find -r '^unset ' | parse -r '^unset (?P<key>\w+)' | get key ) for key in $unsets { hide-env $key } } _ => { ^vp ...$args } } } Alternative
No response
Additional context
No response
Validations
- [x] Read the Contributing Guidelines.
- [x] Confirm this request is for Vite+ itself and not for Vite, Vitest, tsdown, Rolldown, or Oxc.
- [x] Check that there isn't already an issue requesting the same feature.
This part of Vite+ uses Rust. I had never used Rust in a real product before.
So I started by reading the existing code for Bash and Fish, to learn what it did.
I looked up the types and syntax that I did not know, and checked step by step how to do the same thing in Nushell.
But reading the Rust code was not enough. I also had to learn about the Nushell script that the code generates.
For example, look at this code:
# Does not work source "$HOME/.vite-plus/env.nu" # Works source ~/.vite-plus/env.nu In the first example, the path that goes to source starts with $HOME.
In Bash and similar shells, you often write the home directory like this.
But in Nushell, $HOME inside double quotes does not expand. Also, Nushell writes environment variables as $env.HOME.
So I thought that I only had to change that. But it still did not work.
The reason is that Nushell's source decides which file to load when it parses the script, before any command runs.
So you cannot use a runtime environment variable in that path.
I read the Nushell documentation again, and changed the path to start with a tilde, as in the second example.
With a tilde, Nushell can find the home directory when it parses the script.
feat(env): add Nushell support via env.nu wrapper #1312
resolves #1144
While comprehensive support for Nushell seems to be under development in #1305, that pull request has a large impact. Therefore, I have limited the scope of this change to env support to align with the existing issue.
# nushell # 1. Setup vp env setup source ~/.vite-plus/env.nu # or restart Nushell # 2. Set a specific version vp env use 20 # Using Node.js v20.20.2 (resolved from 20) $env.VP_NODE_VERSION # 20.20.2 # 3. Resolve from .node-version vp env use # Using Node.js v22.18.0 (resolved from .node-version) $env.VP_NODE_VERSION # 22.18.0 # 4. Completion (requires Fish) vp <Tab> # add Add packages to dependencies # build Build application # cache Manage the task cache # check Run format, lint, and type checks # ... (all subcommands shown) # 5. False detection regression (bash launched from Nushell) # NU_VERSION is inherited by child processes, but VP_SHELL_NU is not set, # so vp correctly uses POSIX eval mode inside bash. bash -c 'vp env use 20 && echo $VP_NODE_VERSION' The Rust code only makes a string. But I had to think about how Nushell reads that string.
So I ran the script in Nushell to check each change.
Before this, I had never written Rust code for a real product.
I work as a frontend engineer, so I do not use Rust at work.
I had solved some LeetCode problems in Rust, and that was all.
When you study alone, you usually stay with what you already know. In OSS, you cannot do that.
If you want to fix a problem, you must learn the language or the specification, even if you do not know it.
In this case, I had to learn about Nushell as well as Rust.
I did not understand everything at the start. I looked things up, tried them, got help in reviews, and learned a little at a time.
It was good study for me, and it also helped a tool that I use. I think that is a very good deal.
Joining the Core Team
I continued like this. I investigated Issues that interested me, and I opened PRs.
About one month after my first PR, the Vite+ Core Team invited me to join.
I was very surprised.
It was my first time on the core team of an OSS project. The members were engineers that I had looked up to for a long time.
I never thought that I would work on the same team as them.
After I joined the team, I started to look at Issues and PRs from other people, in addition to my own PRs.
For example, when someone reports an Issue, I first try to reproduce it on my machine.
If I can reproduce it, I look for the cause. If the report does not have enough information, I ask the reporter.
I also review more PRs from other people.
But the code alone does not always tell you why a change is necessary.
So I read the related Issues and past discussions. When necessary, I also read the code of projects other than Vite+.
Now I will show some of the work that I did as part of this.
Migrating from tsdown to Vite+
The first one is a problem in the migration from tsdown to Vite+.
tsdown is a tool that builds TypeScript libraries.
Vite+ could already migrate projects from tsdown. But after the migration, some projects got TypeScript errors.
no `tsdown/client` equivalent #1500
Describe the bug
Similar to vite/client, tsdown includes a tsdown/client file to register its bundler features with Typescript.
It would be useful if this was re-exported by Vite+, perhaps in vite-plus/pack/client (I see that vite/client is re-exported as vite-plus/client).
Reproduction
https://github.com/open-circle/schema-benchmarks/pull/124
Steps to reproduce
replace tsdown/client in schemas/tsconfig.json with vite-plus/pack/client
System Info
admin@MacBookPro schema-benchmarks % vp env current VITE+ - The Unified Toolchain for the Web Environment: Version 24.15.0 Source .node-version Source Path /Users/admin/schema-benchmarks/.node-version Project Root /Users/admin/schema-benchmarks Tool Paths: node /Users/admin/.vite-plus/js_runtime/node/24.15.0/bin/node npm /Users/admin/.vite-plus/js_runtime/node/24.15.0/bin/npm npx /Users/admin/.vite-plus/js_runtime/node/24.15.0/bin/npx admin@MacBookPro schema-benchmarks % vp --version VITE+ - The Unified Toolchain for the Web vp v0.1.20 Local vite-plus: vite-plus v0.1.20 Tools: vite v8.0.10 rolldown v1.0.0-rc.17 vitest v4.1.5 oxfmt v0.46.0 oxlint v1.61.0 oxlint-tsgolint v0.22.0 tsdown v0.21.10 Environment: Package manager pnpm v10.33.2 Node.js v24.15.0 (.node-version) Used Package Manager
pnpm
Logs
Validations
- [x] Read the Contributing Guidelines.
- [x] Check that there isn't already an issue for the same bug.
- [x] Confirm this is a Vite+ issue and not an upstream issue (Vite, Vitest, tsdown, Rolldown, or Oxc).
- [x] The provided reproduction is a minimal reproducible example.
I investigated and found that references to the tsdown/client type definitions stayed in the project after the migration.
So I added a matching export, vite-plus/pack/client, to Vite+, and made vp migrate rewrite the references.
But import statements were not the only place with these references. They were also in type declarations, in tsconfig.json, and in other places.
migrate: rewrite `tsdown/client` and `vite/client` in TS imports and tsconfig.json `types` array #1502
Description
When running vp migrate, tsdown is removed as a dependency and replaced with vite-plus equivalents. However, two patterns are currently left unrewritten, causing broken imports and TypeScript errors after migration.
relates: https://github.com/voidzero-dev/vite-plus/pull/1501
Cases to handle
1. import "tsdown/client" in .ts files
// before import "tsdown/client"; // after import "vite-plus/pack/client"; Real-world example: https://github.com/Tarrowren/css-class-intellisense/c91dd77dc1fb5acf/lezer/less/test/test-less.ts#L1
2. types array in tsconfig.json
// before { "compilerOptions": { "types": ["tsdown/client"] } } { "compilerOptions": { "types": ["vite/c...Related posts

The Effort to Reach: Designing for Fitts’s Law
GitHub’s New Dependabot Verdict Can Lag Behind Your Next Commit

