Repository navigation
Speed up frontend builds: patch pofile, switch to SWC - #1415
Merged
Merged
Conversation
Frontend builds on DigitalOcean went from ~6.5 to ~25 minutes once the .po catalogs passed ~16.4k lines. pofile parses with `while (lines.shift())`, and V8's Array.shift becomes O(n) for arrays that size, so every catalog parse turned quadratic. lingui compile, the client build and the SSR build all parse every catalog repeatedly, so all three slowed down 5-10x. - Patch pofile (patch-package) to iterate by index - Compile with @vitejs/plugin-react-swc + @lingui/swc-plugin instead of Babel macros; @swc/core pinned to 1.7.42, the range the Lingui 4 plugin supports - Drop `lingui compile` from the build: the app imports the .po catalogs through the Lingui Vite plugin, so the compiled .js output is unused - Copy patches/ into Docker install stages; skip postinstall in the production-only install, which has neither patch-package nor pofile
|
🚅 Deployed to the Hi.Events-pr-1415 environment in Hi.Events
3 services not affected by this PR
|
railway-app
Bot
temporarily deployed
to
Hi.Events / Hi.Events-pr-1415
October 18, 2026 12:20
Destroyed
Contributor
|
| Mode | Result | ✅ Passed | ❌ Failed | ⏭️ Skipped | ⏱️ Duration | |
|---|---|---|---|---|---|---|
| Self-hosted | 592 | 0 | 2 | 4 | 14m 28s | |
| SaaS | 580 | 0 | 2 | 16 | 13m 49s |
⚠️ Flaky tests: failed first, then passed on retry (4)
- SaaS ·
seating/seating-flag-off.spec.ts:92· reserved seating with the feature flag off on Hi.Events Cloud › a seated event cannot be duplicated into unseated tickets while seating is off - SaaS ·
widget/widget-editor.spec.ts:104· widget editor › organizers embed their events list from the organizer area - Self-hosted ·
widget/widget-editor.spec.ts:104· widget editor › organizers embed their events list from the organizer area - Self-hosted ·
widget/widget-playground.spec.ts:29· widget playground › the playground embeds an organizer events list as a custom element
⏭️ Why tests were skipped
Self-hosted
- 3 × Runs only against a SaaS stack (E2E_SAAS_MODE=true).
- 1 × Lifecycle emails only exist in SaaS mode.
SaaS
- 15 × Runs only against a non-SaaS stack (E2E_SAAS_MODE=false).
- 1 × The composer form is gated behind Stripe/manual account verification in SaaS mode.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Staging frontend deploys on DigitalOcean went from ~6.5 min to ~25 min starting with #1411 (6m 54s on Oct 17 06:21 → 17m 46s at 21:37 → 24m 54s today).
Phase timings from the DO build logs:
lingui extractlingui compilevite buildvite buildThe code barely changed (+37 modules), but each locale
.pofile grew from 15,926 to 16,669 lines. That crossed V8's ~16.4k-element limit for constant-timeArray.prototype.shift().pofile(used by@lingui/format-po) parses withwhile (lines.length > 0) { lines.shift() … }, so each parse turned quadratic: about 9ms → 55ms per catalog. Lingui parses every catalog many times per locale, inlingui compileand again in the Vite plugin for both the client and SSR builds, so every step slowed 5–10x.pofilehas had no release since 2022.What
pofilewithpatch-packageto iterate by index. Parse time is flat again (~150ms for all 21 catalogs at every commit tested).@vitejs/plugin-react-swc+@lingui/swc-plugin@4.1.0replace@vitejs/plugin-reactwith the Babelmacrosplugin.@swc/coreis pinned to1.7.42viaresolutions, the range the Lingui 4 SWC plugin supports.lingui compilefrombuildandDockerfile.ssr. The app loads catalogs withimport(\./locales/${locale}.po`)through@lingui/vite-plugin, so the compiled.jsoutput is never used.messages:extractstays inbuild`: it's the only guard against a string missing from the catalogs, which would render as its hashed id in production.patches/into the install stages so the patch applies. The production-only install inDockerfile.ssrdrops thepostinstallhook, because neitherpatch-packagenorpofileis installed there.Results (CI,
Dockerfile.ssrbuild step on GitHub runners)The client build is still above the pre-slowdown figure because #1412 added the Scalar API reference (+5,900 modules). On this PR's first run, the step's total wall time also includes an uncached dependency install (the lockfile changed) and re-exporting every layer to the Actions cache.
Results (local, Node 24, Apple Silicon)
lingui compilevite buildvite buildDO's builder is roughly 5–10x slower than a laptop on these CPU-bound steps.
Behaviour checks
tmacros (t\Sold to ${name ?? t`an attendee`}`) that Babel never expanded: the innertshipped as a call to whatever minified variable was namedt`. SWC expands them correctly, so this also fixes that.Dockerfile.ssrimage builds, applies the patch indeps, and serves pages.lingui extractleaves the catalogs unchanged, andlingui compileoutput is byte-identical.Notes
node_modulesvolume:docker compose -f docker-compose.dev.yml up -d --build -V frontend