# How Vite+ Workspace Configuration Loads Packages and Constructs Task Graphs

> Learn how Vite+ workspace configuration efficiently loads monorepo packages and constructs task graphs. Discover its optimized package discovery and deterministic cycle handling.

- Repository: [VoidZero/vite-plus](https://github.com/voidzero-dev/vite-plus)
- Tags: internals
- Published: 2026-03-16

---

**Vite+ discovers monorepo packages by traversing directory trees to locate workspace markers, indexes them for O(1) lookups, and delegates to the `vite-task` crate to build a directed task graph that is topologically sorted using `petgraph` with deterministic cycle handling via Tarjan’s algorithm.**

The `voidzero-dev/vite-plus` repository provides a Rust-powered monorepo task runner that treats your codebase as a dynamic **workspace configuration**. This system operates in two distinct phases: first discovering and indexing all packages, then constructing a dependency-aware task graph for execution.

## Discovering and Loading the Workspace Package Graph

When a workspace-aware command like `vp run` or `vp exec` is invoked, the CLI initializes the **workspace configuration** by locating the monorepo root and building an in-memory representation of all packages.

### Locating the Workspace Root

The process begins in [`packages/cli/binding/src/exec/workspace.rs`](https://github.com/voidzero-dev/vite-plus/blob/main/packages/cli/binding/src/exec/workspace.rs). The function `vite_workspace::find_workspace_root(cwd)` traverses upward from the current working directory to identify a **workspace marker**—either a [`pnpm-workspace.yaml`](https://github.com/voidzero-dev/vite-plus/blob/main/pnpm-workspace.yaml) file or a [`package.json`](https://github.com/voidzero-dev/vite-plus/blob/main/package.json) containing a `workspaces` field. This function returns a `WorkspaceRoot` struct containing the absolute path and workspace file type.

### Constructing the Package Graph

Once the root is identified, `vite_workspace::load_package_graph(&workspace_root)` parses every [`package.json`](https://github.com/voidzero-dev/vite-plus/blob/main/package.json) within the workspace boundaries, including nested `packages/**` directories. This builds a directed graph where each node is a `PackageInfo` struct containing the package name, path, absolute path, and dependency metadata.

### Indexing for Fast Lookups

To enable performant querying, `IndexedPackageGraph::index(graph)` creates an auxiliary `PackageNodeIndex` structure. This index allows the system to locate any package by name or path in **O(1)** time, which is critical when resolving filters across large monorepos.

### Resolving Package Queries

The CLI parses flags such as `--filter`, `--recursive`, and `--resume-from` via `args.packages.into_package_query()`. This query is resolved against the indexed graph to produce a sub-graph containing only the selected packages and their implicit dependencies. If selectors match no packages, the system emits a warning (lines 48-54 in [`workspace.rs`](https://github.com/voidzero-dev/vite-plus/blob/main/workspace.rs)).

## Building and Ordering the Task Graph

After establishing the package sub-graph, Vite+ constructs the executable task pipeline.

### Delegating to vite-task

Vite+ does not embed task-graph logic directly in the CLI crate. Instead, it delegates to the **`vite-task`** crate via `vite_task::TaskGraphBuilder::new(&workspace_root)`. This builder receives the `PackageGraph` produced in the discovery phase.

### Defining Task Dependencies

For each package, the `TaskGraphBuilder` reads task definitions from **[`vite-task.json`](https://github.com/voidzero-dev/vite-plus/blob/main/vite-task.json)** or the `run` section of [`vite.config.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/vite.config.ts). These definitions specify commands, explicit `dependsOn` arrays, and environment variables required for execution.

### Implicit Dependencies and Topological Linking

When the `--topological` flag is active (default for `--recursive`), the builder automatically injects **implicit edges** based on package-level dependencies. For example, if `app` lists `lib` in its [`package.json`](https://github.com/voidzero-dev/vite-plus/blob/main/package.json) `dependencies`, the task `app#build` implicitly depends on `lib#build`.

### Cycle Detection and Deterministic Sorting

The builder produces a `DiGraphMap<PackageNodeIndex, ()>` from the `petgraph` library. The CLI then calls `topological_sort_packages(&subgraph)` (lines 59-66 in [`workspace.rs`](https://github.com/voidzero-dev/vite-plus/blob/main/workspace.rs)), which first attempts a standard `petgraph::algo::toposort`. If cycles are detected, it falls back to `tarjan_scc` (Tarjan’s Strongly Connected Components algorithm) to produce a deterministic execution order that respects all acyclic dependencies.

### Execution Modes

The final sorted list is processed either sequentially or in parallel. The `--parallel` flag triggers the parallel branch in [`workspace.rs`](https://github.com/voidzero-dev/vite-plus/blob/main/workspace.rs), spawning each task as a separate `tokio::process::Command` with prefixed output (e.g., `pkg_name$`).

## Practical Implementation Examples

### Detecting the Workspace via Node API

```typescript
import { detectWorkspace } from 'vite-plus';

async function listPackages() {
  const info = await detectWorkspace(process.cwd());
  console.log('Root:', info.root);
  console.log('Monorepo?', info.isMonorepo);
}
listPackages();

```

### Running Tasks Recursively

```bash

# Build all packages respecting dependency order

vp run build -r

```

Behind the scenes:

1. `-r` (`--recursive`) selects all workspace packages via `into_package_query()`
2. `IndexedPackageGraph` creates a sub-graph of all packages
3. `topological_sort_packages` orders them (dependencies before dependents)
4. Tasks execute according to [`vite-task.json`](https://github.com/voidzero-dev/vite-plus/blob/main/vite-task.json) definitions

### Parallel Execution with Prefixing

```bash
vp exec echo "Hello" -p

```

The `-p` (`--parallel`) flag executes commands concurrently across packages, with each line prefixed by the package name unless only one package is selected.

### Filtering Specific Packages

```bash

# Run tests only in packages matching "*ui*"

vp run test --filter "*ui*"

```

Unmatched filter patterns trigger warnings (lines 48-54 in [`workspace.rs`](https://github.com/voidzero-dev/vite-plus/blob/main/workspace.rs)), while matched packages form a filtered sub-graph that maintains topological integrity.

## Summary

- **Workspace Discovery**: `find_workspace_root` locates markers ([`pnpm-workspace.yaml`](https://github.com/voidzero-dev/vite-plus/blob/main/pnpm-workspace.yaml) or [`package.json`](https://github.com/voidzero-dev/vite-plus/blob/main/package.json) with `workspaces`) and `load_package_graph` builds the initial directed graph in [`packages/cli/binding/src/exec/workspace.rs`](https://github.com/voidzero-dev/vite-plus/blob/main/packages/cli/binding/src/exec/workspace.rs).
- **Fast Indexing**: `IndexedPackageGraph::index` enables O(1) package lookups for query resolution.
- **Task Delegation**: The `vite-task` crate's `TaskGraphBuilder` assembles the final execution graph from [`vite-task.json`](https://github.com/voidzero-dev/vite-plus/blob/main/vite-task.json) definitions and implicit package dependencies.
- **Deterministic Ordering**: `topological_sort_packages` uses `petgraph::algo::toposort` with a `tarjan_scc` fallback to handle cycles deterministically.
- **Flexible Execution**: Supports both sequential and parallel (`--parallel`) modes with filtered sub-graphs.

## Frequently Asked Questions

### How does Vite+ determine which directory is the workspace root?

Vite+ uses `vite_workspace::find_workspace_root(cwd)` to walk up the directory tree from the current working directory until it finds either a [`pnpm-workspace.yaml`](https://github.com/voidzero-dev/vite-plus/blob/main/pnpm-workspace.yaml) file or a [`package.json`](https://github.com/voidzero-dev/vite-plus/blob/main/package.json) containing a `workspaces` array. This function returns a `WorkspaceRoot` struct with the absolute path and detected workspace type.

### What happens if my monorepo has circular dependencies?

The system first attempts a standard topological sort via `petgraph::algo::toposort`. If cycles exist, it falls back to `tarjan_scc` (Tarjan’s Strongly Connected Components algorithm) to break the cycle deterministically while preserving all valid dependency constraints, ensuring stable execution order even in complex graphs.

### Where does Vite+ store task definitions for each package?

Task definitions are read from **[`vite-task.json`](https://github.com/voidzero-dev/vite-plus/blob/main/vite-task.json)** in the package root or from the `run` section of **[`vite.config.ts`](https://github.com/voidzero-dev/vite-plus/blob/main/vite.config.ts)**. The `TaskGraphBuilder` in the `vite-task` crate parses these files to extract command configurations, explicit `dependsOn` relationships, and environment variable requirements.

### How does filtering affect the task graph construction?

When using `--filter` or similar flags, the CLI converts arguments into a `PackageQuery` via `args.packages.into_package_query()`. The `IndexedPackageGraph` resolves this to a sub-graph containing only matching packages (plus their implicit dependencies if `--recursive` is used). This filtered sub-graph is then topologically sorted independently, ensuring dependencies are still respected even for partial workspace execution.