How Vite+ Caches Monorepo Tasks Across Build Processes: Delegation to vite-task
Vite+ delegates monorepo task caching to the underlying vite-task library via a UserCacheConfig attached to every synthetic plan request, enabling deterministic cache keys based on command arguments, environment variables, input hashes, and dependency graphs.
Vite+ (from the voidzero-dev/vite-plus repository) implements a sophisticated caching strategy for monorepo tasks that spans across different build processes without maintaining its own ad-hoc cache implementation. Instead of reinventing cache invalidation logic, the CLI delegates all caching responsibilities to the vite-task runner while controlling behavior through a strongly-typed configuration system.
The Delegation Pattern: Why Vite+ Doesn't Reinvent the Wheel
Rather than implementing custom caching logic directly in the CLI, Vite+ follows a delegation pattern. The CLI acts as an orchestrator that prepares cache configurations and hands them off to the specialized task runner. This separation of concerns allows the vite-task library to handle complex cache key calculation, storage, and invalidation while the CLI focuses on command resolution and user interface.
How the Cache Configuration Flows Through the System
The caching mechanism follows a precise pipeline from command resolution to task execution.
Resolving Subcommands with Cache Policies
In packages/cli/binding/src/cli.rs (lines 266-381 and 406-427), the resolver converts synthesizable CLI subcommands into concrete ResolvedSubcommand instances. During this transformation, it constructs a UserCacheConfig that determines whether caching is enabled for the specific command.
For standard build commands, the resolver invokes UserCacheConfig::with_config(EnabledCacheConfig { ... }), while long-running commands like dev and preview explicitly disable caching using UserCacheConfig::disabled() to prevent stale artifacts during development sessions.
Creating the Synthetic Plan Request
Once resolved, the subcommand transforms into a SyntheticPlanRequest through the into_synthetic_plan_request method. This request carries critical metadata including the program, args, environment map, and crucially, the cache_config object that will guide the task runner's caching behavior.
Session Initialization and Task Execution
The execute_vite_task_command function constructs a Session from vite_task::Session and passes the synthetic plan to the task runner's planner. At this point, control transfers to vite-task, which consumes the UserCacheConfig to determine cache lookup strategy.
Cache Key Composition and Storage
Understanding how vite-task generates cache keys reveals why this delegation strategy works effectively for monorepos.
Deterministic Key Generation
Inside vite-task, the cache key derives from several deterministic inputs:
- The command line composition (
program+args) - Environment variables specified in
EnabledCacheConfig.env(such asVITE_*for build tasks orOXLINT_TSGOLINT_PATHfor lint tasks) - Input file hashes provided through
EnabledCacheConfig.input - The monorepo dependency graph, with implicit dependencies added automatically
If any tracked component changes, the key changes, triggering a cache miss and fresh execution.
Global Storage Layout
Cached outputs reside in the global cache root at $HOME/.cache/vite-plus/task/..., as evidenced by the cache layout definitions in vite_js_runtime::cache. Each unique key maps to a directory containing the task's stdout, exit status, and generated files. This global location enables cache persistence beyond single process lifetimes.
Monorepo-Aware Caching Strategies
Vite+ leverages vite-task to handle complex monorepo scenarios that simple file hashing cannot address.
Topological Dependency Tracking
The task runner builds a complete task graph from each package's vite-task.json and package.json dependencies. When one package's build depends on another's, the cache key for the dependent task incorporates the upstream task's output hash. This topological ordering ensures that changes in dependency chains correctly invalidate dependent caches.
Cross-Process Persistence
Because the cache lives in the user's home directory rather than temporary process memory, any subsequent vp run ... or vp build -r invocation reuses the same artifacts even after the original CLI process exits. This cross-process reuse dramatically reduces rebuild times in CI/CD pipelines and local development workflows.
Configuring Cache Behavior
Developers interact with this system through the CLI and configuration objects.
Enable caching for build commands with environment tracking:
// From packages/cli/binding/src/cli.rs
UserCacheConfig::with_config(EnabledCacheConfig {
// Track any VITE_* env var that might affect the build
env: Some(Box::new([Str::from("VITE_*")])),
untracked_env: None,
input: None,
})
Disable caching for long-running development servers:
// Explicitly disable for dev/preview commands
UserCacheConfig::disabled()
Command-line usage:
# Recursive build with cache enabled (default)
vp run build -r
# Force fresh build ignoring cache
vp run build -r --no-cache
# Inspect cache entries
ls ~/.cache/vite-plus/task
Summary
- Vite+ delegates all caching logic to the
vite-tasklibrary rather than implementing ad-hoc solutions - The
UserCacheConfigstruct controls caching behavior per command, withEnabledCacheConfigfor standard tasks anddisabled()for long-running processes - Cache keys incorporate command arguments, specified environment variables, input hashes, and monorepo dependency graphs for deterministic invalidation
- Global storage at
$HOME/.cache/vite-plus/task/enables cross-process cache reuse - Topological dependency tracking ensures upstream changes correctly invalidate downstream tasks
Frequently Asked Questions
Does Vite+ implement its own cache storage mechanism?
No. According to the source code in packages/cli/binding/src/cli.rs, Vite+ does not implement custom cache storage. Instead, it constructs UserCacheConfig objects and passes them to the vite-task runner, which handles all cache key calculation, storage, and retrieval operations in the global cache directory.
Why are dev and preview commands cached differently than build?
Long-running commands like dev and preview explicitly use UserCacheConfig::disabled() as defined in the resolver logic (lines 406-427 of cli.rs). This prevents caching because these processes watch files continuously and require fresh execution on every startup, whereas build commands benefit from caching intermediate results across runs.
How does Vite+ handle cache invalidation when dependencies change?
The vite-task library automatically incorporates the monorepo dependency graph into cache key calculation. When a package's dependencies change or an upstream task's output hash changes, the dependent task's cache key changes, causing a cache miss. This topological invalidation ensures builds remain consistent when dependency trees evolve.
Can developers customize which environment variables affect the cache?
Yes. Through the EnabledCacheConfig.env field, developers can specify which environment variables should participate in cache key generation. For example, build commands track VITE_* variables, while lint commands might track OXLINT_TSGOLINT_PATH. The CLI also respects --cache and --no-cache flags parsed by vite-task for runtime control.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →