Skip to content
Closed
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
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export default extendConfig(
{ text: 'Build', link: '/config/build' },
{ text: 'Pack', link: '/config/pack' },
{ text: 'Staged', link: '/config/staged' },
{ text: 'Troubleshooting', link: '/config/troubleshooting' },
],
},
],
Expand Down
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
41 changes: 41 additions & 0 deletions docs/config/troubleshooting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Configuration Troubleshooting

Use this page when your Vite+ configuration is not behaving the way you expect.

## 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.

Use the `lazy` field in `defineConfig` to defer heavy plugin loading. Plugins provided through `lazy` are only resolved when Vite actually needs them:

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

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

You can keep lightweight plugins inline and defer only the expensive ones. Plugins from `lazy` are appended after existing plugins:

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

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

The resulting plugin order is: `[lightPlugin(), heavyPlugin()]`.

::: info
The `lazy` field is a Vite+ extension. We plan to support this in upstream Vite in the future.
:::
8 changes: 8 additions & 0 deletions packages/cli/snap-tests/lazy-loading-plugins/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/lazy-loading-plugins/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/lazy-loading-plugins/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "lazy-loading-plugins-test",
"private": true
}
4 changes: 4 additions & 0 deletions packages/cli/snap-tests/lazy-loading-plugins/snap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
> # Test that plugins loaded via lazy field are applied during build
> vp build
> cat dist/index.html | grep 'lazy-plugin-injected'
<!-- lazy-plugin-injected --></body>
10 changes: 10 additions & 0 deletions packages/cli/snap-tests/lazy-loading-plugins/steps.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"commands": [
"# Test that plugins loaded via lazy field are applied during build",
{
"command": "vp build",
"ignoreOutput": true
},
"cat dist/index.html | grep 'lazy-plugin-injected'"
]
}
8 changes: 8 additions & 0 deletions packages/cli/snap-tests/lazy-loading-plugins/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({
lazy: async () => {
const { default: myLazyPlugin } = await import('./my-plugin');
return { plugins: [myLazyPlugin()] };
},
});
60 changes: 60 additions & 0 deletions packages/cli/src/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,63 @@ test('should handle async function config without lazy', async () => {
expect(config.plugins?.length).toBe(1);
expect((config.plugins?.[0] as { name: string })?.name).toBe('no-lazy');
});

test('should support async/await lazy loading of plugins', async () => {
const config = await defineConfig({
lazy: async () => {
const plugins = [{ name: 'async-lazy' }];
return { plugins };
},
});
expect(config.plugins?.length).toBe(1);
expect((config.plugins?.[0] as { name: string })?.name).toBe('async-lazy');
});

test('should merge async/await lazy plugins with existing plugins', async () => {
const config = await defineConfig({
plugins: [{ name: 'existing' }],
lazy: async () => {
const plugins = [{ name: 'async-lazy' }];
return { plugins };
},
});
expect(config.plugins?.length).toBe(2);
expect((config.plugins?.[0] as { name: string })?.name).toBe('existing');
expect((config.plugins?.[1] as { name: string })?.name).toBe('async-lazy');
});

test('should support async/await lazy with dynamic import pattern', async () => {
const config = await defineConfig({
lazy: async () => {
// simulates: const { default: plugin } = await import('heavy-plugin')
const plugin = await Promise.resolve({ name: 'dynamic-import-plugin' });
return { plugins: [plugin] };
},
});
expect(config.plugins?.length).toBe(1);
expect((config.plugins?.[0] as { name: string })?.name).toBe('dynamic-import-plugin');
});

test('should support async/await lazy in async function config', async () => {
const configFn = defineConfig(async () => ({
lazy: async () => {
const plugins = [{ name: 'async-fn-async-lazy' }];
return { plugins };
},
}));
const config = await configFn({ command: 'build', mode: 'production' });
expect(config.plugins?.length).toBe(1);
expect((config.plugins?.[0] as { name: string })?.name).toBe('async-fn-async-lazy');
});

test('should support async/await lazy in sync function config', async () => {
const configFn = defineConfig(() => ({
lazy: async () => {
const plugins = [{ name: 'sync-fn-async-lazy' }];
return { plugins };
},
}));
const config = await configFn({ command: 'build', mode: 'production' });
expect(config.plugins?.length).toBe(1);
expect((config.plugins?.[0] as { name: string })?.name).toBe('sync-fn-async-lazy');
});
Loading