Skip to content

build: replace Create React App with Vite - #1616

Open
xantorres wants to merge 25 commits into
apache:devfrom
xantorres:build/vite-2-cutover
Open

xantorres wants to merge 25 commits into
apache:devfrom
xantorres:build/vite-2-cutover

Conversation

@xantorres

Copy link
Copy Markdown

Fixes #1580. Part of #1578. Step 2 of 3, atomic. Depends on #1582 (the Go asset contract, now merged into dev), which is why the server-side parsing does not appear in this diff.

Replaces react-scripts and react-app-rewired with Vite.

The configuration preserves the output contracts the server depends on: the build directory stays at ui/build, which ui/static.go embeds, and emitted assets stay under static/, which internal/router/ui.go serves as a route. Files keep the static/js, static/css and static/media grouping, which the analyze script matches on; the ignore rules for ui/build, previously pinned to that layout's directory depth, now ignore everything under build except the tracked favicon. Production sourcemaps stay on, and the REACT_APP_ prefix is retained so ui/scripts/env.js remains the single source of truth for configuration shared with the server. That same script's public_url value now also drives Vite's base configuration, and the manifest link in ui/index.html uses Vite's %BASE_URL% macro instead of a hardcoded path, so a non-root deployment keeps every asset and manifest reference prefixed the way it did under the previous toolchain's PUBLIC_URL wiring.

Agentic tooling did the mechanical work in this series; every change was reviewed by a human before being committed.

The one server-side line

header.html re-emitted the paths GetStyle() finds as classic scripts. An ES module loaded through a classic script tag fails on its first import, so the tag is now declared as a module and carries crossorigin, matching the tag Vite's own build emits for the client-rendered entry point. Rewriting the built index.html instead was not an option, because internal/router/ui.go serves that same file to boot the SPA and it cannot misdeclare its own script type. A type=module script fetches in CORS mode, so any CDN origin serving these files needs to send the matching CORS headers, with or without crossorigin present; default same-origin deployments are unaffected. The note for CDN users is in the CDN plugin READMEs, apache/answer-plugins#326, per the placement decided on #1567, and the release notes for the version that ships this should carry a short pointer to it.

Route code splitting

Routes loaded pages with lazy(() => import(`@/pages/${pagePath}`)). That shape cannot be statically analyzed, so no page received its own chunk and the specifier reached the browser untransformed, leaving every lazily routed page unable to load. Pages are now enumerated with a bounded glob covering the three directory depths routes actually use, excluding component subtrees so their own index files do not become route chunks. A page path with no matching module now rejects with the requested path and the list of known keys, surfacing through the existing route error boundary.

Behaviour changes, called out deliberately

The custom stylesheet link now derives its href from the build's own base, not a separately computed value. PageTags built the /custom.css link from process.env.PUBLIC_URL, which the previous toolchain exposed with its trailing slash already stripped; at the default configuration that value was an empty string, resolving to /custom.css. import.meta.env.BASE_URL, the direct equivalent under the new toolchain, keeps the trailing slash, so substituting it in the same place would resolve the same default configuration to //custom.css instead. The trailing slash is stripped explicitly before the substitution, reproducing the previous output at the default configuration and staying correct away from it. The output here is unchanged; only the mechanism it depends on is.

Two routes were dead ends and now fail loudly instead of silently. pages/403 and pages/Admin/UserOverview both referenced modules that did not exist in the tree, and both failed the same way under the previous toolchain, silently rendering blank. With the glob above, both now report the missing path through the route error boundary. Step 3 repoints pages/403 at pages/404/403, an existing component, so that route renders; pages/Admin/UserOverview stays a visible gap because creating the missing page is a content decision. Neither is a regression.

The markdown editor now renders its themed background. src/components/Editor/index.scss read var(-bs-body-bg) with a single leading dash. That is not a valid custom property reference, so the declaration was discarded and the editor never received the background it asks for. The previous CSS minifier accepted the invalid value; the current one rejects it outright and fails the build ([lightningcss minify] Unexpected token Ident("-bs-body-bg")), which is how it surfaced and why the one-line fix is part of this PR rather than a follow-up. Fixing it changes rendering.

Dependency removals

react-scripts, react-app-rewired, customize-cra and config-overrides.js are gone, and yaml-loader is replaced by the equivalent Vite plugin, pinned to the schema the previous loader used so bare dates and merge keys keep parsing the same way they did before. The scaffold test the old toolchain generated (App.test.tsx) and its jest packages go with it; this project has no test runner and no unit tests, before or after.

