diff --git a/packages/cli/snap-tests-global/migration-merge-vite-config-ts/package.json b/packages/cli/snap-tests-global/migration-merge-vite-config-ts/package.json index 27bf607d9a..0e2c2600a0 100644 --- a/packages/cli/snap-tests-global/migration-merge-vite-config-ts/package.json +++ b/packages/cli/snap-tests-global/migration-merge-vite-config-ts/package.json @@ -17,6 +17,7 @@ }, "devDependencies": { "@vitejs/plugin-react": "^4.2.0", + "@vitest/browser-playwright": "^4.0.0", "oxfmt": "1", "oxlint": "1", "vite": "^7.0.0", diff --git a/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/package.json b/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/package.json index b27370addb..4d039e5bcb 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/package.json +++ b/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/package.json @@ -5,6 +5,7 @@ "dev": "vite" }, "devDependencies": { + "@vitejs/plugin-react": "catalog:", "vite": "catalog:" }, "packageManager": "pnpm@10.20.0+sha512.cf9998222162dd85864d0a8102e7892e7ba4ceadebbf5a31f9c2fce48dfce317a9c53b9f6464d1ef9042cba2e02ae02a9f7c143a2b438cd93c91840f0192b9dd", diff --git a/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt index 0e18946ce1..c39b26978d 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt +++ b/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt @@ -26,6 +26,7 @@ export default defineConfig({ "prepare": "vp config" }, "devDependencies": { + "@vitejs/plugin-react": "catalog:", "vite": "catalog:", "vite-plus": "catalog:" }, diff --git a/packages/cli/snap-tests-global/migration-monorepo-pnpm/package.json b/packages/cli/snap-tests-global/migration-monorepo-pnpm/package.json index ced1439a6b..4e9329db9a 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-pnpm/package.json +++ b/packages/cli/snap-tests-global/migration-monorepo-pnpm/package.json @@ -16,6 +16,7 @@ "testnpm2": "1.0.0" }, "devDependencies": { + "@vitejs/plugin-react": "catalog:", "oxfmt": "catalog:", "oxlint": "catalog:", "vite": "catalog:", diff --git a/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt index 0a766de088..0db0b7ca6f 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt +++ b/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt @@ -62,6 +62,7 @@ cat: .oxfmtrc.json: No such file or directory "testnpm2": "1.0.0" }, "devDependencies": { + "@vitejs/plugin-react": "catalog:", "vite": "catalog:", "vitest": "catalog:", "vite-plus": "catalog:" diff --git a/packages/cli/snap-tests-global/migration-monorepo-yarn4/package.json b/packages/cli/snap-tests-global/migration-monorepo-yarn4/package.json index ce7788c288..5f4a012282 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-yarn4/package.json +++ b/packages/cli/snap-tests-global/migration-monorepo-yarn4/package.json @@ -19,6 +19,7 @@ "testnpm2": "1.0.0" }, "devDependencies": { + "@vitejs/plugin-react": "catalog:", "oxfmt": "catalog:", "oxlint": "catalog:", "vite": "catalog:", diff --git a/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt index c8a65fb0ef..9b922b4188 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt +++ b/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt @@ -53,6 +53,7 @@ cat: .oxlintrc.json: No such file or directory "testnpm2": "1.0.0" }, "devDependencies": { + "@vitejs/plugin-react": "catalog:", "vite": "catalog:", "vitest": "catalog:", "vite-plus": "catalog:" diff --git a/packages/cli/src/migration/__tests__/compat.spec.ts b/packages/cli/src/migration/__tests__/compat.spec.ts new file mode 100644 index 0000000000..f0035a28e9 --- /dev/null +++ b/packages/cli/src/migration/__tests__/compat.spec.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest'; + +import { checkManualChunksCompat } from '../compat.js'; +import { createMigrationReport } from '../report.js'; + +describe('checkManualChunksCompat', () => { + it('should warn when manualChunks is an object', () => { + const report = createMigrationReport(); + checkManualChunksCompat({ manualChunks: { react: ['react', 'react-dom'] } }, report); + expect(report.warnings).toHaveLength(1); + expect(report.warnings[0]).toContain('Object-form'); + expect(report.warnings[0]).toContain('codeSplitting'); + }); + + it('should not warn when manualChunks is a function', () => { + const report = createMigrationReport(); + checkManualChunksCompat({ manualChunks: () => undefined }, report); + expect(report.warnings).toHaveLength(0); + }); + + it('should not warn when manualChunks is not set', () => { + const report = createMigrationReport(); + checkManualChunksCompat({}, report); + expect(report.warnings).toHaveLength(0); + }); + + it('should not warn when output is undefined', () => { + const report = createMigrationReport(); + checkManualChunksCompat(undefined, report); + expect(report.warnings).toHaveLength(0); + }); + + it('should handle array of outputs', () => { + const report = createMigrationReport(); + checkManualChunksCompat( + [{ manualChunks: () => undefined }, { manualChunks: { vendor: ['lodash'] } }], + report, + ); + expect(report.warnings).toHaveLength(1); + }); + + it('should only add one warning for multiple object-form outputs', () => { + const report = createMigrationReport(); + checkManualChunksCompat( + [{ manualChunks: { react: ['react'] } }, { manualChunks: { lodash: ['lodash'] } }], + report, + ); + expect(report.warnings).toHaveLength(1); + }); +}); diff --git a/packages/cli/src/migration/bin.ts b/packages/cli/src/migration/bin.ts index 24242ccd5e..a20400f774 100644 --- a/packages/cli/src/migration/bin.ts +++ b/packages/cli/src/migration/bin.ts @@ -540,6 +540,22 @@ function showMigrationSummary(options: { } } +async function checkRolldownCompatibility(rootDir: string, report: MigrationReport): Promise { + try { + const { resolveConfig } = await import('../index.js'); + const { checkManualChunksCompat } = await import('./compat.js'); + // Use 'runner' configLoader to avoid Rolldown bundling the config file, + // which prints UNRESOLVED_IMPORT warnings that cannot be suppressed via logLevel. + const config = await resolveConfig( + { root: rootDir, logLevel: 'silent', configLoader: 'runner' }, + 'build', + ); + checkManualChunksCompat(config.build?.rollupOptions?.output, report); + } catch { + // Config resolution may fail — skip compatibility check silently + } +} + async function executeMigrationPlan( workspaceInfoOptional: WorkspaceInfoOptional, plan: MigrationPlan, @@ -637,7 +653,16 @@ async function executeMigrationPlan( cancelAndExit('Vite+ cannot automatically migrate this project yet.', 1); } - // 5. ESLint → Oxlint migration (before main rewrite so .oxlintrc.json gets picked up) + // 5. Check for Rolldown-incompatible config patterns (root + workspace packages) + updateMigrationProgress('Checking config compatibility'); + await checkRolldownCompatibility(workspaceInfo.rootDir, report); + if (workspaceInfo.packages) { + for (const pkg of workspaceInfo.packages) { + await checkRolldownCompatibility(path.join(workspaceInfo.rootDir, pkg.path), report); + } + } + + // 6. ESLint → Oxlint migration (before main rewrite so .oxlintrc.json gets picked up) if (plan.migrateEslint) { updateMigrationProgress('Migrating ESLint'); const eslintOk = await migrateEslintToOxlint( @@ -725,6 +750,7 @@ async function executeMigrationPlan( installArgs, { silent: true }, ); + clearMigrationProgress(); return { installDurationMs: initialInstallSummary.durationMs + finalInstallSummary.durationMs, @@ -825,7 +851,18 @@ async function main() { } } - if (didMigrate) { + // Check for Rolldown-incompatible config patterns (root + workspace packages) + await checkRolldownCompatibility(workspaceInfoOptional.rootDir, report); + if (workspaceInfoOptional.packages) { + for (const pkg of workspaceInfoOptional.packages) { + await checkRolldownCompatibility( + path.join(workspaceInfoOptional.rootDir, pkg.path), + report, + ); + } + } + + if (didMigrate || report.warnings.length > 0) { clearMigrationProgress(); showMigrationSummary({ projectRoot: workspaceInfoOptional.rootDir, diff --git a/packages/cli/src/migration/compat.ts b/packages/cli/src/migration/compat.ts new file mode 100644 index 0000000000..aac9771e3b --- /dev/null +++ b/packages/cli/src/migration/compat.ts @@ -0,0 +1,19 @@ +import { addMigrationWarning, type MigrationReport } from './report.js'; + +/** + * Check for Rolldown-incompatible manualChunks config patterns. + */ +export function checkManualChunksCompat(output: unknown, report: MigrationReport): void { + const outputs = Array.isArray(output) ? output : output ? [output] : []; + for (const out of outputs) { + if (out.manualChunks != null && typeof out.manualChunks !== 'function') { + addMigrationWarning( + report, + 'Object-form `build.rollupOptions.output.manualChunks` is not supported by Rolldown. ' + + 'Convert it to function form or use `build.rolldownOptions.output.codeSplitting`. ' + + 'See: https://rolldown.rs/options/output#manualchunks and https://rolldown.rs/in-depth/manual-code-splitting', + ); + break; + } + } +}