Repository navigation
fix(migrate): warn on esbuild-specific config that fails under Vite 8 - #3178
john-okeefe wants to merge 9 commits into
Conversation
|
Could we extend this into a targeted config migration? For a static #2004 scopes import rewrites to symbols that I’d use the existing AST analysis to identify the config property and preserve comments. The regex can match comments or unrelated plugin options, and it misses quoted property names. For dynamic configs, esbuild-specific options, or We should also adjust the warning: Vite 8 still supports |
Vite 8 no longer bundles esbuild, so a project migrated from Vite <= 7 with build.minify: 'esbuild' fails its next vp build on the deprecated transformWithEsbuild path (esbuild is no longer a vite dependency), and direct transformWithEsbuild usage has no vite-plus equivalent. The error names neither the config file nor the option, and migrate gives no heads-up. Add warnEsbuildConfigInViteConfig and call it wherever migrate finalizes a project's vite config (standalone, monorepo root + packages, per-project rewrite, and the existing-vite+ finalize pass). It emits one migration warning per detected pattern, pointing at the oxc replacements (minify: true, transformWithOxc). Like the rest of migrate it does not rewrite config semantics. Closes voidzero-dev#3176
Review feedback on voidzero-dev#3178: extend the diagnostic into a targeted config migration, use AST analysis instead of a regex, and acknowledge that Vite 8 still supports esbuild when it is installed separately. - build.minify: 'esbuild' inside a default-exported config is rewritten in place to minify: true (the oxc minifier) via span replacement: comments and unrelated plugin options are untouched, quoted keys ('minify') are handled, and the switch is reported through the success log plus a new esbuildMinifyMigrationCount summary counter. - Dynamic minify values, minify options outside the build object, and transformWithEsbuild usage keep their code and get a diagnostic that presents both options: install esbuild to keep the behavior, or migrate to oxc. - Detection walks the serialized oxc AST (analyzeMigrationSource) and covers defineConfig() calls, env-config arrays, TS wrappers, and quoted keys; raw-text scanning is now only a diagnostic fallback and skips comment ranges. - Warning wording no longer implies esbuild support is gone, and the rewrite prefers minify: true over removing the property (removal can disable minification for SSR builds).
e038243 to
f9be853
Compare
|
Thanks @fengmk2 — all four points are addressed in f9be853:
Tests cover the rewrite (incl. comment preservation), quoted keys, unrelated options, dynamic values, |
Audit pass over the two esbuild-migration commits: - finalizeCoreMigrationForExistingVitePlus tracks every migration's outcome in CoreMigrationFinalizationResult; the esbuild migration now does too (esbuildMinifyConfig flag), and the deep-equal result expectations in migrator.spec.ts are updated accordingly. - Helper placement: this file declares helpers before their consumers; the oxc AST helpers sat after migrateEsbuildConfigInViteConfig — moved above it. - Dropped the duplicate SpanLike interface (OxcSpan already describes the same shape). - Condensed the migrateEsbuildConfigInViteConfig JSDoc to the length used by sibling migrations.
|
Follow-up (7d41849): the migration is now also tracked in |
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7d418492dc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
finalizeCoreMigrationForExistingVitePlus records the build.minify 'esbuild' -> true switch in esbuildMinifyConfig, but the didMigrate predicate never read it. On an otherwise up-to-date existing Vite+ project the static rewrite emits no warnings, so the 'already using Vite+' fast exit swallowed the change: the summary, the formatting pass, and any mention of the switch were skipped even though vite.config was rewritten on disk. Include the flag in the predicate so the rewrite surfaces like every other config update. Covered by a new CLI snapshot fixture that reintroduces minify: 'esbuild' on a converged project (fails without the fix) and a unit test asserting the flag and its convergence.
…he esbuild config check
Two static-analysis gaps in migrateEsbuildConfigInViteConfig:
- staticConfigObjects dropped function bodies, so a production-only
esbuild selection inside a callback — defineConfig(({ mode }) => ({
build: { minify: mode === 'legacy' ? 'esbuild' : true } })
— produced neither a rewrite nor a warning, and the Vite 8 build
failed silently. Config factories now expose the objects built by
their direct returns; expression bodies directly, block bodies via
top-level return statements. Returns nested in conditionals stay
with the raw-occurrence fallback diagnostics.
- propertyName matched computed identifier keys, so
const build = 'plugin' + export default { [build]: { minify:
'esbuild' } } was misread as Vite's build option and the unrelated
string was rewritten to true. Computed identifiers no longer match;
computed string literals (['build']) still do, since they are the
same key.
The raw-occurrence diagnostic still covers everything the static walk
does not account for, so both gaps now warn instead of staying
silent.
|
All three review findings are addressed:
Validation: 377/377 unit tests, |
|
To use Codex here, create a Codex account and connect to github. |
|
@fengmk2 Just wondering if you could check on this again. Thanks! |
| if (value.type === 'Literal' && value.value === 'esbuild') { | ||
| minifyValueSpans.push({ start: value.start, end: value.end }); |
There was a problem hiding this comment.
[P1] Preserve esbuild-specific behavior before switching minifiers
The rewrite checks the minify literal without checking the surrounding esbuild options. A project that explicitly uses esbuild features is therefore switched to Oxc while those features silently stop working, even when esbuild is installed:
export default {
esbuild: { drop: ['console'], mangleProps: /_$/ },
build: { minify: 'esbuild' },
};The current helper changes this to minify: true, records one migration, and emits no warning. A real Vite 8.3.5 build confirms the effect: with 'esbuild', the output removes console.log and renames field_; changing only the minifier to true retains both the console call and field_. The Vite migration guide requires a separate migration for drop and states that Oxc does not support property mangling.
Only apply the automatic switch when the relevant esbuild behavior can be preserved. If options are unsupported or cannot be resolved safely, retain the original minifier and issue the manual-migration diagnostic.
| case 'CallExpression': | ||
| case 'NewExpression': | ||
| yield* staticConfigObjects(n.arguments); |
There was a problem hiding this comment.
[P2] Restrict automatic edits to helpers with known config semantics
staticConfigObjects treats arguments of every function call and constructor as Vite config objects. An unknown helper can use its argument before returning a config, so changing that argument from a string to a boolean can break config evaluation:
function createConfig(options) {
if (options.build.minify.toLowerCase() !== 'esbuild') {
throw new Error('unexpected minifier');
}
return options;
}
export default createConfig({ build: { minify: 'esbuild' } });This evaluates successfully before migration. After migration it throws TypeError: options.build.minify.toLowerCase is not a function, with no migration warning. Resolve the callee binding and limit automatic edits to recognized config helpers with compatible semantics. Preserve arguments to unknown functions and constructors and diagnose the esbuild setting instead.
| const rawOccurrences = [...source.matchAll(/\bminify\s*:\s*(['"])esbuild\1/g)].filter( | ||
| (match) => | ||
| !comments.some((comment) => match.index >= comment.start && match.index < comment.end), | ||
| ).length; | ||
| const hasUnaccountedMinify = rawOccurrences > minifyValueSpans.length; |
There was a problem hiding this comment.
[P2] Identify unhandled esbuild settings by AST location, not match counts
The fallback compares the number of regex matches with the number of AST edits, but those sets do not contain the same occurrences. For example:
export default ({ mode }) => {
if (mode === 'legacy') {
return { build: { minify: 'esbuild' } };
}
return { build: { 'minify': 'esbuild' } };
};The direct return is rewritten, while the nested return remains unchanged. The regex matches only the nested return's bare key, while the edit count includes the direct return's quoted key. Both counts equal one, so migration emits no warning. A subsequent build in legacy mode still selects esbuild and fails if it is absent.
The regex also misses quoted keys and comments before the value in unsupported shapes, such as const base = { build: { 'minify': 'esbuild' } }; export default { ...base };. Use parsed property nodes for diagnostics and exclude only the exact value spans already rewritten. This covers unresolved shapes without changing their code.
| } else if (value.type !== 'Literal') { | ||
| // Dynamic (non-literal) values can't be migrated blind. | ||
| hasDynamicMinify = true; |
There was a problem hiding this comment.
[P2] Do not report boolean-only minify values as esbuild usage
Every non-literal value sets hasDynamicMinify, including values that cannot select esbuild:
export default {
build: { minify: process.env.MINIFY === 'true' },
};This expression only returns a boolean, but migration warns that minify: "esbuild" requires a separate installation and suggests minify: true. The same warning occurs for true as const and an identifier bound to true. Following the replacement advice would remove the conditional behavior. Each subsequent migration emits the warning again; on an existing Vite+ project it also prevents the normal no-op return because that condition requires no report warnings.
Require evidence that the value can select esbuild before issuing this esbuild-specific diagnostic. Recognize boolean expressions and unwrap TypeScript wrappers; distinguish unresolved values from known esbuild usage.
Problem
vp migraterewrites the imports vite-plus owns but leaves config semantics untouched (per the scoped-rewrite policy in #2004). One leftover is not a silent behavioral difference but a guaranteed build failure: Vite 8 no longer bundles esbuild, so a project migrating from Vite ≤ 7 withfails on its very next
vp buildwith:The error names neither
minifynor the config file, and nothing in the migration output flags it. Repro + full details in #3176.What this does (updated after review)
Per @fengmk2's review, this now performs a targeted config migration rather than only warning:
build.minify: 'esbuild'inside thebuildobject of a default-exported config is rewritten in place tominify: truevia span replacement over the serialized oxc AST (analyzeMigrationSource). Comments and unrelated plugin options are preserved, quoted keys ('minify') are handled, and the switch is reported through the success log plus a newesbuildMinifyMigrationCountin the migration summary. Rewriting (rather than removing the property) keeps minification enabled for SSR builds.esbuildseparately to keep esbuild behavior, or migrate to oxc — for: dynamic minify values,minifyoptions outside thebuildobject, andtransformWithEsbuildusage (which may need argument changes). Vite 8 still supports esbuild-specific config when esbuild is installed separately, so the diagnostics no longer imply support is gone.defineConfig({...}),defineConfig([{...}, ...])env-config arrays, TS wrappers (as/satisfies), and quoted keys. Raw-text scanning exists only to feed diagnostics for shapes the static walk can't see (spread configs, helper-built objects), and it skips comment ranges.Where it hooks in
Called everywhere migrate finalizes a project's vite config, mirroring
migrateTaskCacheConfigInViteConfig:rewriteStandaloneProjectrewriteMonorepo(workspace root + each package)rewriteMonorepoProjectfinalizeCoreMigrationForExistingVitePlus(already-on-vite+ projects running the finalize pass)Test plan
New tests in
migrator.spec.tscover: the static rewrite (including comment preservation), quoted keys, unrelatedminifyoptions left alone with a diagnostic, dynamic values diagnosed without rewriting,transformWithEsbuilddiagnostics, comment-only mentions ignored, oxc-compatible configs untouched, and the missing-config no-op.pnpm exec vitest run src/migration/__tests__/migrator.spec.ts— 370 passed (362 pre-existing + 8 new).Closes #3176