Three removed packages were already inert before this migration. Both purgecss packages were declared but wired nowhere: no postcss config exists, the overrides file never referenced them, and no script invoked them. buffer was aliased and provided as a global, but no application source uses it, and the one dependency requiring it declares buffer: false in its own browser field.

sass and @types/node are raised to the versions the toolchain requires; the previous sass predates the async compiler API it now calls. sass is pinned below the release that begins deprecating @import, which this project uses across 30 files. Migrating those to @use is a separate concern. Bootstrap's own Sass internals print dozens of dependency deprecation warnings on every build, unrelated to anything in this project's own styles; vite.config.mts sets css.preprocessorOptions.scss.quietDeps: true to silence those specifically while still surfacing warnings from this project's own stylesheets. One such app-own warning remains in this PR's build output, a mixed-declarations notice from Comment/index.scss; the one-line reorder that clears it is a step 3 nit.

The eslint config no longer extends react-app/jest, which shipped inside react-scripts and configured rules for a test suite this project does not have.

A failure found only by running the built application

With the build green and the dev server working, the built application did not boot. React never mounted, the page showed its loading spinner indefinitely, and the browser console was empty.

i18next attaches its resource-store methods to the instance inside init(). The builtin plugins register their translations while their modules are being evaluated. Whether that happens before or after init depends on how the bundler groups and orders chunks, so the previously working order was incidental rather than guaranteed. When it inverts, the registration throws while the entry module is still evaluating, which takes the application down before it mounts and produces no console output.

Registration now happens immediately only when there is an initialised instance to register into, and otherwise falls to the initialized handler the code already installed, which is correct in either order. The check that guards both orders is part of step 3.

Parity fixes found in review

Bootstrap icon fonts. The bootstrap-icons stylesheet points at font files under a path the bundler could not resolve on the first migration pass, so the build silently emitted zero font files and every icon rendered as a missing-glyph box. The stylesheet now overrides the package's font-directory variable to a path Vite can resolve; the fonts are emitted, and the boot test below confirms they are served.

Type checking. pnpm build now also runs tsc --noEmit before the bundler runs, and is clean today. The previous toolchain ran its checker in the dev server (blocking) and during builds (downgraded to warnings by this project's TSC_COMPILE_ON_ERROR setting); none of that carried over when the bundler changed, so until this fix a type error shipped with no signal at all.

Yaml parsing. The Vite yaml plugin defaults to js-yaml's more permissive schema, which resolves bare dates to JS Date objects and enables merge keys; the previous loader's schema kept both as plain strings. The plugin is now pinned to that same schema.

Declared Node range. The bundler's declared support range is ^20.19.0 || >=22.12.0. ui/package.json's own engines.node allowed >=20, which admits versions below that floor, and now matches it exactly. The release workflow's pinned Node, which also sat below the floor, is raised to 20.19.0 to match. That version bump is the only change this PR makes anywhere under .github/; it does not add a job.

Typed environment variables. import.meta.env.REACT_APP_* previously typed as any, since no ImportMetaEnv augmentation existed; a typo'd key would compile clean and only surface as a missing value at runtime. The two keys the app reads this way, REACT_APP_API_URL and REACT_APP_BASE_URL, are now declared, with strictImportMetaEnv enabled so an undeclared key is a type error instead of a silent any.

Verification

On dev at ace02c49, which includes step 1, plus these commits: pnpm install --frozen-lockfile under the pinned pnpm@9.7.0 and pnpm build (which now includes tsc --noEmit) complete; the build prints three warnings, each once: front-matter, a dependency unrelated to this project's own pinned js-yaml@^4.1.0, pulls in a legacy js-yaml@3.x copy whose buffer import gets externalized for browser compatibility; the Sass mixed-declarations notice from Comment/index.scss mentioned above; and one chunk over the default size threshold, see the note on chunking below. go build ./... and go vet ./... pass; TestGetStyleResolvesBuiltAssets passes against the Vite output; a search for react-scripts, react-app-rewired, customize-cra and config-overrides finds nothing outside the lockfile. The built binary was booted against sqlite3 and loaded in a browser: / renders with the client mounted (React's fiber container present on the root element), the module entry script and both entry stylesheets are fetched, and the console shows no errors. The same holds for /tags. The server-rendered / response itself carries the module entry script and both entry stylesheets, which is GetStyle() from step 1 finding them in the Vite output and header.html re-emitting them.

