vite-plugin-virtual-link
v0.0.2
Published
Vite plugin for developing against local projects on disk — with correct dependency isolation, zero symlinks, and no changes to node_modules.
Downloads
254
Maintainers
Readme
vite-plugin-virtual-link
Vite plugin for developing against local projects on disk — with correct dependency isolation, zero symlinks, and no changes to node_modules.
Problems it solves
You're building a Vite app and want to iterate on another local package at the same time. The traditional tools (npm link, yarn link, pnpm link) cause real problems:
| | npm link / yarn link / pnpm link | vite-plugin-virtual-link |
|---|:---:|:---:|
| HMR | ❌ doesn't reliably detect changes, preserveSymlinks: true breaks HMR entirely | ✅ |
| Transitive dependencies | ❌ only places a symlink in your app's node_modules, which intermediate packages never look at | ✅ |
| Aliased packages | ❌ always uses the package's own name field | ✅ |
| Single framework instance | ❌ React, Vue, etc. loaded twice, breaking hooks, context, and state sharing | ✅ |
| No node_modules mutation | ❌ manual, stateful process that's easy to forget to clean up | ✅ |
| Dependency isolation | ❌ linked library can import packages hoisted from the app's tree | ✅ |
[!NOTE] Why not workspaces? Workspaces (npm, yarn, or pnpm) require all packages in the same repo. This plugin works across separate repos — useful for iterating on third-party packages, forks, or packages from other teams without restructuring your project.
What it does
Given two separate projects on disk:
your-app/ (Vite root) utils/ (local, NOT linked)
├── node_modules/ ├── node_modules/
│ ├── react@18 │ ├── react@18 ← same major
│ ├── lodash@3 │ └── lodash@4 ← different major
│ └── [email protected] ├── index.js
├── vite.config.ts └── package.json
└── src/
└── app.tsxWith the plugin enabled, Vite sees this:
your-app/ (Vite root)
├── node_modules/
│ ├── react@18 ← shared (same major)
│ ├── lodash@3
│ └── [virtual] utils/ ← served from /path/to/utils
│ ├── index.js
│ └── node_modules/
│ └── lodash@4 ← isolated (different major)
├── vite.config.ts
└── src/
└── app.tsx[!TIP] Shared deps stay shared (so React hooks and context work), while version-mismatched deps stay isolated (so nothing breaks at runtime).
HMR and source maps work out of the box — edits to the local project trigger hot updates in the browser just like any other module.
Install
npm install -D vite-plugin-virtual-link
# or
pnpm add -D vite-plugin-virtual-linkUsage
// vite.config.ts
import { defineConfig } from "vite";
import { virtualLink } from "vite-plugin-virtual-link";
export default defineConfig({
plugins: [
virtualLink({
// Redirect all imports of "utils" to your local copy
"utils": "/path/to/utils",
// Works for aliases! e.g. in package.json: `"utils-aliased": "npm:utils@^1.0.0"`
"utils-aliased": "/path/to/utils",
// Scoped: only redirect imports of "utils" under framework's subtree
// app/node_modules/framework/node_modules/utils 🔀 redirected
// app/node_modules/utils ⏹️ left alone
"utils": { dir: "/path/to/utils", scope: "framework" },
}),
],
});API
virtualLink(links, options?)
links
Record<string, string | { dir: string; scope?: string }> — map of package names to local project directories.
- String — absolute path to the local project directory. Redirects all imports of the package.
- Object:
dir— absolute path to the local project directory.scope— optional. Only redirect imports that would normally resolve under the named package'snode_modulessubtree. Withoutscope, behaves the same as the string form.
options.resolveMain
boolean — default false. Include "main" in the resolver's mainFields. Enable when dependencies only declare "main" in their package.json and resolution fails.
Limitations
[!WARNING] Dev mode only — the plugin relies on Vite's on-demand dep optimizer, which is not available during
vite build. The plugin warns and disables itself in build mode.
[!NOTE] Yarn PnP is not supported. The plugin relies on
node_modulesdirectory layout for dependency resolution and version comparison. Yarn Plug'n'Play replacesnode_moduleswith a runtime loader (.pnp.cjs) and zip archives, which is fundamentally incompatible with the current approach. npm and pnpm (including strict isolation) are fully supported.
License
MIT
