Skip to content

build: move the UI off create-react-app and onto Vite - #916

Open
blaipr wants to merge 4 commits into
ctrliq:mainfrom
blaipr:build/move-ui-to-vite
Open

build: move the UI off create-react-app and onto Vite#916
blaipr wants to merge 4 commits into
ctrliq:mainfrom
blaipr:build/move-ui-to-vite

Conversation

@blaipr

@blaipr blaipr commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Sits on top of #914, which is still open, so the first commit here is that pull request unchanged and this one should land after it.

Steps two and three of Path to Vite, taken together. Splitting them the way the thread lays out would mean an index.html that has to satisfy webpack and Vite at the same time, and reviewing that is harder than reviewing the whole move.

What goes

The ejected create-react-app machinery: scripts/build.js, scripts/start.js, config/webpack.config.js, config/webpackDevServer.config.js, and the env, paths, modules, getHttpsConfig, themeMetaLoader, webpack and devUtils files beside them. 17,202 lines deleted against 5,866 added.

34 devDependencies go with them, 62 down to 28. The thread estimated 29, and typescript is in the set as it predicted: no .ts file, no tsconfig.json, nothing to check.

The three pieces of source that were written against webpack

Each is now written against neither bundler rather than ported to Vite:

themeRegistry.js used require.context twice, once to pull the stylesheets into the build and once through config/themeMetaLoader.js, a custom webpack loader that read a /* Name: */ comment out of each theme and detected the dark variant. Both are import.meta.glob now with the metadata parsing inlined, which is the one real blocker the sizing named. It also means the module works under the test runner, so testUtils/themeRegistryMock.js and its alias are deleted rather than ported.

setupCSP.js assigned __webpack_nonce__, which only webpack understands, and which would have thrown in a strict-mode ES module. styled-components v6 looks for a <meta name="sc-nonce"> before it looks for that global, so Django now renders the nonce into index.html and the source stops caring what bundles it. The blanket /* eslint-disable */ at the top of that file went with it: it was there for the undeclared global, and the file lints clean with no suppression now, which is worth having since a blanket disable hid everything else in the file too.

i18nLoader.js imported `./locales/${locale}/messages`. The static part of a dynamic import has to carry an extension, or the import cannot be analysed and no catalogue is bundled.

That one has a tail. lingui.config.js set compileNamespace: 'cjs', so the compiled catalogues were module.exports = .... The production bundler converts CommonJS on the way through, so the built application was fine and every check passed, but the dev server hands modules to the browser as they are written and there is no module to assign to. dynamicActivate threw, and the application sat on its pre-i18n "Loading..." forever. It is compileNamespace: 'es' now, which makes lingui emit messages.mjs, and the import follows.

index.html is a Django template, and that shapes the config

awx/ui/build is in TEMPLATES.DIRS and awx/ui/build/static is in STATICFILES_DIRS, so the output layout is a contract rather than a preference: build/static/{js,css,media} is what the Makefile copies into /var/lib/awx/public/static and what Django serves at /static/.

The file moves to the repository root as Vite expects, and stays servable as plain HTML by the dev server. The {% load static %}, the CSP nonce, the policy and the {% static %} favicon are injected at build time by a small plugin in vite.config.mjs.

The dev server proxy, and an option that should not have been there

src/setupProxy.js handed webpack-dev-server a target and nothing else, and two departures from that turned out to matter. Both broke the dev server while leaving the built application perfectly healthy.

changeOrigin: true reads as harmless and is not. It rewrites the Host header to the target, so the browser still sends Origin: https://localhost:3001 while the request claims to be for :8043. Django compares the two, CSRF_TRUSTED_ORIGINS is empty, and POST /api/login/ comes back 403. The option is gone, which is what the original had.

server.cors, which Vite enables by default, answers every OPTIONS request itself as a preflight, 204 with no body, and never forwards it. This API uses OPTIONS to describe its fields, so any screen reading data.actions.GET got undefined and threw Cannot read properties of undefined. Jobs, Templates, Credentials and the rest of the list views all failed this way. It is off now: the API is reached through the proxy on the same origin, so there is nothing for CORS to permit in the first place.

Three things the build got wrong before the output was read