The three make check-ui guards from #1567 (asset paths, non-default locale, plugin i18n order) are not in this PR; they arrive as step 3 and were run green against the equivalent tree there.

Measurements

Captured for #1567 at its head d06b623, against main at 3b9f137, same machine, same Node and package manager versions, clean tree and clean install on both sides, five runs per timing metric. They are carried over rather than recaptured: the frontend build inputs here are the same as at that head, minus the step 3 nits and plus the newer locale files on dev.

Metric Before After
Cold production build 19.87s 4.77s
Warm build 8.75s 4.87s
Dev server time to ready 7104ms 359ms
HMR latency 421ms 139ms
Bundle JS, raw / gzip 3477.60KB / 1198.75KB 3010.49KB / 1035.57KB
Bundle total, raw / gzip 4290.39KB / 1652.24KB 3763.97KB / 1474.34KB
Direct dependencies 75 65
Packages installed 1578 689
Audit findings, critical/high/moderate/low 4/78/63/13 1/50/38/6

Five things worth stating rather than leaving to be inferred:

  • Cold and warm builds are within noise of each other, because the new build has no meaningful persistent cache to warm and now also runs a type check on every build, cold or warm alike. The old toolchain had a real warm cache, which is why its two figures differ so much. The comparison to draw is cold against cold.
  • Dev server time-to-ready is wall clock on both sides, including process spawn. The new tool self-reports a much smaller number that excludes that, and using it would compare two different quantities.
  • Chunk boundaries differ structurally between the two bundlers, so the bundle rows compare total shipped bytes rather than like-for-like chunks.
  • HMR latency was measured once, at commit 7200ca3 on the build: replace Create React App with Vite #1567 branch, on both toolchains, and carried forward from there: none of the commits since touch the hot-update path.
  • The bundle totals include the bootstrap-icons font files the first migration pass had silently dropped, and the cold build includes the restored type check. Both are named costs of parity fixes, already folded into the deltas.

Not included

This PR adds no CI job for the frontend. The project runs no frontend job today, and a build on every push is a cost a maintainer should choose to take on, not one this migration should impose. One constraint carries forward for whoever wires that job later: a bare go test ./... reports ok while asserting nothing about the built asset paths, because TestGetStyleResolvesBuiltAssets skips when no frontend build is embedded. Any future CI job needs to build the frontend first.

The dev server now binds to loopback only by default; reaching it from another device on the network needs an explicit --host flag.

Create React App's SVG-as-component imports (import { ReactComponent as X } from './x.svg') are not carried over to this configuration. Nothing in this codebase used them.

The commits carry (cherry picked from commit ...) lines pointing at the branch behind #1567, where these changes were first reviewed and where the regression matrix (subdirectory deploy, OAuth callbacks, absolute CDN public_url) was run.

The previous rules ignored /ui/build/*/*/*, which encoded the directory
depth of the old asset layout: build/static/js/main.js sits three levels
under build, so it matched. Assets emitted directly into build/static are
two levels down and matched nothing, leaving 229 build artifacts totalling
9.9 MB staged for commit.

Ignore everything under build and re-include the one file that is tracked
there, so the rules stop depending on how the build tool happens to nest
its output.

(cherry picked from commit 5fd7fc8)
react-scripts and react-app-rewired are replaced by Vite. The configuration
preserves the two output contracts the Go server depends on: the build
directory stays at ui/build, which static.go embeds, and emitted assets stay
under static/, which internal/router/ui.go serves as a route. Emitted files
keep the static/js, static/css and static/media grouping, because both the
repository's .gitignore and the analyze script match on that layout.

Production sourcemaps stay enabled to match the previous build, and the
REACT_APP_ prefix is retained so scripts/env.js remains the single source of
truth for configuration shared with the server.

sass and @types/node are raised to the versions Vite requires; the previous
sass predates the async compiler API the current toolchain calls.

(cherry picked from commit 70cf6ee)
Values still come from .env files generated by scripts/env.js under the
REACT_APP_ prefix, so no configuration keys change.

The public URL needs care rather than a direct substitution. The previous
toolchain stripped the trailing slash before exposing the value, so it read as
an empty string at the site root. import.meta.env.BASE_URL keeps the slash, and
substituting it directly produced a protocol-relative //custom.css. The trailing
slash is stripped here to preserve the previous behaviour at the root and to
stay correct if the site is ever served from a sub-path.

