In the previous chapter, we used Vite+ for a normal application:
vp dev
vp check
vp test
vp build
That workflow is already useful.
But there's another situation where Vite+ becomes much more interesting:
What happens when your project becomes a lot bigger?
Imagine that one application becomes five, then you add shared UI components, then shared utilities, then a design system, then internal libraries.
Eventually, your repository might look like this:
company-project/
├── apps/
│ ├── web/
│ ├── admin/
│ └── docs/
│
├── packages/
│ ├── ui/
│ ├── utils/
│ ├── config/
│ └── api/
│
└── package.json
This is a monorepo.
And this is where running every task all the time can become expensive.
Vite+ includes Vite Task specifically to handle dependency-aware task execution and caching in monorepos.
1. What Is a Monorepo?
Before talking about Vite+, let's understand the problem.
A monorepo is simply a repository containing multiple projects or packages.
For example:
my-company/
│
├── apps/
│ ├── web/
│ └── mobile/
│
└── packages/
├── ui/
├── utils/
└── types/
Here we might have:
-
web→ the main website -
mobile→ a mobile application -
ui→ shared UI components -
utils→ shared utilities -
types→ shared TypeScript types
The advantage is that everything lives together.
A developer can change a shared package and immediately test the applications that use it.
But this also creates a new problem:
Which tasks actually need to run?
2. The Dependency Problem
Let's say we have:
web
│
└── ui
│
└── utils
This means:
web depends on ui
ui depends on utils
Now imagine you change something inside:
packages/utils/
Should we rebuild everything? Maybe.
But what if you only changed:
apps/docs/
Do we really need to rebuild web? Probably not.
A large monorepo can contain hundreds of tasks.
Running all of them after every small change wastes time.
This is where a task system becomes important.
3. What Is a Task?
A task is simply something your project needs to do.
For example:
build
test
lint
typecheck
A package might have:
{
"scripts": {
"build": "vite build",
"test": "vitest",
"lint": "oxlint"
}
}
These are tasks, in a small application, running them manually isn't difficult.
In a monorepo, however, you might have:
20 packages
×
4 tasks
=
80 possible tasks
Now the task runner has a much bigger job.
4. Enter vp run
Vite+ provides:
vp run
This command can run package scripts and monorepo tasks while understanding dependencies between them. It also supports task caching.
For example:
vp run build
You can think of this as saying:
"Run the build tasks that are relevant to this project."
Instead of manually navigating through every package.
5. Task Dependencies
Let's use a simple example.
Imagine:
packages/utils
↓
packages/ui
↓
apps/web
The relationships are:
utils
↓
ui
↓
web
If web needs ui, and ui needs utils, then the build order matters.
You can't necessarily build:
web
before:
ui
and you can't build:
ui
before:
utils
The dependency graph is therefore:
utils
│
▼
ui
│
▼
web
A task runner can use this graph to determine what needs to happen first.
6. Why Dependency-Aware Execution Matters
Imagine your repository contains:
apps/
├── web
├── admin
└── docs
packages/
├── ui
├── utils
├── api
└── config
Suppose:
web → ui → utils
admin → ui → utils
docs → ui
If utils changes, both web and admin may be affected.
But docs might not depend directly on utils.
A dependency-aware task runner can use those relationships instead of blindly running everything.
Conceptually:
utils changed
│
├── ui
│ ├── web
│ └── admin
│
└── unrelated packages
↓
skip
That's the important idea.
7. Caching
Now we reach one of the most useful features.
Imagine you run:
vp run build # takes 45 seconds
Then you run the exact same command again.
If nothing relevant changed, rebuilding everything from scratch is unnecessary.
This is where caching comes in.
Conceptually:
First run
Source code
↓
Build
↓
45 seconds
↓
Save result
Then:
Second run
Same inputs
↓
Cache lookup
↓
Reuse previous result
Instead of "45 seconds", you can potentially get a result much faster.
Vite+ describes vp run as providing caching and dependency-aware scheduling for monorepo tasks.
8. What Does the Cache Actually Mean?
A common misunderstanding is:
"The cache means Vite+ never runs the command again."
That's not quite right.
The important question is whether the task's relevant inputs have changed.
Think about:
Input
↓
Task
↓
Output
For example:
src/
package.json
tsconfig.json
environment
↓
build
↓
dist/
If the inputs are unchanged, the previous result may be reusable.
If an important input changes:
src/Button.tsx
then the task may need to run again.
Conceptually:
Same inputs
↓
Cache hit
↓
Reuse result
versus:
Changed inputs
↓
Cache miss
↓
Run task again
This is the basic idea behind task caching.
9. Cache Hits and Cache Misses
You'll often hear two terms:
Cache hit
The previous result can be reused.
web#build — cache hit
Cache miss
The task needs to run again.
web#build — cache miss
For example:
First run:
utils#build → cache miss
ui#build → cache miss
web#build → cache miss
Everything has to run.
Then you run it again without changing anything:
Second run:
utils#build → cache hit
ui#build → cache hit
web#build → cache hit
The exact behavior depends on the task inputs and configuration, but this is the mental model developers should have.
10. Why This Matters in CI
Now let's move from your laptop to CI.
A typical CI pipeline might look like:
Developer pushes code
↓
GitHub
↓
CI starts
↓
Install deps
↓
Check
↓
Test
↓
Build
Without caching, every CI run may repeat expensive work.
Imagine:
Install → 30 sec
Lint → 20 sec
Tests → 60 sec
Build → 90 sec
That's already several minutes.
Now imagine a repository with dozens of packages, the wasted time adds up quickly.
11. Vite+ and GitHub Actions
Vite+ provides an official GitHub Action called setup-vp for installing Vite+ in GitHub Actions. The current repository documentation recommends pinning the action to an exact release or commit rather than using the old floating v1 tag.
A simplified example looks like this:
name: CI
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: voidzero-dev/setup-vp@<setup-vp-version>
with:
node-version: '22'
cache: true
- run: vp install
- run: vp check
- run: vp test
- run: vp build
The important part isn't the exact YAML,, it's the workflow:
GitHub Actions
↓
setup-vp
↓
vp install
↓
vp check
↓
vp test
↓
vp build
The same vp commands you use locally can be used in CI. That consistency is valuable.
12. Local Development and CI Use the Same Interface
One thing I particularly like about this approach is that developers don't have to learn a completely different system for CI.
Locally:
vp check
vp test
vp build
CI:
- run: vp check
- run: vp test
- run: vp build
The commands are the same.
That means when something fails in CI, you can often reproduce the same command locally.
For example:
vp test
Instead of trying to understand:
some-custom-ci-script.sh
that internally calls several other tools.
13. A Monorepo Example
Let's imagine a real project.
acme/
├── apps/
│ ├── storefront/
│ └── dashboard/
│
├── packages/
│ ├── ui/
│ ├── auth/
│ ├── api-client/
│ └── utils/
│
├── package.json
└── vite.config.ts
Dependencies might look like:
┌── ui ────────┐
│ │
storefront ──────┤ │
│ ▼
└── api-client
│
▼
utils
And:
dashboard
│
├── ui
│
└── auth
│
▼
utils
Now imagine you modify:
packages/utils/
Several projects could be affected.
A task runner can use the dependency graph to determine the appropriate execution order.
Instead of manually figuring out:
1. Build utils
2. Build api-client
3. Build ui
4. Build storefront
5. Build auth
6. Build dashboard
you can use the task system to coordinate those tasks.
14. Parallelism
There's another important idea here:
Not every task needs to run sequentially.
Suppose:
storefront
↓
ui
dashboard
↓
auth
The two branches are independent.
Conceptually, they can be processed like this:
ui ────────> storefront
/
Start ──
\
auth ──────> dashboard
Instead of:
ui
↓
storefront
↓
auth
↓
dashboard
a task system can execute independent work concurrently where appropriate.
This becomes increasingly important as the repository grows.
The goal is simple:
Don't make one task wait for another task if there is no dependency between them.
15. vp run Is More Than npm run
At first, you might look at:
vp run build
and think:
"Isn't that basically the same as
npm run build?"
For a simple project, the difference may not feel dramatic.
But in a monorepo, the task system adds concepts such as:
- dependency-aware execution
- task graphs
- caching
- filtering
- parallel execution
- task-level configuration
So:
npm run build
is primarily: Run this script
Whereas:
vp run build
can be thought of as: Run the appropriate build tasks across the project while understanding their relationships and cached results.
That distinction becomes important at scale.
16. Filtering Tasks
Large monorepos don't always need everything to run.
Sometimes you only want to work with one package.
For example, you might want to run a task for:
apps/storefront
rather than the entire repository.
Vite+ supports filtering in its task workflow, for example:
vp run --filter storefront build
The exact filters you use depend on your workspace structure and task configuration.
The important idea is:
Whole repository
↓
filter
↓
Relevant packages
↓
Relevant tasks
This is particularly useful when working locally.
17. One Configuration for the Whole Toolchain
We saw in Chapter 3 that Vite+ can use:
vite.config.ts
as a central configuration file.
For a larger repository, that becomes even more useful.
The configuration can include:
import { defineConfig } from 'vite-plus'
export default defineConfig({
plugins: [],
test: {
include: ['src/**/*.test.ts'],
},
lint: {
ignorePatterns: ['dist/**'],
},
fmt: {
semi: true,
singleQuote: true,
},
run: {
tasks: {
'generate:icons': {
command: 'node scripts/generate-icons.js',
},
},
},
})
This means the same configuration surface can describe parts of:
Development
↓
Testing
↓
Linting
↓
Formatting
↓
Task execution
The current Vite+ repository documents this unified configuration approach.
18. But Caching Isn't Magic
This is important.
Caching sounds simple:
Nothing changed
↓
Use cache
Real projects are more complicated.
A task can read files you didn't expect.
A build can generate files.
A tool can modify something inside node_modules.
Environment variables can affect output.
Generated files can become inputs to another task.
This means cache correctness depends on correctly understanding the task's inputs and outputs.
The Vite+ project has continued improving task-cache reliability, and its release history includes fixes around automatically tracked inputs and caching behavior.
So when introducing caching to a large repository, you should still verify that tasks are configured correctly.
19. Don't Treat Cache Hits as Guaranteed
This is another useful lesson.
You might expect:
vp run build
followed by:
vp run build
to always produce 100% cache hits.
But real-world projects can have tasks that modify their own inputs or produce files that affect the task fingerprint.
The Vite+ issue tracker has documented examples of cache misses caused by generated files and dependency/runtime cache directories.
This doesn't mean caching isn't useful.
It means developers should understand what the cache is actually tracking.
A good mental model is:
Caching is a correctness feature first and a performance feature second.
If the system cannot prove that the previous result is still valid, rerunning the task is safer than incorrectly reusing an old result.
20. A Practical CI Pipeline
For a monorepo, you might eventually have something like:
Pull Request
│
▼
Install deps
│
▼
vp check
│
▼
vp test
│
▼
vp run build
│
▼
Deploy / Publish
The important thing is that developers and CI share the same vocabulary.
Local:
vp check
vp test
vp run build
CI:
- run: vp check
- run: vp test
- run: vp run build
That reduces the gap between:
"It works on my machine."
and:
"Why does CI use completely different commands?"
21. When Does This Actually Matter?
If you're building a small personal website:
1 app
5 components
1 package
you probably won't notice a huge difference.
You can happily use:
vp dev
vp test
vp build
and move on.
But imagine:
10 applications
30 packages
100 developers
hundreds of tasks
Now task execution becomes a real engineering problem.
You care about:
- What changed?
- What needs rebuilding?
- What can run in parallel?
- What can use a previous result?
- What should CI execute?
- How can local and CI workflows stay consistent?
That's the environment where Vite Task and vp run become much more important.
22. The Big Picture
Let's compare the two situations.
Small application
Developer
│
├── vp dev
├── vp check
├── vp test
└── vp build
Simple.
Large monorepo
Vite+
│
┌──────┴──────┐
│ │
Local CI
│ │
vp run vp run
│ │
┌─────┴─────┐ ┌──┴───┐
│ │ │ │
Tasks Cache Tasks Cache
│ │ │ │
└─────┬─────┘ └──┬───┘
│ │
└──────┬──────┘
│
Dependency graph
This is where Vite+ starts looking less like:
"A nicer command for Vite."
and more like:
A unified development and task-execution environment for a modern JavaScript repository.
23. Ssummary
In this chapter, we moved from a single application to large repositories, the main concepts were:
Monorepos
Multiple applications and packages living in one repository.
Tasks
Operations such as:
build
test
lint
typecheck
Dependency graphs
Understanding which packages depend on which other packages.
Caching
Reusing previous task results when the relevant inputs haven't changed.
Parallel execution
Running independent tasks at the same time.
CI
Using the same vp workflow locally and in GitHub Actions.
The important command to remember is:
vp run
For a small project, you might barely notice it.
For a large monorepo, it can become one of the most important parts of the Vite+ workflow.
What comes next ?
We've now covered a lot of things, that leaves one big question:
Should you actually use Vite+?
It's easy to get excited about a new toolchain, but every tool has trade-offs.
In next chapter; we'll look at:
- When Vite+ makes sense
- When it might be unnecessary
- Migrating an existing project
- The benefits and trade-offs
- The beta/early-stage considerations
- How to decide whether Vite+ fits your project
- What to consider before adopting it in a team
That will bring the series together and give you a practical answer to the question:
"Should I use Vite+?"
Top comments (0)