zgba Network

Vite+ — Chapter 4: Vite+ at Scale — Monorepos, Tasks, Caching, and CI

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@ 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+?”

View original article