Vite-plus run has two modes:
- Implicit mode is running
vpwithoutruncommand, it will run the task in the current package, it supposed to replace thepnpm/yarn runcommand. - Explicit mode is running
vp runwith run command, likevp run vite-plus#build.
⚠️ Note: Implicit mode is currently incomplete and under development. Several implementation details need to be resolved:
- Conflict resolution when user-defined tasks have the same name as built-in commands (build, test, lint)
- Precedence rules for command resolution
- Handling of edge cases with task arguments and flags
This documentation describes the current behavior and should be updated when the Implicit Mode RFC is done.
With implicit mode, vp will run the task in the current package. It can't accept more than on task. The first argument will be treated as the task name; the args following the command will be treated as the task args and bypass to the task.
Given the following package.json file:
{
"scripts": {
"build": "vite build",
"lint": "oxlint"
}
}This command equivalent to vite build:
vp buildThis command equivalent to vite build --mode production:
vp build --mode productionThis command equivalent to oxlint packages/cli/binding/index.js:
vp lint packages/cli/binding/index.jsIf the command contains #, the command will be treated as a task in the workspace subpackage.
This command equivalent to vp run cli#build:
vp cli#buildWith explicit mode, vp run will run the task in the workspace subpackage. It can accept more than one task. The arguments will be treated as the task names; the args following the -- will be treated as the task args and bypass to the task.
The vp run command will run the scoped tasks in dependency order.
Task names without # will be resolved in the current package (the package containing the nearest package.json file from the current working directory). For example:
vp run build- runs the build task in the current packagevp run test lint- ThrowOnlyOneTaskRequesterrorvp run @other/package#build- runs the build task in @other/package
Behaviors when the package root and/or the workspace root is not found:
| package root | workspace root | behaviour |
|---|---|---|
| found | not found | No possible: workspace root always fallbacks to package root. |
| not found | found | vp run build should throw an CurrentPackageNotFound error. vp run @other/package#build is still allowed |
| not found | not found | Throw an error on any vp run. The workspace root must always exist as it's where we store the cache. |
With the --recursive,-r flag, the vp run command will run the tasks in all monorepo packages.
The task name should't contain # with the --recursive,-r flag. If any task name contains #, it would cause an RecursiveRunWithScope error.
The --topological flag controls whether implicit dependencies based on package dependencies are included in the task graph. This flag affects how tasks are ordered and executed:
- With
--topological(or-t): When package A depends on package B, and both have a task with the same name (e.g., "build"), then A's task will automatically depend on B's task. This ensures that dependencies are built before dependents. - With
--no-topological: Explicitly disables topological ordering. Only explicit task dependencies defined invite-task.jsonfiles are honored. Package dependencies do not create implicit task dependencies.
Default behavior:
- When used with
--recursive, topological is enabled by default (can be disabled with--no-topological) - When used without
--recursive, topological is disabled by default (can be enabled with--topologicalor-t)
Examples:
# Recursive build with topological ordering (default)
vp run build -r
# Recursive build WITHOUT topological ordering
vp run build -r --no-topological
# Single package with topological ordering enabled
vp run app#build -t
# Multiple packages without topological ordering (default)
vp run app#build web#buildNote: --topological and --no-topological are mutually exclusive and cannot be used together. See boolean-flags.md for more information about boolean flag patterns.
Vite-plus supports two types of task dependencies:
Tasks can declare explicit dependencies in vite-task.json files using the dependsOn field:
{
"tasks": {
"lint": {
"command": "eslint src",
"cacheable": true,
"dependsOn": ["build", "core#build"]
},
"deploy": {
"command": "deploy-script --prod",
"cacheable": false,
"dependsOn": ["test", "build", "utils#lint"]
}
}
}These explicit dependencies are always honored regardless of the --topological flag.
When --topological is enabled, vite-plus automatically creates dependencies between tasks with the same name based on package dependencies:
- If package
appdepends on packageutilsinpackage.json - And both packages have a
buildscript - Then
app#buildwill automatically depend onutils#build
This works transitively - if app depends on utils, and utils depends on core, then app#build will depend on both utils#build and core#build.
Tasks are executed in topological order based on their dependencies:
- Tasks with no dependencies run first
- Tasks only run after all their dependencies have completed successfully
- Independent tasks may run in parallel when
--parallelis used
Given the following monorepo structure with topological ordering enabled:
Package Dependencies:
app → utils, ui
ui → utils
utils → (none)
Task Dependencies (explicit):
app#build → app#lint
utils#build → utils#test
The execution flow for vp run build -r --topological:
┌─────────────────────────────────────────────────────────────┐
│ Task Resolution │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step 1: Collect all build tasks │
│ ──────────────────────────────────────── │
│ • app#build │
│ • ui#build │
│ • utils#build │
│ │
│ Step 2: Add explicit dependencies │
│ ───────────────────────────────────── │
│ • app#build depends on app#lint │
│ • utils#build depends on utils#test │
│ │
│ Step 3: Add implicit dependencies (--topological) │
│ ────────────────────────────────────────────────── │
│ • app#build depends on utils#build (app→utils) │
│ • app#build depends on ui#build (app→ui) │
│ • ui#build depends on utils#build (ui→utils) │
│ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Execution Order │
├─────────────────────────────────────────────────────────────┤
│ │
│ Wave 1: No dependencies │
│ ┌──────────────┐ │
│ │ utils#test │ │
│ │ app#lint │ (can run in parallel with --parallel) │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ Wave 2: Dependencies from Wave 1 │
│ ┌──────────────┐ │
│ │ utils#build │ │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ Wave 3: Dependencies from Wave 2 │
│ ┌──────────────┐ │
│ │ ui#build │ │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ Wave 4: Final dependencies │
│ ┌──────────────┐ │
│ │ app#build │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
When a script contains && operators, it's split into subtasks that execute sequentially:
{
"scripts": {
"build": "tsc && rollup -c && echo 'Build complete'"
}
}This creates three subtasks:
package#build(subcommand 0):tscpackage#build(subcommand 1):rollup -cpackage#build:echo 'Build complete'
Cross-package dependencies connect to the first subtask, and the last subtask is considered the completion of the task.
Vite-plus builds a package graph to understand the relationships between packages in your monorepo:
┌─────────────────────────────────────────────────────────────────────────┐
│ Workspace Root │
│ (pnpm-workspace.yaml) │
└─────────────────┬───────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 1. Package Discovery │
│ │
│ packages/ packages/ packages/ │
│ ├── app/ ├── utils/ └── nameless/ │
│ │ └── package.json │ └── package.json └── package.json │
│ │ name: │ name: (no name) │
│ │ "app" │ "utils" │
└─────────────────┬───────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. Dependency Resolution │
│ │
│ app ─────depends─on────▶ utils │
│ ↓ ↑ │
│ └──────depends─on────▶ nameless │
│ │
│ Note: Nameless packages can be referenced via paths │
└─────────────────┬───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. Task Graph Construction │
│ │
│ app#build ──────────▶ utils#build │
│ ↓ ↓ │
│ app#test utils#test │
│ │
│ build ◀──── test (nameless package internal deps) │
└─────────────────┬───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. Execution Planning │
│ │
│ Execution Order: utils#build → app#build → parallel(tests) │
└─────────────────────────────────────────────────────────────┘
The package graph builder starts by discovering all packages in the workspace:
- Reads the workspace configuration (
pnpm-workspace.yaml,yarn workspaces, ornpm workspaces) - Resolves glob patterns to find all package directories
- Loads
package.jsonfrom each package directory - Creates a node in the graph for each package
For each package, the builder analyzes its dependencies:
- Examines
dependencies,devDependencies, andpeerDependenciesinpackage.json - Identifies workspace dependencies (marked with
workspace:*protocol) - Creates edges in the graph between packages based on these dependencies
- Validates that all referenced workspace packages exist
Once the package graph is built, vite-plus constructs a task graph:
- Loads tasks from
vite-task.jsonfiles in each package - Loads scripts from
package.jsonfiles - Resolves explicit task dependencies (from
dependsOnfields) - When
--topologicalis enabled, adds implicit dependencies based on package relationships - Validates that all task dependencies can be resolved
The final step creates an execution plan:
- Performs topological sorting of the task graph
- Identifies tasks that can run in parallel
- Detects circular dependencies and reports errors
- Determines the optimal execution order
Task requests are in form of task_name or pkg#task_name. They occur in two places:
- one or multiple parameters following after
vp run. - items in
dependsOn.
How task requests work:
buildinvp run buildmatches taskbuildin the current package determined by cwd.buildindependsOn: ["build"]matches taskbuildin the package where the config file is.app#buildmatches taskbuildin packageapp.app#buildraises an error if there are multiple packages namedapp.- Task requests with multiple
#are invalid. - Nameless packages are handled consistently with the rules above. They are not special cases. This means:
#buildis valid and matches taskbuildin the nameless package.#buildraises an error if there are multiple nameless packages.builddoes not match taskbuildin the nameless package.
While task requests with multiple # are invalid, packages with # in their names are valid. For example, a package named pkg#special can have a task named build. It can be referenced by executing vp run build under the folder of package pkg#special.