The build exiting zero is not the same as the build being right, and none of these would have failed until runtime:

  • A web worker was written to build/assets. That is not one of Django's static directories, so simulationWorker would have 404'd in the browser while the build reported success. Workers take their own rollupOptions.
  • And once it was served, it could not load its own dependencies. simulationWorker.js calls importScripts('d3-collection.v1.min.js') and four siblings, relative to the worker's own URL. That worked only because webpack emitted the worker into /static/js, beside those files. The dev server serves it from /src/util, so all five 404'd and the topology view threw. They are absolute /static/js/... paths now, which resolve identically under the dev server and under Django.
  • The favicon was linked twice, once for the dev server and once through {% static %}.
  • server.https: {} leaves Vite with no certificate. HTTPS=true generated one under the ejected dev server, so @vitejs/plugin-basic-ssl keeps the UI on https://127.0.0.1:3001, which is what CONTRIBUTING.md tells people to open.

Shared with the test config

The babel pass and the alias list live in config/build/ and are used by both vite.config.mjs and vitest.config.mjs, so the application and its tests cannot come to disagree about what a module means. Babel transforms the JSX itself rather than leaving it to oxc, because this codebase keeps JSX in .js files, a create-react-app convention, and Vite 8 offers no way to declare that.

Two things already broken on main, fixed in passing

  • awx/ui/Dockerfile adds .eslintignore, .eslintrc.json and .linguirc. None have existed since the flat eslint config landed, so docker build there fails on the first of them today.
  • The babel block in package.json was dead configuration that only eslint's parser still read. Removing one of its plugins from devDependencies made eslint fail to parse all 1,084 files, which is how it surfaced.

The build chain, not just the build

npm run build is not what CI or the image runs, so the whole chain was exercised from a wiped build/ and flag file:

  • npm ci from an empty directory, with nothing but package.json and the lockfile, since an incrementally grown node_modules can hide a lockfile that does not resolve. 695 packages in 37s. On main the same command installs 1,676, so this removes 981 packages, a little under 60% of the tree.
  • make ui-release, which is what make sdist reaches through $(UI_BUILD_FLAG_FILE) and therefore what the image build runs: npm ci, compilemessages.py, compile-strings, then the build. Clean, 9.62s.
  • awx-manage collectstatic, the step in the Dockerfile that actually depends on this change, since it reads awx/ui/build/static through STATICFILES_DIRS. 100 files collected, and the application serves from the collected tree with the Playwright suite green against it.

One thing that is worth knowing and is not a defect: index.html is a Django template, so after a UI rebuild the web process has to be restarted before it serves the newly hashed filenames. The image bakes both before anything starts, so it only affects a running development stack.

Checks

  • npm run build clean, and the output checked file by file rather than trusted: only build/static at the top level, the worker inside static/js, one favicon link, fonts resolving both for the copied patternfly.min.css and for the processed theme CSS.
  • Dev server up over https in 458ms, with JSX, the lingui macro and the theme glob each confirmed by fetching real modules through it.
  • Full UI suite: 553 files, 2985 tests, green, and run twice in a row from a clean start rather than once, because the leak above failed intermittently and a single green run is what the broken version produced about half the time.
  • eslint . and prettier --check clean.

Both paths are also verified in a real browser against the running stack, which is what caught the catalogue format above. Django renders the template with a real nonce and no unrendered tags, every referenced asset returns 200 including the worker and the fonts, and the login screen renders with data-theme applied, no console errors, no failed requests and no CSP violations. The dev server on 3001 does the same.

Signing in is exercised too, on both, which is what turned up the proxy option above and is also the only way to reach styled-components: it injects nothing until there is a page to style. After login it emits its stylesheet carrying the nonce from the sc-nonce meta, 21 rules, no CSP violations, so the replacement for __webpack_nonce__ is confirmed working rather than merely present.

Worth saying plainly, because it is the argument for doing that last check at all. Two defects survived every automated gate here, and both were dev-server only:

defect what it broke what still passed
compileNamespace: 'cjs' dev server never finished loading production build, 2985 tests, eslint, prettier
the fix for it, compileNamespace: 'es' 502 of 553 test files the production build, the browser, and the Playwright suite
changeOrigin: true login on the dev server, 403 all of the above, plus login on the built application
server.cors default every metadata driven screen on the dev server all of the above, plus every screen on the built application

