Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ Vite+ extends the basic Vite configuration with these additions:
- [`test`](/config/test) for Vitest
- [`run`](/config/run) for Vite Task
- [`pack`](/config/pack) for tsdown
- [`staged`](/config/staged) for staged-file checks
- [`staged`](/config/staged) for staged-file checks
31 changes: 31 additions & 0 deletions docs/guide/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,37 @@ export default defineConfig({
});
```

## Slow config loading caused by heavy plugins

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.

Pass a factory function to `plugins` in `defineConfig` to defer plugin loading. The factory is only called for commands that need plugins (`dev`, `build`, `test`, `preview`), and skipped for everything else:

```ts
import { defineConfig } from 'vite-plus';

export default defineConfig({
plugins: () => [myPlugin()],
});
```

For heavy plugins that should be lazily imported, combine with dynamic `import()`:

```ts
import { defineConfig } from 'vite-plus';

export default defineConfig({
plugins: async () => {
const { default: heavyPlugin } = await import('vite-plugin-heavy');
return [heavyPlugin()];
},
});
```

::: warning
The plugins factory requires `defineConfig` from `vite-plus`, not from `vite`. Vite's native `defineConfig` does not support factory functions for the `plugins` field.
:::

## Asking for Help

If you are stuck, please reach out:
Expand Down
34 changes: 34 additions & 0 deletions packages/cli/binding/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,24 @@ pub enum SynthesizableSubcommand {
},
}

impl SynthesizableSubcommand {
/// Return the command name string for use in `VP_COMMAND` env var.
fn command_name(&self) -> &'static str {
match self {
Self::Lint { .. } => "lint",
Self::Fmt { .. } => "fmt",
Self::Build { .. } => "build",
Self::Test { .. } => "test",
Self::Pack { .. } => "pack",
Self::Dev { .. } => "dev",
Self::Preview { .. } => "preview",
Self::Doc { .. } => "doc",
Self::Install { .. } => "install",
Self::Check { .. } => "check",
}
}
}

/// Top-level CLI argument parser for vite-plus.
#[derive(Debug, Parser)]
#[command(name = "vp", disable_help_subcommand = true)]
Expand Down Expand Up @@ -233,6 +251,22 @@ impl SubcommandResolver {
resolved_vite_config: Option<&ResolvedUniversalViteConfig>,
envs: &Arc<FxHashMap<Arc<OsStr>, Arc<OsStr>>>,
cwd: &Arc<AbsolutePath>,
) -> anyhow::Result<ResolvedSubcommand> {
let command_name = subcommand.command_name();
let mut resolved = self.resolve_inner(subcommand, resolved_vite_config, envs, cwd).await?;
// Inject VP_COMMAND so that defineConfig's plugin factory knows which command is running,
// even when the subcommand is synthesized inside `vp run`.
let envs = Arc::make_mut(&mut resolved.envs);
envs.insert(Arc::from(OsStr::new("VP_COMMAND")), Arc::from(OsStr::new(command_name)));
Ok(resolved)
}

async fn resolve_inner(
&self,
subcommand: SynthesizableSubcommand,
resolved_vite_config: Option<&ResolvedUniversalViteConfig>,
envs: &Arc<FxHashMap<Arc<OsStr>, Arc<OsStr>>>,
cwd: &Arc<AbsolutePath>,
) -> anyhow::Result<ResolvedSubcommand> {
match subcommand {
SynthesizableSubcommand::Lint { mut args } => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import fs from 'node:fs';
import path from 'node:path';

export default function myVitestPlugin() {
return {
name: 'my-vitest-plugin',
configureVitest() {
fs.writeFileSync(
path.join(import.meta.dirname, '.vitest-plugin-loaded'),
'configureVitest hook executed',
);
},
};
}
4 changes: 4 additions & 0 deletions packages/cli/snap-tests/vite-plugins-async-test/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "vite-plugins-async-test",
"private": true
}
10 changes: 10 additions & 0 deletions packages/cli/snap-tests/vite-plugins-async-test/snap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
> vp test # async plugins factory should load vitest plugin with configureVitest hook
RUN <cwd>

✓ src/index.test.ts (1 test) <variable>ms

Test Files 1 passed (1)
Tests 1 passed (1)
Start at <date>
Duration <variable>ms (transform <variable>ms, setup <variable>ms, import <variable>ms, tests <variable>ms, environment <variable>ms)

11 changes: 11 additions & 0 deletions packages/cli/snap-tests/vite-plugins-async-test/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import fs from 'node:fs';
import path from 'node:path';

import { expect, test } from '@voidzero-dev/vite-plus-test';

test('async plugin factory should load vitest plugin with configureVitest hook', () => {
const markerPath = path.join(import.meta.dirname, '..', '.vitest-plugin-loaded');
expect(fs.existsSync(markerPath)).toBe(true);
expect(fs.readFileSync(markerPath, 'utf-8')).toBe('configureVitest hook executed');
fs.unlinkSync(markerPath);
});
5 changes: 5 additions & 0 deletions packages/cli/snap-tests/vite-plugins-async-test/steps.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"commands": [
"vp test # async plugins factory should load vitest plugin with configureVitest hook"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'vite-plus';

export default defineConfig({
plugins: async () => {
const { default: myVitestPlugin } = await import('./my-vitest-plugin');
return [myVitestPlugin()];
},
});
8 changes: 8 additions & 0 deletions packages/cli/snap-tests/vite-plugins-async/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<script type="module">
console.log('hello');
</script>
</body>
</html>
8 changes: 8 additions & 0 deletions packages/cli/snap-tests/vite-plugins-async/my-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function myLazyPlugin() {
return {
name: 'my-lazy-plugin',
transformIndexHtml(html: string) {
return html.replace('</body>', '<!-- lazy-plugin-injected --></body>');
},
};
}
4 changes: 4 additions & 0 deletions packages/cli/snap-tests/vite-plugins-async/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "lazy-loading-plugins-test",
"private": true
}
3 changes: 3 additions & 0 deletions packages/cli/snap-tests/vite-plugins-async/snap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
> vp build
> cat dist/index.html | grep 'lazy-plugin-injected' # async vitePlugins() should apply plugins during build
<!-- lazy-plugin-injected --></body>
9 changes: 9 additions & 0 deletions packages/cli/snap-tests/vite-plugins-async/steps.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"commands": [
{
"command": "vp build",
"ignoreOutput": true
},
"cat dist/index.html | grep 'lazy-plugin-injected' # async vitePlugins() should apply plugins during build"
]
}
8 changes: 8 additions & 0 deletions packages/cli/snap-tests/vite-plugins-async/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'vite-plus';

export default defineConfig({
plugins: async () => {
const { default: myLazyPlugin } = await import('./my-plugin');
return [myLazyPlugin()];
},
});
8 changes: 8 additions & 0 deletions packages/cli/snap-tests/vite-plugins-run-build/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<script type="module">
console.log('hello');
</script>
</body>
</html>
8 changes: 8 additions & 0 deletions packages/cli/snap-tests/vite-plugins-run-build/my-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function myPlugin() {
return {
name: 'my-run-build-plugin',
transformIndexHtml(html: string) {
return html.replace('</body>', '<!-- run-build-plugin-injected --></body>');
},
};
}
7 changes: 7 additions & 0 deletions packages/cli/snap-tests/vite-plugins-run-build/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "vite-plugins-run-build-test",
"private": true,
"scripts": {
"build-task": "vp build"
}
}
3 changes: 3 additions & 0 deletions packages/cli/snap-tests/vite-plugins-run-build/snap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
> vp run build-task
> cat dist/index.html | grep 'run-build-plugin-injected' # vp run build should load plugins from factory
<!-- run-build-plugin-injected --></body>
9 changes: 9 additions & 0 deletions packages/cli/snap-tests/vite-plugins-run-build/steps.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"commands": [
{
"command": "vp run build-task",
"ignoreOutput": true
},
"cat dist/index.html | grep 'run-build-plugin-injected' # vp run build should load plugins from factory"
]
}
8 changes: 8 additions & 0 deletions packages/cli/snap-tests/vite-plugins-run-build/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'vite-plus';

export default defineConfig({
plugins: async () => {
const { default: myPlugin } = await import('./my-plugin');
return [myPlugin()];
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
throw new Error('Plugins should not be loaded during lint');

export default function heavyPlugin() {
return { name: 'heavy-plugin' };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "vite-plugins-skip-on-lint-test",
"private": true
}
3 changes: 3 additions & 0 deletions packages/cli/snap-tests/vite-plugins-skip-on-lint/snap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
> vp lint src/ # vp lint should not load plugins (heavy-plugin.ts throws if imported)
Found 0 warnings and 0 errors.
Finished in <variable>ms on 1 file with <variable> rules using <variable> threads.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const foo = 'bar';
5 changes: 5 additions & 0 deletions packages/cli/snap-tests/vite-plugins-skip-on-lint/steps.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"commands": [
"vp lint src/ # vp lint should not load plugins (heavy-plugin.ts throws if imported)"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'vite-plus';

export default defineConfig({
plugins: async () => {
const { default: heavyPlugin } = await import('./heavy-plugin');
return [heavyPlugin()];
},
});
8 changes: 8 additions & 0 deletions packages/cli/snap-tests/vite-plugins-sync/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<script type="module">
console.log('hello');
</script>
</body>
</html>
8 changes: 8 additions & 0 deletions packages/cli/snap-tests/vite-plugins-sync/my-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function mySyncPlugin() {
return {
name: 'my-sync-plugin',
transformIndexHtml(html: string) {
return html.replace('</body>', '<!-- sync-plugin-injected --></body>');
},
};
}
4 changes: 4 additions & 0 deletions packages/cli/snap-tests/vite-plugins-sync/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "vite-plugins-sync-test",
"private": true
}
3 changes: 3 additions & 0 deletions packages/cli/snap-tests/vite-plugins-sync/snap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
> vp build
> cat dist/index.html | grep 'sync-plugin-injected' # sync vitePlugins() should apply plugins during build
<!-- sync-plugin-injected --></body>
9 changes: 9 additions & 0 deletions packages/cli/snap-tests/vite-plugins-sync/steps.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"commands": [
{
"command": "vp build",
"ignoreOutput": true
},
"cat dist/index.html | grep 'sync-plugin-injected' # sync vitePlugins() should apply plugins during build"
]
}
7 changes: 7 additions & 0 deletions packages/cli/snap-tests/vite-plugins-sync/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'vite-plus';

import mySyncPlugin from './my-plugin';

export default defineConfig({
plugins: () => [mySyncPlugin()],
});
Loading
Loading