Skip to content

Commit b11047e

Browse files
committed
feat: extend defineConfig to support lazy plugin loading via factory functions
The `plugins` field in `defineConfig` now accepts factory functions (`() => PluginOption[]` or `async () => Promise<PluginOption[]>`) in addition to the standard `PluginOption[]` array. The factory is only called for vite commands (dev, build, test, preview) and skipped for non-vite commands (lint, fmt, check, etc.), avoiding unnecessary plugin loading overhead. When VP_COMMAND is unset (e.g., running vitest directly or via VS Code extension), plugins load by default. VP_COMMAND is set automatically by `vp` in bin.ts and injected into child process envs by the Rust resolver for synthesized subcommands. Refs vitejs/vite#22085
1 parent a2da29e commit b11047e

File tree

41 files changed

+460
-142
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+460
-142
lines changed

docs/config/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ Vite+ extends the basic Vite configuration with these additions:
2828
- [`test`](/config/test) for Vitest
2929
- [`run`](/config/run) for Vite Task
3030
- [`pack`](/config/pack) for tsdown
31-
- [`staged`](/config/staged) for staged-file checks
31+
- [`staged`](/config/staged) for staged-file checks

docs/guide/troubleshooting.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,34 @@ export default defineConfig({
8989
});
9090
```
9191

92+
## Slow config loading caused by heavy plugins
93+
94+
When `vite.config.ts` imports heavy plugins at the top level, every `import` is evaluated eagerly, even for commands like `vp lint` or `vp fmt` that don't need those plugins. This can make config loading noticeably slow.
95+
96+
Use `lazyPlugins` to wrap plugin loading. Plugins are only loaded for commands that need them (`dev`, `build`, `test`, `preview`), and skipped for everything else:
97+
98+
```ts
99+
import { defineConfig, lazyPlugins } from 'vite-plus';
100+
import myPlugin from 'vite-plugin-foo';
101+
102+
export default defineConfig({
103+
plugins: lazyPlugins(() => [myPlugin()]),
104+
});
105+
```
106+
107+
For heavy plugins that should be lazily imported, combine with dynamic `import()`:
108+
109+
```ts
110+
import { defineConfig, lazyPlugins } from 'vite-plus';
111+
112+
export default defineConfig({
113+
plugins: lazyPlugins(async () => {
114+
const { default: heavyPlugin } = await import('vite-plugin-heavy');
115+
return [heavyPlugin()];
116+
}),
117+
});
118+
```
119+
92120
## Asking for Help
93121

94122
If you are stuck, please reach out:

packages/cli/binding/src/cli/resolver.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,22 @@ impl SubcommandResolver {
6868
resolved_vite_config: Option<&ResolvedUniversalViteConfig>,
6969
envs: &Arc<FxHashMap<Arc<OsStr>, Arc<OsStr>>>,
7070
cwd: &Arc<AbsolutePath>,
71+
) -> anyhow::Result<ResolvedSubcommand> {
72+
let command_name = subcommand.command_name();
73+
let mut resolved = self.resolve_inner(subcommand, resolved_vite_config, envs, cwd).await?;
74+
// Inject VP_COMMAND so that defineConfig's plugin factory knows which command is running,
75+
// even when the subcommand is synthesized inside `vp run`.
76+
let envs = Arc::make_mut(&mut resolved.envs);
77+
envs.insert(Arc::from(OsStr::new("VP_COMMAND")), Arc::from(OsStr::new(command_name)));
78+
Ok(resolved)
79+
}
80+
81+
async fn resolve_inner(
82+
&self,
83+
subcommand: SynthesizableSubcommand,
84+
resolved_vite_config: Option<&ResolvedUniversalViteConfig>,
85+
envs: &Arc<FxHashMap<Arc<OsStr>, Arc<OsStr>>>,
86+
cwd: &Arc<AbsolutePath>,
7187
) -> anyhow::Result<ResolvedSubcommand> {
7288
match subcommand {
7389
SynthesizableSubcommand::Lint { mut args } => {

packages/cli/binding/src/cli/types.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,24 @@ pub enum SynthesizableSubcommand {
9999
},
100100
}
101101

102+
impl SynthesizableSubcommand {
103+
/// Return the command name string for use in `VP_COMMAND` env var.
104+
pub(super) fn command_name(&self) -> &'static str {
105+
match self {
106+
Self::Lint { .. } => "lint",
107+
Self::Fmt { .. } => "fmt",
108+
Self::Build { .. } => "build",
109+
Self::Test { .. } => "test",
110+
Self::Pack { .. } => "pack",
111+
Self::Dev { .. } => "dev",
112+
Self::Preview { .. } => "preview",
113+
Self::Doc { .. } => "doc",
114+
Self::Install { .. } => "install",
115+
Self::Check { .. } => "check",
116+
}
117+
}
118+
}
119+
102120
/// Top-level CLI argument parser for vite-plus.
103121
#[derive(Debug, Parser)]
104122
#[command(name = "vp", disable_help_subcommand = true)]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
4+
export default function myVitestPlugin() {
5+
return {
6+
name: 'my-vitest-plugin',
7+
configureVitest() {
8+
fs.writeFileSync(
9+
path.join(import.meta.dirname, '.vitest-plugin-loaded'),
10+
'configureVitest hook executed',
11+
);
12+
},
13+
};
14+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "vite-plugins-async-test",
3+
"private": true
4+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
> vp test # async plugins factory should load vitest plugin with configureVitest hook
2+
RUN <cwd>
3+
4+
✓ src/index.test.ts (1 test) <variable>ms
5+
6+
Test Files 1 passed (1)
7+
Tests 1 passed (1)
8+
Start at <date>
9+
Duration <variable>ms (transform <variable>ms, setup <variable>ms, import <variable>ms, tests <variable>ms, environment <variable>ms)
10+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
4+
import { expect, test } from '@voidzero-dev/vite-plus-test';
5+
6+
test('async plugin factory should load vitest plugin with configureVitest hook', () => {
7+
const markerPath = path.join(import.meta.dirname, '..', '.vitest-plugin-loaded');
8+
expect(fs.existsSync(markerPath)).toBe(true);
9+
expect(fs.readFileSync(markerPath, 'utf-8')).toBe('configureVitest hook executed');
10+
fs.unlinkSync(markerPath);
11+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"commands": [
3+
"vp test # async plugins factory should load vitest plugin with configureVitest hook"
4+
]
5+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { defineConfig, lazyPlugins } from 'vite-plus';
2+
3+
export default defineConfig({
4+
plugins: lazyPlugins(async () => {
5+
const { default: myVitestPlugin } = await import('./my-vitest-plugin');
6+
return [myVitestPlugin()];
7+
}),
8+
});

0 commit comments

Comments
 (0)