(cherry picked from commit 5e1625c)
require() in application source relied on the previous bundler's CommonJS
interop. Each call is replaced with the import form matching how the package
actually publishes itself: a namespace import for diff, which ships ESM with
named exports and no default, and named imports for semver, whose single
static export object is statically analyzable.

(cherry picked from commit 5ce56c9)
The tilde prefix is a webpack resolver convention with no equivalent elsewhere.
bootstrap-icons is given its explicit entry path rather than relying on the
package's sass field being consulted during bare-specifier resolution.

(cherry picked from commit 49b0ff5)
Routes loaded pages with a dynamic import built from an aliased template
literal. That shape cannot be analyzed, so no page received its own chunk and
the specifier reached the browser untransformed, leaving every lazily routed
page unable to load. The entry bundle absorbed the pages it should have split
out, growing to over a megabyte.

Pages are now enumerated with a bounded glob covering the three directory
depths routes actually use, excluding component subtrees so their own index
files do not become route chunks.

A page path with no matching module now rejects with the requested path and the
list of known keys, surfacing through the existing route error boundary. Two
routes are already in that state, pages/403 and pages/Admin/UserOverview,
neither of which has a module in the tree; both failed the same way before this
change, silently.

(cherry picked from commit 4fca611)
GetStyle scraped index.html with regexes matching one exact tag shape:
classic scripts with defer first, and stylesheet links with href before rel.
Any bundler emitting a different shape returned nothing, and server-rendered
pages would load with no JavaScript and no stylesheet while every build step
still reported success.

The tags are now read from the parsed document, so attribute order, attribute
set and quoting no longer matter.

header.html emits the scraped paths as script tags itself, and those were
classic scripts. A module bundle loaded that way fails on its first import, so
fixing only the parsing would have left server-rendered pages broken; the tag
is now declared as a module.

The self-check fixtures are replaced. The previous two asserted failure on
module scripts and on rel-before-href, both of which the parser now accepts, so
they would have inverted into false alarms. The replacements cover a stylesheet
with no script, a script with no stylesheet, and an inline script with no src.

golang.org/x/net moves to a direct requirement, matching its use here.

(cherry picked from commit eab6f9d, ui/template/header.html only)
react-scripts, react-app-rewired, customize-cra and config-overrides.js are no
longer reachable now that the build runs on Vite. yaml-loader is replaced by the
equivalent Vite plugin.

Three of the removed packages were already inert before this migration began.
Both purgecss packages were declared but wired nowhere: no postcss config exists
in the repository, config-overrides.js never referenced them, and no script
invoked them. buffer was aliased and provided as a global, but no application
source uses it, and the one dependency that requires it declares buffer as false
in its browser field, so bundlers stub it out.

The eslint config no longer extends react-app/jest, which shipped inside
react-scripts and configured rules for a test suite this project does not have.

(cherry picked from commit 1605fa7)
Plugin i18n modules call initI18nResource while they are being evaluated.
i18next only attaches its resource-store methods to the instance during
init, so if a plugin module evaluates before i18next.init has run, the
immediate addResourceBundle call throws.

Whether that happens is decided by the bundler: it is a function of how
modules are grouped into chunks and in what order those chunks evaluate.
Nothing in the application controls it and nothing reports it. The build
succeeds, the dev server works, the page returns 200 with the
server-rendered markup present, and the console stays empty, because the
throw happens while the entry module is still evaluating and takes the
whole application down before it mounts. The user gets the loading spinner
forever.

Register immediately only when there is an initialised instance to
register into, and otherwise let the existing initialized handler do it.
That makes the call correct in either order rather than correct in the one
order the current chunking happens to produce.

(cherry picked from commit 8a2b44e)
None of these are referenced any more, verified by searching the source,
the scripts, the workflows, the Makefile, the Dockerfile and the shell
scripts:

- postcss, declared but wired to nothing; no postcss config exists, and the
  build tooling pulls its own copy
- @testing-library/dom, already an indirect dependency of
  @testing-library/react
- @testing-library/user-event, imported nowhere

The ignore file still listed a config file that no longer exists, and two
comments pointed at an index.html path that moved.

The env generator also emitted three variables the current build never
reads: two compiler flags belonging to the old toolchain, and a bare
PUBLIC_URL that bypassed the REACT_APP_ prefix. The prefix itself stays,
since the build config reads exactly that prefix and the generator remains
the single source of truth for both sides.

