Skip to content
Merged
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
9 changes: 2 additions & 7 deletions .github/actions/build-upstream/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ runs:
pnpm --filter vite build-types
pnpm --filter=@voidzero-dev/vite-plus-core build
pnpm --filter=@voidzero-dev/vite-plus-test build
pnpm --filter=vite-plus build-ts
pnpm --filter=vite-plus-cli build-ts

# NAPI builds - only run on cache miss (slow, especially on Windows)
# Must run before vite-plus/vite-plus-cli TypeScript builds which depend on the bindings
Expand Down Expand Up @@ -94,10 +96,3 @@ runs:
packages/global/binding/index.js
packages/global/binding/index.d.ts
key: ${{ steps.cache-key.outputs.key }}

# Build vite-plus TypeScript after native bindings are ready
- name: Build vite-plus TypeScript packages
shell: bash
run: |
pnpm --filter=vite-plus build-ts
pnpm --filter=vite-plus-cli build-ts
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"install-global-cli": "pnpm --filter=vite-plus-cli copy-binding && tool install-global-cli vite",
"install-global-cli:local": "pnpm --filter=vite-plus-cli copy-binding && tool install-global-cli vp",
"tsgo": "tsgo -b tsconfig.json",
"lint": "vite lint --type-aware --type-check --threads 4",
"lint": "vite lint --type-aware --threads 4",
"test": "vite test run && pnpm -r snap-test",
"fmt": "vite fmt",
"test:unit": "vite test run",
Expand Down
47 changes: 43 additions & 4 deletions packages/cli/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
*/

