Skip to content

Commit a58741f

Browse files
kazuponclaude
andauthored
fix(cli): warn when object-form manualChunks is detected (#1046)
resolve #900 --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4f22251 commit a58741f

File tree

10 files changed

+115
-2
lines changed

10 files changed

+115
-2
lines changed

packages/cli/snap-tests-global/migration-merge-vite-config-ts/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
},
1818
"devDependencies": {
1919
"@vitejs/plugin-react": "^4.2.0",
20+
"@vitest/browser-playwright": "^4.0.0",
2021
"oxfmt": "1",
2122
"oxlint": "1",
2223
"vite": "^7.0.0",

packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"dev": "vite"
66
},
77
"devDependencies": {
8+
"@vitejs/plugin-react": "catalog:",
89
"vite": "catalog:"
910
},
1011
"packageManager": "pnpm@10.20.0+sha512.cf9998222162dd85864d0a8102e7892e7ba4ceadebbf5a31f9c2fce48dfce317a9c53b9f6464d1ef9042cba2e02ae02a9f7c143a2b438cd93c91840f0192b9dd",

packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default defineConfig({
2626
"prepare": "vp config"
2727
},
2828
"devDependencies": {
29+
"@vitejs/plugin-react": "catalog:",
2930
"vite": "catalog:",
3031
"vite-plus": "catalog:"
3132
},

packages/cli/snap-tests-global/migration-monorepo-pnpm/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"testnpm2": "1.0.0"
1717
},
1818
"devDependencies": {
19+
"@vitejs/plugin-react": "catalog:",
1920
"oxfmt": "catalog:",
2021
"oxlint": "catalog:",
2122
"vite": "catalog:",

packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ cat: .oxfmtrc.json: No such file or directory
6262
"testnpm2": "1.0.0"
6363
},
6464
"devDependencies": {
65+
"@vitejs/plugin-react": "catalog:",
6566
"vite": "catalog:",
6667
"vitest": "catalog:",
6768
"vite-plus": "catalog:"

packages/cli/snap-tests-global/migration-monorepo-yarn4/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"testnpm2": "1.0.0"
2020
},
2121
"devDependencies": {
22+
"@vitejs/plugin-react": "catalog:",
2223
"oxfmt": "catalog:",
2324
"oxlint": "catalog:",
2425
"vite": "catalog:",

packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ cat: .oxlintrc.json: No such file or directory
5353
"testnpm2": "1.0.0"
5454
},
5555
"devDependencies": {
56+
"@vitejs/plugin-react": "catalog:",
5657
"vite": "catalog:",
5758
"vitest": "catalog:",
5859
"vite-plus": "catalog:"
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { checkManualChunksCompat } from '../compat.js';
4+
import { createMigrationReport } from '../report.js';
5+
6+
describe('checkManualChunksCompat', () => {
7+
it('should warn when manualChunks is an object', () => {
8+
const report = createMigrationReport();
9+
checkManualChunksCompat({ manualChunks: { react: ['react', 'react-dom'] } }, report);
10+
expect(report.warnings).toHaveLength(1);
11+
expect(report.warnings[0]).toContain('Object-form');
12+
expect(report.warnings[0]).toContain('codeSplitting');
13+
});
14+
15+
it('should not warn when manualChunks is a function', () => {
16+
const report = createMigrationReport();
17+
checkManualChunksCompat({ manualChunks: () => undefined }, report);
18+
expect(report.warnings).toHaveLength(0);
19+
});
20+
21+
it('should not warn when manualChunks is not set', () => {
22+
const report = createMigrationReport();
23+
checkManualChunksCompat({}, report);
24+
expect(report.warnings).toHaveLength(0);
25+
});
26+
27+
it('should not warn when output is undefined', () => {
28+
const report = createMigrationReport();
29+
checkManualChunksCompat(undefined, report);
30+
expect(report.warnings).toHaveLength(0);
31+
});
32+
33+
it('should handle array of outputs', () => {
34+
const report = createMigrationReport();
35+
checkManualChunksCompat(
36+
[{ manualChunks: () => undefined }, { manualChunks: { vendor: ['lodash'] } }],
37+
report,
38+
);
39+
expect(report.warnings).toHaveLength(1);
40+
});
41+
42+
it('should only add one warning for multiple object-form outputs', () => {
43+
const report = createMigrationReport();
44+
checkManualChunksCompat(
45+
[{ manualChunks: { react: ['react'] } }, { manualChunks: { lodash: ['lodash'] } }],
46+
report,
47+
);
48+
expect(report.warnings).toHaveLength(1);
49+
});
50+
});

packages/cli/src/migration/bin.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,22 @@ function showMigrationSummary(options: {
540540
}
541541
}
542542

543+
async function checkRolldownCompatibility(rootDir: string, report: MigrationReport): Promise<void> {
544+
try {
545+
const { resolveConfig } = await import('../index.js');
546+
const { checkManualChunksCompat } = await import('./compat.js');
547+
// Use 'runner' configLoader to avoid Rolldown bundling the config file,
548+
// which prints UNRESOLVED_IMPORT warnings that cannot be suppressed via logLevel.
549+
const config = await resolveConfig(
550+
{ root: rootDir, logLevel: 'silent', configLoader: 'runner' },
551+
'build',
552+
);
553+
checkManualChunksCompat(config.build?.rollupOptions?.output, report);
554+
} catch {
555+
// Config resolution may fail — skip compatibility check silently
556+
}
557+
}
558+
543559
async function executeMigrationPlan(
544560
workspaceInfoOptional: WorkspaceInfoOptional,
545561
plan: MigrationPlan,
@@ -637,7 +653,16 @@ async function executeMigrationPlan(
637653
cancelAndExit('Vite+ cannot automatically migrate this project yet.', 1);
638654
}
639655

640-
// 5. ESLint → Oxlint migration (before main rewrite so .oxlintrc.json gets picked up)
656+
// 5. Check for Rolldown-incompatible config patterns (root + workspace packages)
657+
updateMigrationProgress('Checking config compatibility');
658+
await checkRolldownCompatibility(workspaceInfo.rootDir, report);
659+
if (workspaceInfo.packages) {
660+
for (const pkg of workspaceInfo.packages) {
661+
await checkRolldownCompatibility(path.join(workspaceInfo.rootDir, pkg.path), report);
662+
}
663+
}
664+
665+
// 6. ESLint → Oxlint migration (before main rewrite so .oxlintrc.json gets picked up)
641666
if (plan.migrateEslint) {
642667
updateMigrationProgress('Migrating ESLint');
643668
const eslintOk = await migrateEslintToOxlint(
@@ -725,6 +750,7 @@ async function executeMigrationPlan(
725750
installArgs,
726751
{ silent: true },
727752
);
753+
728754
clearMigrationProgress();
729755
return {
730756
installDurationMs: initialInstallSummary.durationMs + finalInstallSummary.durationMs,
@@ -825,7 +851,18 @@ async function main() {
825851
}
826852
}
827853

828-
if (didMigrate) {
854+
// Check for Rolldown-incompatible config patterns (root + workspace packages)
855+
await checkRolldownCompatibility(workspaceInfoOptional.rootDir, report);
856+
if (workspaceInfoOptional.packages) {
857+
for (const pkg of workspaceInfoOptional.packages) {
858+
await checkRolldownCompatibility(
859+
path.join(workspaceInfoOptional.rootDir, pkg.path),
860+
report,
861+
);
862+
}
863+
}
864+
865+
if (didMigrate || report.warnings.length > 0) {
829866
clearMigrationProgress();
830867
showMigrationSummary({
831868
projectRoot: workspaceInfoOptional.rootDir,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { addMigrationWarning, type MigrationReport } from './report.js';
2+
3+
/**
4+
* Check for Rolldown-incompatible manualChunks config patterns.
5+
*/
6+
export function checkManualChunksCompat(output: unknown, report: MigrationReport): void {
7+
const outputs = Array.isArray(output) ? output : output ? [output] : [];
8+
for (const out of outputs) {
9+
if (out.manualChunks != null && typeof out.manualChunks !== 'function') {
10+
addMigrationWarning(
11+
report,
12+
'Object-form `build.rollupOptions.output.manualChunks` is not supported by Rolldown. ' +
13+
'Convert it to function form or use `build.rolldownOptions.output.codeSplitting`. ' +
14+
'See: https://rolldown.rs/options/output#manualchunks and https://rolldown.rs/in-depth/manual-code-splitting',
15+
);
16+
break;
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)