Running Binaries
Use vpx, vp exec, and vp dlx to run package binaries and scripts without switching between local installs, downloaded packages, and project-specific tools.
Overview
vpx does one of two things, depending on its first argument:
vpx <package>runs a binary from a local or remote npm package, likenpx.vpx <file>runs a TypeScript or JavaScript file, liketsx.
The other commands give stricter control over where a binary comes from:
vp execruns a command from localnode_modules/.bindirectories, falling back toPATH.vp dlxruns a package binary without adding it as a dependency.
vpx -v or vpx --version prints the Vite+ version, like vp --version. In package.json scripts, vpx prints the version of the project's vite-plus package.
Running Package Binaries
vpx [options] <pkg[@version]> [args...]vpx looks for the binary in local node_modules/.bin directories, Vite+-managed global packages, and the system PATH, in that order. If none has it, vpx downloads the package and runs it via vp dlx. With pkg@version, --package/-p, or --shell-mode, it runs via vp dlx directly.
Options
-p, --package <name>installs one or more additional packages before running the command-c, --shell-modeexecutes the command inside a shell-s, --silentsuppresses Vite+ output and only shows the command output
Examples
vpx eslint .
vpx create-vue my-app
vpx oxlint@1.85.0 --version
vpx -p cowsay -c 'echo "hi" | cowsay'Package managers and vp run put node_modules/.bin first on PATH, so package.json scripts run the vpx bin of the project's vite-plus package. That bin does not need the global CLI, and it looks only in local node_modules/.bin directories. It runs an installed binary directly, as the global vpx does, and pkg@version, --package/-p, or --shell-mode via vp dlx. A binary that is not installed is an error, so a script never downloads a package by accident:
error: vpx: 'eslint' is not installed in node_modules
note: Add it to devDependencies, or run `vp dlx eslint` to download and run it.Running Scripts
vpx [--tsconfig <path>] [node options] <file> [args...]vpx runs TypeScript and JavaScript files directly, without a tsx or ts-node dependency. Scripts run on the project's Node.js version, the same one vp node selects, with the oxc-node loader that ships with Vite+.
vpx ./scripts/seed.ts --dry-run
vpx scripts/build.mts
vpx --watch ./server.ts
vpx --env-file=.env ./scripts/migrate.ts
vpx --tsconfig tsconfig.scripts.json ./tools/gen.tsFiles and Package Names
vpx runs its argument as a file when it is:
- a path (starting with
./,../, or/; on Windows also.\,..\, a drive such asC:\, or\\) ending in.ts,.mts,.cts,.tsx,.js,.mjs,.cjs, or.jsx - a name with one of those extensions that exists as a file, such as
scripts/seed.ts - a path to a file whose shebang runs
vpx(see Shebang Scripts)
A missing path, or a missing name with a TypeScript extension, is an error: vpx never downloads a package for it. After Node.js options, such as vpx --watch seed.ts, only a missing path is reported by vpx; Node.js reports a missing name itself. A name with a JavaScript extension that is not a file, such as highlight.js, runs the package of that name.
Supported Syntax
- TypeScript syntax that Node.js type stripping rejects: enums, namespaces, parameter properties, and JSX
- Decorators with
"experimentalDecorators": true, includingemitDecoratorMetadata; standard decorators are not supported yet - tsconfig
pathsforimport,.jsimports that point at.tsfiles, and extensionless imports - ESM and CommonJS, including
require()of.tsfiles; a.ctsfile is CommonJS, so it usesimport x = require('...')andexport =, andimport/exportdeclarations there fail with an error that suggests renaming the file to.mts - TypeScript published in
node_modules - tsconfig
jsx,jsxImportSource,jsxFactory,jsxFragmentFactory,useDefineForClassFields, andverbatimModuleSyntax; see JSX and tsconfig for the defaults
vpx does not type-check. Run vp check for that.
Options
--tsconfig <path>applies one tsconfig to every file (see tsconfig)- Node.js options before the file, such as
--watch,--inspect,--test,--env-file,--require, and--import, are passed to Node.js;--requireand--importpreloads can be TypeScript too - Everything after the file, including
--, is passed to the script
Shebang Scripts
Scripts can use vpx as their interpreter. Make the file executable and keep its extension:
#!/usr/bin/env vpx
// scripts/release.tsIn package.json Scripts
The vpx bin of the vite-plus package runs scripts on its own, so "seed": "vpx ./scripts/seed.ts" works in package.json scripts without the global CLI. It runs them on the Node.js that started it, which inside vp run and package manager scripts is the project's version.
tsconfig
vpx reads compiler options from a tsconfig but does not type-check with it. Each file uses the nearest tsconfig.json above it that includes it through files, include, exclude, or project references, as tsc does, and extends is followed. Files in node_modules use no tsconfig, and neither does a file that no tsconfig includes. Such a file cannot use paths aliases either: with a root tsconfig whose include is ["src"], a script in scripts/ that imports an alias fails with ERR_MODULE_NOT_FOUND. Add the directory to include, or pass --tsconfig. Because the config depends on the file's location, not on the working directory, vpx compiles a file the same way wherever it runs from.
--tsconfig <path> applies one config to every file instead, including files that config does not include. Without --tsconfig, an OXC_TSCONFIG_PATH or TS_NODE_PROJECT environment variable does the same.
A file that no tsconfig applies to runs as if its compilerOptions were empty:
| Option | Without a tsconfig |
|---|---|
module | The module format comes from package.json "type": ESM with "module", CommonJS otherwise. .mts is always ESM and .cts always CommonJS. With a tsconfig module of node16, node18, nodenext, es2015, or later, .ts and .tsx files run as ESM regardless of "type"; for the node* values, tsc and tsx follow "type" instead. |
paths, baseUrl | None. Imports resolve as in Node.js, plus .js to .ts or .tsx, .mjs to .mts, .cjs to .cts, and extensionless imports. |
useDefineForClassFields | true: class fields have [[Define]] semantics and run natively, as with a target of ES2022 or later. A lower target switches to [[Set]] semantics, as in tsc. |
experimentalDecorators | false: a decorator fails with an error that asks for this option. |
emitDecoratorMetadata | false. |
jsx, jsxImportSource | The automatic runtime, importing react/jsx-runtime (see JSX). |
verbatimModuleSyntax | false: imports whose bindings are unused at runtime are removed, with their side effects. |
rewriteRelativeImportExtensions | false. ./file.ts imports work either way. |
Types are removed, and enums, namespaces, and parameter properties are compiled, whatever the tsconfig says. strict, lib, and other type-checking options have no effect, and target only changes the class field default.
JSX
.jsx and .tsx files compile to calls into a JSX runtime, React's by default. Install the runtime they use, react or the package named by jsxImportSource; Vite+ does not ship one. tsconfig options select the runtime:
tsconfig jsx | Output |
|---|---|
not set, or "react-jsx" | The automatic runtime: jsx() and jsxs() from <jsxImportSource>/jsx-runtime, with jsxImportSource defaulting to react |
"react-jsxdev" | The development runtime: jsxDEV() from <jsxImportSource>/jsx-dev-runtime, with the file name, line, and column of each element |
"react" | The classic runtime: React.createElement(), or jsxFactory, with fragments as React.Fragment, or jsxFragmentFactory |
"preserve", "react-native" | The automatic runtime, because Node.js cannot run JSX |
A classic factory that the file imports, such as import { h, Fragment } from 'my-jsx', is kept even though only JSX uses it.
Pragma comments override the tsconfig for that file:
/** @jsxImportSource my-jsx *//** @jsxRuntime classic */
/** @jsx h */
/** @jsxFrag Fragment */
import { h, Fragment } from 'my-jsx';When a file has both @jsx and @jsxImportSource, @jsxImportSource selects the automatic runtime and @jsx is ignored. Pragmas are read from every comment in the file, so prose such as // @jsxImportSource is set above in an ordinary comment changes the runtime too.
A .jsx file gets a tsconfig's JSX options only when that tsconfig sets "allowJs": true, as in tsc and tsx. Otherwise it uses the defaults above.
JSX that depends on a compiler plugin is not supported. vpx compiles JSX only into calls to a runtime's jsx(), jsxs(), and jsxDEV() exports or to a classic factory. A setup that relies on a Babel or bundler plugin to transform JSX, usually with "jsx": "preserve", fails at runtime or loses what that plugin adds.
Coming from tsx
Most scripts that run with tsx run with vpx unchanged. These differences can need a change:
pathsoutsideinclude: tsx resolves tsconfigpathsfor every file.vpxapplies them only to files a tsconfig includes, so a script inscripts/next to a root tsconfig with"include": ["src"]cannot resolve its aliases. Add the directory toinclude, or pass--tsconfig(see tsconfig).- JSX default: without a tsconfig
jsxoption, tsx compiles JSX toReact.createElement(), which needsReactin scope, whilevpxuses the automatic runtime. Set"jsx": "react"to keep the classic output. Because of its default, tsx also ignores a lone/** @jsxImportSource … */comment unless the file has/** @jsxRuntime automatic */or the tsconfig sets"jsx": "react-jsx";vpxapplies the comment on its own. .ctsfiles withimportorexport: tsx compiles them to CommonJS, andvpxreports an error. Rename the file to.mts, or write it withimport x = require()andexport =.- Node.js version:
vpxneeds Node.js^22.18.0 || ^24.11.0 || >=26.0.0, while tsx also runs on older releases. - Watch mode:
tsx watchbecomesvpx --watch, which is Node.js--watch. It has no--include,--exclude, or--clear-screenoptions; use--watch-pathto watch more files. - Eval and REPL:
vpx --evaland the REPL do not transform TypeScript. node --import tsx:vpxhas no equivalent for tools that must startnodethemselves.- Other loaders: remove
--import tsxfromNODE_OPTIONS. Two loaders registered together compete for the same files.
vp exec
Use vp exec to run tools already installed in your project or available in your environment.
vp exec <command> [args...]Examples:
vp exec eslint .
vp exec tsc --noEmitvp dlx
Use vp dlx for one-off package execution without adding the package to your project dependencies.
vp dlx <package> [args...]Examples:
vp dlx create-vite
vp dlx oxlint@1.85.0 --version