(cherry picked from commit ffa3160)
The config is written in ES module syntax but was being loaded as
CommonJS, which the build tool warns about on every run and has announced
it will stop supporting. The warning printed on every check run too, which
is noise in output that is meant to be read.

Renaming to .mts makes it load as an ES module, where __dirname does not
exist, so derive the directory from import.meta.url instead. Using
fileURLToPath rather than import.meta.dirname keeps it working on the
whole Node range the package declares support for.

(cherry picked from commit 1b59fa7, ui/vite.config.ts to ui/vite.config.mts only)
Module scripts are deferred by definition, so defer has no effect on them.
Leaving it there reads as though it controls load behaviour.

(cherry picked from commit f3eba46)
src/App.test.tsx is the test that ships with a fresh Create React App
project. It asserts the text "learn react", which appears nowhere in this
application, so it would fail if it ran, and it cannot run: no test runner
is installed, and there is no test script, runner config or setup file.

It was the only reason three devDependencies and a tsconfig include
pointing into node_modules were still present. Verified before removing
that it is the only test file, that nothing else imports the testing
library, that no plugin workspace package depends on it, and that the
project still type-checks with the include gone.

The commit that removed the rest of that toolchain missed this file.

(cherry picked from commit bcdee31)
bootstrap-icons.scss builds its @font-face url()s from
$bootstrap-icons-font-dir, which defaults to "./fonts", a path meant
to be relative to the partial's own location inside node_modules.
Dart Sass does not rebase url()s during @import, so that relative
path survived unchanged into the compiled CSS. Vite then had no way
to resolve "./fonts/bootstrap-icons.woff2" from the entry stylesheet,
so the production CSS shipped the literal unresolved path and the
build emitted zero font files. Every icon in the app rendered as a
missing-glyph box.

Setting the font-dir variable to a package-resolvable specifier
before importing the partial lets Vite's asset pipeline resolve and
emit the woff/woff2 files under static/media and rewrite the url()s
to point at them.

(cherry picked from commit d78f566)
configs/config.yaml's ui.public_url used to flow into the old CRA
build's PUBLIC_URL, which set webpack's public path and was read
directly by PageTags for the custom.css href. The Vite migration
dropped that bare PUBLIC_URL variable as apparently unused, but
nothing replaced its job: vite.config.mts never set base, so
import.meta.env.BASE_URL stayed at the default "/" regardless of
config, and index.html's manifest link had been hardcoded to a
root-relative path during the migration. A deployment served from a
non-root public_url would silently lose asset and manifest
prefixing.

Derive base from the same REACT_APP_PUBLIC_URL value scripts/env.js
already generates from config.yaml, normalizing the trailing slash
Vite requires without double-slashing the root case. Let the
manifest link use Vite's %BASE_URL% html macro instead of a
hardcoded path, so it resolves the same way the rest of the built
asset tags do. PageTags' existing custom.css href logic already
derives correctly from BASE_URL once it carries the real value, so
it needed no change.

Verified with a build against a non-root public_url: the emitted
index.html's script src, stylesheet hrefs, modulepreload links and
manifest link all came out prefixed with the configured path, and
the default root config still builds byte-identical manifest and
asset paths to before.

(cherry picked from commit 9c4b557)
CRA's webpack build surfaced type errors through its fork-ts-checker
plugin, so a broken type would fail the build. Vite's build has no
equivalent checker wired in, and the migration dropped type checking
from the build entirely with nothing else covering it. Run tsc in
check mode before the bundler so the build fails again on type
errors. tsc --noEmit is clean today and takes about 4 seconds.

(cherry picked from commit 794abd8)
Every build prints dozens of Sass deprecation warnings (color
function renames, mixed-decls) that originate entirely inside
bootstrap 5.3.3's own scss files. There is nothing to act on here,
and the volume buries any warning that points at our own code.
quietDeps only silences warnings whose source sass file is loaded
as a dependency, so warnings from our own stylesheets still show.

(cherry picked from commit 0fbf501)
yaml@2.6.1's core schema kept bare dates as strings and left merge
keys unresolved. @modyfi/vite-plugin-yaml defaults to js-yaml's
DEFAULT_SCHEMA, which resolves bare YYYY-MM-DD scalars to JS Date
objects and turns on merge key support. Nothing in the current i18n
or plugin yaml corpus hits either case, so this is a guard against a
future translator adding one and silently changing a string field
into a Date. Pin CORE_SCHEMA, which the plugin accepts directly as a
js-yaml Schema value. js-yaml is already a direct devDependency used
by scripts/env.js and scripts/loadPlugins.js, so this adds nothing
new to package.json.