import { existsSync, globSync, readdirSync, statSync } from 'node:fs';
import { copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import { copyFile, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { parseArgs } from 'node:util';

import { createBuildCommand, NapiCli } from '@napi-rs/cli';
import { format } from 'oxfmt';
Expand All @@ -29,25 +30,36 @@ import {
parseJsonSourceFileConfigFileContent,
readJsonConfigFile,
sys,
ModuleKind,
} from 'typescript';

const projectDir = dirname(fileURLToPath(import.meta.url));
const TEST_PACKAGE_NAME = '@voidzero-dev/vite-plus-test';
const CORE_PACKAGE_NAME = '@voidzero-dev/vite-plus-core';

const skipNative = process.argv.includes('--skip-native');
const skipTs = process.argv.includes('--skip-ts');
const {
values: { ['skip-native']: skipNative, ['skip-ts']: skipTs },
} = parseArgs({
options: {
['skip-native']: { type: 'boolean', default: false },
['skip-ts']: { type: 'boolean', default: false },
},
strict: false,
});

// Filter out custom flags before passing to NAPI CLI
const napiArgs = process.argv
.slice(2)
.filter((arg) => arg !== '--skip-native' && arg !== '--skip-ts');

if (!skipTs) {
await buildCli();
}
// Build native first - TypeScript may depend on the generated binding types
if (!skipNative) {
await buildNapiBinding();
}
if (!skipTs) {
await buildCli();
Comment thread
cursor[bot] marked this conversation as resolved.
await syncCorePackageExports();
await syncTestPackageExports();
}
Expand Down Expand Up @@ -106,11 +118,38 @@ async function buildCli() {
sys,
projectDir,
);

const options = {
...initialOptions,
noEmit: false,
outDir: join(projectDir, 'dist'),
};

const cjsHost = createCompilerHost({
...options,
module: ModuleKind.CommonJS,
});

const cjsProgram = createProgram({
rootNames: ['src/define-config.ts'],
options: {
...options,
module: ModuleKind.CommonJS,
},
host: cjsHost,
});

const { diagnostics: cjsDiagnostics } = cjsProgram.emit();

if (cjsDiagnostics.length > 0) {
console.error(formatDiagnostics(cjsDiagnostics, cjsHost));
process.exit(1);
}
await rename(
join(projectDir, 'dist/define-config.js'),
join(projectDir, 'dist/define-config.cjs'),
);

const host = createCompilerHost(options);

const program = createProgram({
Expand Down
122 changes: 122 additions & 0 deletions packages/cli/src/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,125 @@ test('should keep vitest exports stable', () => {
expect(defaultInclude).toBeDefined();
expect(defaultBrowserPort).toBeDefined();
});

test('should support lazy loading of plugins', async () => {
const config = await defineConfig({
lazy: () => Promise.resolve({ plugins: [{ name: 'test' }] }),
});
expect(config.plugins?.length).toBe(1);
});

test('should merge lazy plugins with existing plugins', async () => {
const config = await defineConfig({
plugins: [{ name: 'existing' }],
lazy: () => Promise.resolve({ plugins: [{ name: 'lazy' }] }),
});
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('lazy');
});

test('should handle lazy with empty plugins array', async () => {
const config = await defineConfig({
lazy: () => Promise.resolve({ plugins: [] }),
});
expect(config.plugins?.length).toBe(0);
});

test('should handle lazy returning undefined plugins', async () => {
const config = await defineConfig({
lazy: () => Promise.resolve({}),
});
expect(config.plugins?.length).toBe(0);
});

test('should handle Promise config with lazy', async () => {
const config = await defineConfig(
Promise.resolve({
lazy: () => Promise.resolve({ plugins: [{ name: 'lazy-from-promise' }] }),
}),
);
expect(config.plugins?.length).toBe(1);
expect((config.plugins?.[0] as { name: string })?.name).toBe('lazy-from-promise');
});

test('should handle Promise config with lazy and existing plugins', async () => {
const config = await defineConfig(
Promise.resolve({
plugins: [{ name: 'existing' }],
lazy: () => Promise.resolve({ plugins: [{ name: 'lazy' }] }),
}),
);
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('lazy');
});

test('should handle Promise config without lazy', async () => {
const config = await defineConfig(
Promise.resolve({
plugins: [{ name: 'no-lazy' }],
}),
);
expect(config.plugins?.length).toBe(1);
expect((config.plugins?.[0] as { name: string })?.name).toBe('no-lazy');
});

test('should handle function config with lazy', async () => {
const configFn = defineConfig(() => ({
lazy: () => Promise.resolve({ plugins: [{ name: 'lazy-from-fn' }] }),
}));
expect(typeof configFn).toBe('function');
const config = await configFn({ command: 'build', mode: 'production' });
expect(config.plugins?.length).toBe(1);
expect((config.plugins?.[0] as { name: string })?.name).toBe('lazy-from-fn');
});

test('should handle function config with lazy and existing plugins', async () => {
const configFn = defineConfig(() => ({
plugins: [{ name: 'existing' }],
lazy: () => Promise.resolve({ plugins: [{ name: 'lazy' }] }),
}));
const config = await configFn({ command: 'build', mode: 'production' });
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('lazy');
});

test('should handle function config without lazy', () => {
const configFn = defineConfig(() => ({
plugins: [{ name: 'no-lazy' }],
}));
const config = configFn({ command: 'build', mode: 'production' });
expect(config.plugins?.length).toBe(1);
expect((config.plugins?.[0] as { name: string })?.name).toBe('no-lazy');
});

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

test('should handle async function config with lazy and existing plugins', async () => {
const configFn = defineConfig(async () => ({
plugins: [{ name: 'existing' }],
lazy: () => Promise.resolve({ plugins: [{ name: 'lazy' }] }),
}));
const config = await configFn({ command: 'build', mode: 'production' });
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('lazy');
});

test('should handle async function config without lazy', async () => {
const configFn = defineConfig(async () => ({
plugins: [{ name: 'no-lazy' }],
}));
const config = await configFn({ command: 'build', mode: 'production' });
expect(config.plugins?.length).toBe(1);
expect((config.plugins?.[0] as { name: string })?.name).toBe('no-lazy');
});
70 changes: 70 additions & 0 deletions packages/cli/src/define-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {
defineConfig as viteDefineConfig,
type ConfigEnv,
} from '@voidzero-dev/vite-plus-test/config';

import type { UserConfig } from './index';

type ViteUserConfigFnObject = (env: ConfigEnv) => UserConfig;
type ViteUserConfigFnPromise = (env: ConfigEnv) => Promise<UserConfig>;
type ViteUserConfigFn = (env: ConfigEnv) => UserConfig | Promise<UserConfig>;
type ViteUserConfigExport =
| UserConfig
| Promise<UserConfig>
| ViteUserConfigFnObject
| ViteUserConfigFnPromise
| ViteUserConfigFn;

export function defineConfig(config: UserConfig): UserConfig;
export function defineConfig(config: Promise<UserConfig>): Promise<UserConfig>;
export function defineConfig(config: ViteUserConfigFnObject): ViteUserConfigFnObject;
export function defineConfig(config: ViteUserConfigFnPromise): ViteUserConfigFnPromise;
export function defineConfig(config: ViteUserConfigExport): ViteUserConfigExport;

export function defineConfig(config: ViteUserConfigExport): ViteUserConfigExport {
if (typeof config === 'object') {
if (config instanceof Promise) {
return config.then((config) => {
if (config.lazy) {
return config.lazy().then(({ plugins }) =>
viteDefineConfig({
...config,
plugins: [...(config.plugins || []), ...(plugins || [])],
}),
);
}
return viteDefineConfig(config);
});
} else if (config.lazy) {
return config.lazy().then(({ plugins }) =>
viteDefineConfig({
...config,
plugins: [...(config.plugins || []), ...(plugins || [])],
}),
);
Comment thread
Brooooooklyn marked this conversation as resolved.
}
} else if (typeof config === 'function') {
return viteDefineConfig((env) => {
const c = config(env);
if (c instanceof Promise) {
return c.then((v) => {
if (v.lazy) {
return v
.lazy()
.then(({ plugins }) =>
viteDefineConfig({ ...v, plugins: [...(v.plugins || []), ...(plugins || [])] }),
);
}
return v;
});
}
if (c.lazy) {
return c
.lazy()
.then(({ plugins }) => ({ ...c, plugins: [...(c.plugins || []), ...(plugins || [])] }));
Comment thread
Brooooooklyn marked this conversation as resolved.
}
return c;
});
}
return viteDefineConfig(config);
}
3 changes: 3 additions & 0 deletions packages/cli/src/index.cts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ const vite = require('@voidzero-dev/vite-plus-core');

const vitest = require('@voidzero-dev/vite-plus-test/config');

const { defineConfig } = require('./define-config');
Comment thread
Brooooooklyn marked this conversation as resolved.

module.exports = {
...vite,
...vitest,
defineConfig,
};
9 changes: 8 additions & 1 deletion packages/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defineConfig } from '@voidzero-dev/vite-plus-test/config';
import { type Plugin as VitestPlugin } from '@voidzero-dev/vite-plus-test/config';

import { defineConfig } from './define-config.js';
import type { LibUserConfig } from './lib';
import type { FormatOptions } from './oxfmt-config';
import type { OxlintConfig } from './oxlint-config';
Expand All @@ -17,6 +18,12 @@ declare module '@voidzero-dev/vite-plus-core' {
lib?: LibUserConfig | LibUserConfig[];

tasks?: Tasks;

// temporary solution to load plugins lazily
// We need to support this in the upstream vite
lazy?: () => Promise<{
plugins?: VitestPlugin[];
}>;
Comment thread
Brooooooklyn marked this conversation as resolved.
}
}

Expand Down
Loading
Loading