| worker importScripts | the topology view on the dev server | all of the above, plus the topology view on the built application |

None of the four is visible to anything this repository runs in CI, and they share a shape: the configuration ported cleanly, but anything that depended on where a file physically sat broke when the serving path changed.

The catalogue fix has its own tail, and it is the reason the row above lists what still passed. Moving lingui to ES modules made messages a named export where it had been module.exports, and seven files import the compiled catalogue as a default. That broke testUtils/rtlContexts.js, and through it 502 of the 553 test files. The production build did not notice, because the bundler converts CommonJS on the way through, and neither did the Playwright suite, because it runs against the built application. Only the unit suite loads those catalogues directly. Three forms needed correcting: the default imports, the bare references at their call sites, and one dynamic import() built from a template literal that a grep for static imports does not find.

A pre-existing leak, found while confirming the above

src/util/debounce.test.js calls vi.useFakeTimers() and never puts the real ones back. Under the VM pool the environment is shared by every file in a worker, so from that point on each file that ran after it inherited fake timers, and anything waiting on a real clock, waitFor included, stopped behaving. It presented as one test failing per full run, a different one each time, in a file that passes on its own.

That file now restores them, and setupTests.js does the same in its global teardown so the next one cannot leak them either. This is the third leak of that shape here, after the shared url and the unrestored window.localStorage: sharing an environment is what makes the suite four times faster, and the price is that whatever a file installs globally it has to put back.

Walked on both the dev server and the built application, each clean with no console errors: twelve list views, Jobs, Templates, Credentials, Projects, Inventories, Hosts, Users, Organizations, Schedules, Instance Groups, Applications and Notification Templates; the job template form, which reads actions.POST; the topology view, which drives the worker; and the API websocket, which completes its subscribe and receives its acknowledgement through the proxy. Catalogue loading was checked in Japanese, Spanish and Arabic, the last of which also confirms dir="rtl" still lands on the root element.

The Playwright suite in awx/ui/e2e passes, 11 tests, against the built application and against the dev server. It is the right suite for this change: #762 added it for what jsdom cannot reach, event bubbling, focus and portal rendered content, and the workflow job selector specs in it exist because a jsdom test once passed on a click that did nothing in every real browser.

/sso was compared path by path rather than exercised, since no backend is configured here: /sso/complete/, /sso/login/ and /sso/metadata/saml/ return the same status through the proxy as they do direct, the last of them a real 200. The job output view, the heaviest screen in the application, renders a completed job identically on both, PLAY RECAP and all.

Theme switching gets its own paragraph, because it is what this change rewrote. All five themes were switched through on the built application under the enforced CSP: dark, light, awx, default and an uploaded custom one. Each applies, and the dark variants pick up pf-v6-theme-dark while the light ones do not, which is the part worth checking: that flag used to be decided by config/themeMetaLoader.js reading the stylesheet at build time, and it is now decided by the same regular expression inlined into themeRegistry.js. Same answer from both. The custom theme is listed last after the four shipped ones and its stylesheet applies, so the interaction with #898 is intact, with no CSP violations.

@blaipr
blaipr force-pushed the build/move-ui-to-vite branch 5 times, most recently from 29e6d1c to 2529863 Compare September 10, 2026 19:17
@cigamit cigamit self-assigned this Sep 10, 2026
@cigamit cigamit added the enhancement New feature or request label Sep 10, 2026
@blaipr
blaipr force-pushed the build/move-ui-to-vite branch from 2529863 to 75ec2f7 Compare September 10, 2026 20:27
@blaipr

blaipr commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Fixing some errors

@cigamit

cigamit commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

I am getting sporatic test failures (always a different test) after around 190+ seconds. Not sure if we are hitting a timeout or something.

 Test Files 491 passed (553)
      Tests 2747 passed (2747)
   Start at 20:36:03
   Duration 194.62s
node:events:497
      throw er; // Unhandled 'error' event
      ^