Verified with a standalone parse of every file under i18n/*.yaml and
ui/src against both schemas: all 54 files parse identically, so the
pin changes nothing for the real corpus. A synthetic bare-date and a
synthetic merge-key fixture each parse differently under the two
schemas, confirming the pin has real effect where it would matter.

(cherry picked from commit 3340c22)
Module scripts fetch in CORS mode, so the tag the bundler emits for
this entry carries the crossorigin attribute. The server-rendered
template was missing it, which meant the two render paths for the
same script produced different tag shapes for no functional reason.

Adding crossorigin here brings the template in line with the built
output. Note that this attribute does not itself impose a new
requirement: any CDN origin serving these files already needs to
send Access-Control-Allow-Origin for module scripts, because that
follows from type="module" regardless of whether crossorigin is
present on the tag.

(cherry picked from commit 80ec426)
The release pipeline builds the frontend with a pinned Node version
before running goreleaser. Vite, the bundler this project now uses,
declares an engines floor of node ^20.19.0 || >=22.12.0, and the
pinned 20.18.1 sits below that floor. Bump the pin to 20.19.0 so the
release build runs on a Node version Vite actually supports.

Checked every other workflow file under .github/workflows: none of
them pin a Node version for a frontend build. The Docker image
workflows only invoke docker buildx against the root Dockerfile,
which installs Node from an unpinned Alpine package, a separate
concern not touched here.

(cherry picked from commit f0ee9b9)
Vite's resolveBaseUrl keeps an absolute external base exactly as
configured only when the command is build. For dev and preview
(both command=serve) it silently reduces the same base to its
pathname, dropping scheme and host. Confirmed by reading
resolveBaseUrl in the installed vite package: the external branch
only survives when isBuild is true.

Our base wiring passes REACT_APP_PUBLIC_URL straight through with
no mode check, and that is correct: only vite build output ever
reaches the Go server, which is the only thing actually deployed.
Local dev and preview serving a reduced base is a difference in a
throwaway artifact, not a bug.

Recorded the constraint next to the code so a future change does
not "fix" serve mode into breaking the one output that matters.

(cherry picked from commit 7a65192)
The declared floor was >=20, which admits Node 20.0 through 20.18,
21.x, and 22.0 through 22.11, all of which vite 8 warns about on
every invocation: its own supported range is ^20.19.0 || >=22.12.0.
Declare that exact range so the engines field and the tool agree on
which Node versions are expected to work. pnpm reports a mismatch as
a warning either way, since engine-strict is not set.

(cherry picked from commit 36cd19a)
No ImportMetaEnv augmentation existed, so import.meta.env.REACT_APP_*
fell back to vite/client's index signature and typed as any. A typo
in one of those keys compiled clean and would only surface at
runtime as a missing value.

Added the strictImportMetaEnv marker interface vite/client checks
for, which drops that fallback, plus an ImportMetaEnv augmentation
for the keys actually read that way. Grepped import.meta.env.REACT_APP_
across src and vite.config.mts first: only REACT_APP_API_URL
(request.ts) and REACT_APP_BASE_URL (App.tsx, router/alias.ts)
are read through import.meta.env, so only those two are declared.
REACT_APP_PUBLIC_URL exists too, but vite.config.mts reads it
through loadEnv, never through import.meta.env, so it stays out.

Verified with a throwaway probe referencing
import.meta.env.REACT_APP_TYPO: tsc failed on it naming the probe
file, then passed again once the probe was deleted. vite/client's
own BASE_URL, MODE, DEV, PROD and SSR keys still type-check
unchanged, since strictImportMetaEnv only removes the fallback for
keys nothing declares.

(cherry picked from commit d3f21dd)
The declaration read var(-bs-body-bg) with a single leading dash, which is not
a valid custom property reference, so the background-color was discarded and
the editor never received the themed background it asks for. The previous CSS
minifier accepted the invalid value and passed it through; the current one
rejects it outright, which is how it surfaced.

This changes rendering: the editor now takes the themed background it was
always meant to have.

(cherry picked from commit 4f1ae4f)
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.

1 participant