Skip to content

fix(migrate): warn on esbuild-specific config that fails under Vite 8 - #3178

Open
john-okeefe wants to merge 7 commits into
voidzero-dev:mainfrom
john-okeefe:fix/migrate-warn-esbuild-config
Open

john-okeefe wants to merge 7 commits into
voidzero-dev:mainfrom
john-okeefe:fix/migrate-warn-esbuild-config

Conversation

@john-okeefe

@john-okeefe john-okeefe commented Oct 21, 2026 •

Copy link
Copy Markdown

Problem

vp migrate rewrites 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 with

export default defineConfig({
  build: { minify: 'esbuild' },
});

fails on its very next vp build with:

[plugin vite:esbuild-transpile]
Error: Failed to load `transformWithEsbuild`. It is deprecated and it now requires esbuild to be installed separately. If you are a package author, please migrate to `transformWithOxc` instead.

The error names neither minify nor 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:

  • Static case rewritten: build.minify: 'esbuild' inside the build object of a default-exported config is rewritten in place to minify: true via 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 new esbuildMinifyMigrationCount in the migration summary. Rewriting (rather than removing the property) keeps minification enabled for SSR builds.
  • Everything else keeps its code and gets a diagnostic presenting both valid paths — install esbuild separately to keep esbuild behavior, or migrate to oxc — for: dynamic minify values, minify options outside the build object, and transformWithEsbuild usage (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.
  • AST-driven, regex only as a guarded fallback: detection walks the oxc AST, covering 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:

  • rewriteStandaloneProject
  • rewriteMonorepo (workspace root + each package)
  • rewriteMonorepoProject
  • finalizeCoreMigrationForExistingVitePlus (already-on-vite+ projects running the finalize pass)

Test plan

New tests in migrator.spec.ts cover: the static rewrite (including comment preservation), quoted keys, unrelated minify options left alone with a diagnostic, dynamic values diagnosed without rewriting, transformWithEsbuild diagnostics, 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

fengmk2 commented Oct 21, 2026

Copy link
Copy Markdown
Member

Could we extend this into a targeted config migration? For a static build.minify: 'esbuild' with no esbuild-specific behavior to preserve, we could change it to minify: true and report the switch to Oxc. This would complete the migration for the case in #3176.

#2004 scopes import rewrites to symbols that vite-plus owns; it does not prohibit config migrations. We already migrate task-cache and pack configuration.

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 transformWithEsbuild calls that need argument changes, we should preserve the code and provide a diagnostic.

We should also adjust the warning: Vite 8 still supports build.minify: 'esbuild' when users install esbuild. Users who need that behavior should have the option to retain it. For the Oxc migration, prefer minify: true over removing the property, since removal can disable minification for SSR builds.

John O'Keefe added 2 commits October 21, 2026 14:22
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).
@john-okeefe
john-okeefe force-pushed the fix/migrate-warn-esbuild-config branch from e038243 to f9be853 Compare October 21, 2026 18:37
@john-okeefe

Copy link
Copy Markdown
Author

Thanks @fengmk2 — all four points are addressed in f9be853:

  • Targeted migration: static build.minify: 'esbuild' inside a default-exported config is now rewritten in place to minify: true (span replacement over the serialized oxc AST from analyzeMigrationSource), and the switch is reported via the success log plus a new esbuildMinifyMigrationCount in the migration summary. Rewriting rather than removing, as you noted, keeps minification enabled for SSR builds.
  • AST-driven: detection walks the oxc AST, so comments are preserved (spans replace only the value node), unrelated plugin options are never touched, and quoted keys ('minify') resolve correctly. Raw-text scanning is now only a fallback for diagnostics and skips comment ranges.
  • Diagnostics for the non-static cases (dynamic values, minify outside build, transformWithEsbuild calls): code is preserved and the diagnostic presents both valid paths — install esbuild to keep the behavior, or migrate to oxc.
  • Wording adjusted: nothing implies esbuild support is gone under Vite 8.