Error: Worker exited unexpectedly with exit code 1 during started state while running test file /awx_devel/awx/ui/src/routeConfig.test.js
    at Worker.emitUnexpectedExit (file:///awx_devel/awx/ui/node_modules/vitest/dist/chunks/index.B89dZ0-N.js:10938:33)
    at Worker.emit (node:events:519:28)
    at Worker.[kOnExit] (node:internal/worker:318:10)
    at Worker.<computed>.onexit (node:internal/worker:232:20)
Emitted 'error' event at:
    at Worker.emitUnexpectedExit (file:///awx_devel/awx/ui/node_modules/vitest/dist/chunks/index.B89dZ0-N.js:10940:22)
    at Worker.emit (node:events:519:28)
    at Worker.[kOnExit] (node:internal/worker:318:10)
    at Worker.<computed>.onexit (node:internal/worker:232:20)

 Test Files 550 passed (553)
      Tests 2981 passed (2981)
   Start at 20:40:26
   Duration 197.09s
node:events:497
      throw er; // Unhandled 'error' event
      ^

Error: Worker exited unexpectedly with exit code 1 during started state while running test file /awx_devel/awx/ui/src/screens/Inventory/InventoryHosts/InventoryHosts.test.js
    at Worker.emitUnexpectedExit (file:///awx_devel/awx/ui/node_modules/vitest/dist/chunks/index.B89dZ0-N.js:10938:33)
    at Worker.emit (node:events:519:28)
    at Worker.[kOnExit] (node:internal/worker:318:10)
    at Worker.<computed>.onexit (node:internal/worker:232:20)
Emitted 'error' event at:
    at Worker.emitUnexpectedExit (file:///awx_devel/awx/ui/node_modules/vitest/dist/chunks/index.B89dZ0-N.js:10940:22)
    at Worker.emit (node:events:519:28)
    at Worker.[kOnExit] (node:internal/worker:318:10)
    at Worker.<computed>.onexit (node:internal/worker:232:20)
 Test Files 552 passed (553)
      Tests 2983 passed (2983)
   Start at 20:44:57
   Duration 198.58s
node:events:497
      throw er; // Unhandled 'error' event
      ^

Error: Worker exited unexpectedly with exit code 1 during started state while running test file /awx_devel/awx/ui/src/screens/Inventory/InventoryGroups/InventoryGroups.test.js
    at Worker.emitUnexpectedExit (file:///awx_devel/awx/ui/node_modules/vitest/dist/chunks/index.B89dZ0-N.js:10938:33)
    at Worker.emit (node:events:519:28)
    at Worker.[kOnExit] (node:internal/worker:318:10)
    at Worker.<computed>.onexit (node:internal/worker:232:20)
Emitted 'error' event at:
    at Worker.emitUnexpectedExit (file:///awx_devel/awx/ui/node_modules/vitest/dist/chunks/index.B89dZ0-N.js:10940:22)
    at Worker.emit (node:events:519:28)
    at Worker.[kOnExit] (node:internal/worker:318:10)
    at Worker.<computed>.onexit (node:internal/worker:232:20)

@blaipr

blaipr commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Working on it

Steps two and three of the sequence in the Path to Vite thread, taken
together. Splitting them would mean an index.html that has to satisfy two
bundlers at once, which is worse to review than the whole move.

The ejected create-react-app machinery goes: scripts/build.js, scripts/start.js,
config/webpack.config.js, config/webpackDevServer.config.js, and the env,
paths, modules, getHttpsConfig, themeMetaLoader, webpack and devUtils files
beside them. 34 devDependencies go with it, 62 down to 28, typescript among
them, which the thread called out for having no .ts file to check.

Three pieces of source were written against webpack rather than against a
bundler, and each is now written against neither:

  themeRegistry.js discovered the shipped themes with require.context, twice,
  once for the stylesheets and once through config/themeMetaLoader.js, a
  webpack loader that read a /* Name: */ comment out of each file. Both are
  import.meta.glob now, the metadata parsing inlined. This was the one real
  blocker the sizing named. It also means the module works under the test
  runner, so testUtils/themeRegistryMock.js and its alias are deleted rather
  than ported.

  setupCSP.js assigned __webpack_nonce__, which only webpack understands.
  styled-components looks for a <meta name="sc-nonce"> before it looks for
  that global, so Django renders the nonce into index.html and the source
  stops caring what bundles it.

  i18nLoader.js imported `./locales/${locale}/messages`. The static part of a
  dynamic import has to carry an extension or the catalogues are not bundled.

index.html moves to the root as Vite expects. It stays a Django template,
because awx/ui/build is a template directory and awx/ui/build/static is a
staticfiles directory, so the output layout is a contract rather than a
preference: build/static/{js,css,media} is what the Makefile copies and what
STATICFILES_DIRS points at. The {% load static %}, the nonce and the policy
are injected at build time by a plugin in vite.config.mjs, which keeps the
file servable as-is by the dev server.

Three things the build got wrong before the output was read rather than
trusted. A web worker was written to build/assets, which Django does not
serve, so it would have 404'd at runtime with the build reporting success.
The favicon was linked twice, once for the dev server and once through
{% static %}. And server.https: {} leaves Vite with no certificate, where
HTTPS=true generated one, so @vitejs/plugin-basic-ssl keeps the dev server on
https://127.0.0.1:3001 as CONTRIBUTING.md documents.

The babel pass and the alias list are shared with vitest.config.mjs rather
than restated, so the application and its tests cannot disagree about what a
module means. Babel transforms the JSX itself because this codebase keeps JSX
in .js files, a create-react-app convention, and Vite 8 offers no way to say
so.

Two things fixed in passing, both already broken on main. awx/ui/Dockerfile
adds .eslintignore, .eslintrc.json and .linguirc, none of which have existed
since the flat eslint config landed. And the babel block in package.json was
dead configuration that only eslint's parser still read, which is why eslint
started failing on every file the moment its plugin was uninstalled.

Verified: production build clean with the output layout checked file by file,
dev server serving over https in 458ms with the JSX, lingui macro and theme
glob confirmed through it, full UI suite 553 files and 2985 tests green,
eslint and prettier clean.
@blaipr
blaipr force-pushed the build/move-ui-to-vite branch from 75ec2f7 to 56697bf Compare September 10, 2026 21:09
@blaipr

blaipr commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, the varying counts with no failures is what points at it.

That run was against 75ec2f7771. The head is 56697bf207 now, pushed at 21:09, and two fixes landed in between. One of them is likely the cause: src/util/debounce.test.js called vi.useFakeTimers() and never put the real ones back. The VM pool shares one environment per worker, so every file that ran after it inherited fake timers. That fits a worker dying rather than a test failing, on a different file each run. Fixed there and in setupTests.js.

Worth a pull and another run.

I could not reproduce the worker exit here, with the heap capped at 1.5GB, with and without a vmThreads.memoryLimit, so I have not added one. If it comes back that is the next thing to try, and knowing how much memory the container has would help.

Vitest splits jest's one timeout in two, and only testTimeout came across with
the rest of the config. hookTimeout kept its 10s default, which a beforeEach
that renders a whole form can outrun while every other worker in the vm pool is
rendering one of its own.

Testing Library's findBy* and waitFor have a third timeout, again separate, and
its one second default is the same story. Neither masks a real failure: a query
that will never match still fails, it just takes longer to say so.

Measured on the Template screens, 264 tests: without these, four of seven runs
failed somewhere. With them, eight runs of that directory and a full run of all
553 files pass.
@cigamit

cigamit commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Tests are not completing for me as is. My dev server has 12 vcpus and 48 GB RAM. When I start the tests, I have 36GB unused, and the process dies with a OOM error before it finishes. At about that time, the process has chewed through roughly 20GB (plenty of ram left on the server). Sometimes it will finish but 4/5 runs dies OOM.

 ❯ src/screens/WorkflowApproval/shared/WorkflowApprovalButton.test.js [queued]

 Test Files 1 failed | 472 passed (553)
      Tests 2 failed | 2683 passed (2685)
   Start at 03:28:59
   Duration 195.69s

<--- Last few GCs --->

[1080:0x7f3a34002000]   189814 ms: Scavenge 1424.0 (1470.8) -> 1414.5 (1474.6) MB, pooled: 0 MB, 87.89 / 0.00 ms  (average mu = 0.996, current mu = 0.995) allocation failure; 
[1080:0x7f3a34002000]   190234 ms: Scavenge 1426.4 (1476.1) -> 1419.7 (1479.3) MB, pooled: 0 MB, 43.48 / 0.01 ms  (average mu = 0.996, current mu = 0.995) task; 


<--- JS stacktrace --->

FATAL ERROR: NewSpace::EnsureCurrentCapacity Allocation failed - JavaScript heap out of memory
----- Native stack trace -----

 1: 0x7f4599e71491  [/lib64/libnode.so.127]
 2: 0x7f459c060284 v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/lib64/libnode.so.127]
 3: 0x7f459c060649 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) [/lib64/libnode.so.127]
 4: 0x7f459c27c57b  [/lib64/libnode.so.127]
 5: 0x7f459c2b3884 v8::internal::MarkCompactCollector::Finish() [/lib64/libnode.so.127]
 6: 0x7f459c293d68 v8::internal::Heap::MarkCompact() [/lib64/libnode.so.127]
 7: 0x7f459c294685 v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::internal::GarbageCollectionReason, char const*) [/lib64/libnode.so.127]
 8: 0x7f459c294a94  [/lib64/libnode.so.127]
 9: 0x7f459c2974ac  [/lib64/libnode.so.127]
10: 0x7f459cb6c2c7  [/lib64/libnode.so.127]
Aborted (core dumped)

I see we are using pool: vmThreads. if I change it to just forks or threads, it barely uses any memory (less than 2G), but takes a lot lot longer.

 Test Files  553 passed (553)
      Tests  2985 passed (2985)
   Start at  03:41:17
   Duration  570.27s (import 33%, environment 32%, tests 27%, transform 4%, setup 4%)

     Import  1687 modules were evaluated 57362 times · 1934.96s total, 33% of tracked time
             ~149.41s faster with isolate: false — shared modules are evaluated once per worker instead of once per file
             learn more: https://vitest.dev/guide/improving-performance#test-isolation

Running it with vmForks still takes a lot of memory, but it seems to succeed every time, and runs a bit faster than vmThreads in my setup.

 Test Files  553 passed (553)
      Tests  2985 passed (2985)
   Start at  03:56:14
   Duration  178.97s (tests 51%, import 37%, setup 4%, transform 3%, worker 3%, environment 2%)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The containerized dev server is unreachable externally, browser targeting no longer follows the declared policy, and the Node tooling does not meet the pnpm requirement.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Replaces the ejected Create React App/Webpack toolchain with Vite while preserving Django static assets, CSP, themes, localization, workers, and Vitest integration.

Changes:

  • Adds shared Vite/Vitest Babel and alias configuration.
  • Converts Lingui catalogs to ESM and updates theme/worker handling.
  • Removes obsolete Webpack infrastructure and dependencies.
File summaries
File Description
Makefile Documents the Vite development target.
awx/ui/vitest.config.mjs Reuses shared Babel and alias configuration.
awx/ui/vite.config.mjs Defines the Vite build, proxy, assets, workers, and Django template integration.
awx/ui/testUtils/themeRegistryMock.js Removes the obsolete Webpack-specific theme mock.
awx/ui/testUtils/rtlContexts.js Uses the ESM Lingui catalog export.
awx/ui/src/util/validators.test.js Updates the English catalog import.
awx/ui/src/util/simulationWorker.js Uses stable absolute dependency URLs.
awx/ui/src/util/getRelatedResourceDeleteDetails.test.js Updates the catalog import.
awx/ui/src/util/debounce.test.js Restores real timers after tests.
awx/ui/src/themeRegistry.js Replaces require.context with Vite globs.
awx/ui/src/setupTests.js Adjusts Testing Library timeouts and timer cleanup.
awx/ui/src/setupProxy.js Removes the Webpack proxy module.
awx/ui/src/setupCSP.js Removes the Webpack nonce global.
awx/ui/src/screens/WorkflowApproval/shared/WorkflowApprovalUtils.test.js Loads the ESM catalog.
awx/ui/src/screens/Template/shared/PlaybookSelect.test.js Uses the named catalog export.
awx/ui/src/screens/Inventory/shared/ConstructedInventoryHint.test.js Uses the named catalog export.
awx/ui/src/screens/Inventory/ConstructedInventoryDetail/ConstructedInventorySyncButton.test.js Uses the named catalog export.
awx/ui/src/screens/Instances/Shared/RemoveInstanceButton.test.js Uses the named catalog export.
awx/ui/src/locales/en/messages.js Removes the CommonJS catalog.
awx/ui/src/i18nLoader.js Dynamically imports ESM catalogs.
awx/ui/src/customTheme.js Updates bundler-related documentation.
awx/ui/scripts/start.js Removes the CRA development launcher.
awx/ui/scripts/build.js Removes the CRA production builder.
awx/ui/public/index.html Removes the CRA HTML template.
awx/ui/package.json Adds Vite and removes Webpack dependencies and scripts.
awx/ui/package-lock.json Updates the npm dependency graph.
awx/ui/lingui.config.js Emits ES-module catalogs.
awx/ui/index.html Adds the Vite/Django HTML entry point.
awx/ui/eslint.config.mjs Ignores the Vite configuration.
awx/ui/Dockerfile Packages the Vite configuration and entry point.
awx/ui/config/webpackDevServer.config.js Removes Webpack dev-server configuration.
awx/ui/config/webpack/persistentCache/createEnvironmentHash.js Removes Webpack cache support.
awx/ui/config/webpack/ModuleScopePlugin.js Removes CRA module-scope enforcement.
awx/ui/config/webpack/ModuleNotFoundPlugin.js Removes Webpack error rewriting.
awx/ui/config/webpack/InterpolateHtmlPlugin.js Removes CRA HTML interpolation.
awx/ui/config/webpack.config.js Removes the Webpack build configuration.
awx/ui/config/themeMetaLoader.js Removes the custom theme loader.
awx/ui/config/paths.js Removes CRA path resolution.
awx/ui/config/modules.js Removes CRA module resolution.
awx/ui/config/getHttpsConfig.js Removes CRA HTTPS configuration.
awx/ui/config/env.js Removes CRA environment handling.
awx/ui/config/devUtils/printBuildError.js Removes build-error formatting.
awx/ui/config/devUtils/formatWebpackMessages.js Removes Webpack message formatting.
awx/ui/config/devUtils/fileSizeReporter.js Removes CRA bundle-size reporting.
awx/ui/config/devUtils/eslintFormatter.js Removes the Webpack ESLint formatter.
awx/ui/config/devUtils/devServerUtils.js Removes CRA server utilities.
awx/ui/config/devUtils/colors.js Removes terminal-color helpers.
awx/ui/config/devUtils/clearConsole.js Removes console-clearing support.
awx/ui/config/devUtils/checkRequiredFiles.js Removes CRA file validation.
awx/ui/config/devUtils/checkBrowsers.js Removes CRA browser-target validation.
awx/ui/config/build/babel.mjs Shares Babel transformation across Vite and Vitest.
awx/ui/config/build/aliases.mjs Centralizes source aliases.
Review details
  • Files reviewed: 50/69 changed files
  • Comments generated: 3
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread awx/ui/vite.config.mjs
Comment on lines +115 to +116
server: {
port: 3001,
Comment thread awx/ui/vite.config.mjs
// /static/, so an absolute base is what makes the two line up.
base: '/',
publicDir: resolvePath('./public'),
build: {
Comment thread awx/ui/package-lock.json
Three things the review asked for.

The VM pool is what this suite's speed comes from, and the contexts it
keeps are what it costs memory in. A thread pool holds them all in one
process heap: on a twelve core machine the run reached 20GB and died in
V8's own allocator, four runs in five, with the machine far from full.
Forks give each worker its own heap, and the run takes the same time.

The dev server binds to 0.0.0.0 and accepts any Host header, which is
what the ejected one did. The UI image starts it and publishes the port,
so the browser reaching it is not on the loopback interface Vite listens
on by default.

The build target is stated rather than left to Vite's default, because
Vite does not read browserslist for JavaScript the way the ejected build
did. package.json now declares the same set, so the policy and the build
agree.
@blaipr

blaipr commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @cigamit

pool is vmForks now. The VM contexts are what the memory goes on, and a thread pool keeps them all in one process heap, which is why it dies in V8's allocator with the machine far from full. A fork per worker gives each its own heap. Here it runs 553 files in 240s, the same as before, so the speed the VM pool buys is kept.

Two review points went in with it:

  • The dev server binds to 0.0.0.0 and accepts any Host header again, which is what the ejected one did and what the UI image needs when it publishes the port.
  • build.target is stated in vite.config.mjs, since Vite does not read browserslist for JavaScript. package.json declares the same set, so the policy and the build agree.

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

Labels

enhancement New feature or request

Development

Successfully merging this pull request may close these issues.

3 participants