Tests cover the rewrite (incl. comment preservation), quoted keys, unrelated options, dynamic values, transformWithEsbuild, comment-only mentions, and the no-op paths — 370 passed, and vp check on the touched module is clean.

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.
@john-okeefe

Copy link
Copy Markdown
Author

Follow-up (7d41849): the migration is now also tracked in CoreMigrationFinalizationResult (esbuildMinifyConfig), like the other finalize-path migrations, with the result-shape expectations in migrator.spec.ts updated to match. Style/convention pass only — nothing in the behavior described above changed.

@fengmk2

fengmk2 commented Oct 23, 2026

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-23T13:39:19.187236Z 7d41849 Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +195 to +196
result.esbuildMinifyConfig =
migrateEsbuildConfigInViteConfig(projectPath, silent, report) || result.esbuildMinifyConfig;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the esbuild result when marking a migration complete

When an already-on-Vite+ project has no other pending work and this is the only rewrite, this function returns esbuildMinifyConfig: true, but the didMigrate predicate in packages/cli/src/migration/bin.ts:1161-1168 checks only through taskCacheConfig. The early exit at bin.ts:1203-1222 then reports that the project is already using Vite+ and skips the migration summary/formatting even though vite.config was modified; include this new result flag in that predicate.

Useful? React with 👍 / 👎.

Comment on lines +958 to +959
default:
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Inspect esbuild values returned by config callbacks

When a supported config uses a callback such as defineConfig(({ mode }) => ({ build: { minify: mode === 'legacy' ? 'esbuild' : true } })), the call arm passes the ArrowFunctionExpression here and this default branch drops it. The fallback regex only matches a quote immediately after minify:, so this production-only esbuild selection emits neither a rewrite nor a warning and that Vite 8 build still fails; traverse direct callback return objects before falling back.

Useful? React with 👍 / 👎.

Comment on lines +918 to +920
if (key.type === 'Identifier' && typeof key.name === 'string') {
return key.name;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude computed identifiers from config-key matching

When a config has a computed key such as const build = 'plugin'; export default { [build]: { minify: 'esbuild' } }, ESTree still gives the key an Identifier named build, so this helper classifies it as Vite's literal build key and later rewrites the unrelated string to true. Check prop.computed before accepting identifier keys (while still allowing static string-literal computed keys) so migration does not alter non-Vite options.

Useful? React with 👍 / 👎.

John O'Keefe added 2 commits October 23, 2026 11:05
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.
@john-okeefe

Copy link
Copy Markdown
Author

All three review findings are addressed:

  • "Include the esbuild result when marking a migration complete" — coreMigrationResult.esbuildMinifyConfig is now part of the didMigrate predicate, so the minify switch reaches the summary and formatting pass instead of being swallowed by the "already using Vite+" fast exit. Regression-pinned by a new CLI snapshot fixture (migration_existing_vite_plus_esbuild_minify) that reintroduces minify: 'esbuild' on a converged project — verified it fails without the fix — plus a unit test asserting the flag and its convergence. (e7ed78e)

  • "Inspect esbuild values returned by config callbacks" — staticConfigObjects now unwraps config factories: expression bodies directly, block bodies via their direct return statements. Literal 'esbuild' values there are rewritten; dynamic selections (e.g. mode === 'legacy' ? 'esbuild' : true) now emit the diagnostic instead of staying silent. Returns nested in conditionals keep the raw-occurrence fallback behavior (diagnosed, never rewritten blind). Four new unit tests. (86d871f)

  • "Exclude computed identifiers from config-key matching" — propertyName no longer matches computed identifier keys, so [build]: { minify: 'esbuild' } with const build = 'plugin' is left untouched (and still diagnosed via the raw-occurrence fallback); computed string literals (['build']) still match, since they are the same key. Two new unit tests. (86d871f)

Validation: 377/377 unit tests, vp check clean, the snapshot fixture passes in compare mode, and it was confirmed to fail without the didMigrate fix.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

vp migrate doesn't flag esbuild-specific config (minify: "esbuild") — migrated project fails to build on Vite 8

2 participants