Skip to content

fix: Shrink the consumer plugin bundle and upload its source maps - #481

Open
kevwilliams wants to merge 6 commits into
mainfrom
perf/consumer-plugin-bundle
Open

kevwilliams wants to merge 6 commits into
mainfrom
perf/consumer-plugin-bundle

Conversation

@kevwilliams

@kevwilliams kevwilliams commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The project home page in cloud-portal waits on this plugin's Workloads column, and the column was costing far more than its size suggests. The plugin bundled its own copy of every library it shares with the portal (react-router, react-query, the curated datum-ui subset). The host's copy always wins at runtime, but the browser still downloaded and parsed each copy on every load. Together with minify: false, the home column shipped 1.7MB of JavaScript (403KB gzipped) across 23 requests. A Firefox capture of cloud.datum.net showed about 900ms from remoteEntry.js to the workloads API call.

This PR has four commits:

  • Stop shipping fallback copies of host modules. Production builds mark host-provided modules import: false. Dev builds keep the fallbacks so the standalone preview still works. react/jsx-runtime keeps its own copy, because the portal doesn't share that subpath and the plugin would otherwise throw "must be provided by host" on load.
  • Minify the bundle.
  • Upload source maps to Sentry, so minified frames stay readable. Maps go to the cloud-portal project, since that's where plugin errors are reported, and are matched by debug IDs rather than release. The Dockerfile deletes the maps from the image, because serve.ts serves every file in dist/.
  • Pass the Sentry token as a BuildKit secret. The token reaches the build through publish-docker's build-secrets input and a secret mount, not a build arg. A build arg would be recorded in the build stage's history, which publish-docker exports to the Actions cache (mode=max). The consumer image job no longer uses secrets: inherit, which would also have forwarded the token as a build arg.
Project home column Before After
Requests 23 19
Raw 1,713KB 237KB
Gzipped 403KB 74KB

Every module now marked import: false is already provided by cloud-portal's federation-host.ts (react, react-dom, react-dom/client, react-router, @tanstack/react-query and the datum-ui subpaths). This can ship without a portal change.

Test plan

  • Baseline build reproduces production's chunk hashes, so before/after sizes compare like for like
  • Loaded each build in headless Chrome from a minimal host using the same shared singletons as federation-host.ts: the home column renders identically, all five exposed modules resolve, and there are no errors or React "Invalid hook call" warnings
  • Build without SENTRY_AUTH_TOKEN skips Sentry entirely
  • Build with a token injects debug IDs into 54 of 55 chunks and attempts only the source-map upload. Tested against an unreachable Sentry URL: the failed upload doesn't fail the build
  • bun run typecheck passes
  • Token-reading step in the Dockerfile yields an empty token without the secret and the token with it; publish.yaml parses
  • Docker build with the secret mount runs in CI (not run locally)
  • Workloads pages and the instance shell window work in staging against the real portal
  • First publish run's build log shows the source-map upload succeeded

Notes for reviewers

  • Token availability is unconfirmed. The consumer image job passes secrets.SENTRY_AUTH_TOKEN as a BuildKit build secret. I couldn't check whether the org-level secret is shared with this repo. If it isn't, the secret is empty and the upload skips silently.
  • Sentry URL. No Sentry URL is configured, matching cloud-portal, which relies on the org auth token carrying the self-hosted URL.
  • Readability. Minifying removes the readable built bundle that the old minify: false comment was protecting. The uploaded source maps are the replacement.

kevwilliams and others added 3 commits October 14, 2026 16:49
The plugin bundled its own copy of every library it shares with the
portal (react-router, react-query and the curated datum-ui subset).
The host's copy always wins, yet the browser fetched and parsed each
fallback on every load: about 300KB gzipped, 516KB of it unminified
react-router source, for the project home column alone.

Production builds now mark the host-provided modules import: false.
Dev builds keep the fallbacks so the standalone preview runs without
a host.

react/jsx-runtime gets an explicit entry that keeps its fallback.
Left implicit it inherits import: false from react, but the portal
does not share that subpath, so the plugin would throw "must be
provided by host" on load.

Co-Authored-By: Claude <claude@anthropic.com>
minify: false kept the built bundle readable, but every page load
paid for it: the project home column shipped 576KB of unminified
JavaScript (119KB gzipped). Minified it is 237KB (74KB gzipped),
which cuts both transfer and parse time.

Readable stack traces come back through source maps uploaded to
Sentry in the next commit.

Co-Authored-By: Claude <claude@anthropic.com>
The plugin runs inside cloud-portal, so its errors reach the portal's
Sentry project with minified frames. Builds now emit hidden source
maps and upload them to the cloud-portal project.

Frames resolve through debug IDs injected into each chunk, not a
release: the release on those events names the portal's build, not
this plugin's. Release creation and finalizing are off for the same
reason.

The token comes from publish-docker's SENTRY_AUTH_TOKEN build arg and
is only visible to the build stage. Builds without it (local, forks)
skip the upload. The Dockerfile deletes the maps after the build,
since serve.ts serves every file in dist/ and the portal's asset
proxy would otherwise expose them.

Co-Authored-By: Claude <claude@anthropic.com>
@kevwilliams
kevwilliams requested a review from a team as a code owner October 14, 2026 20:50
Comment thread ui/consumer/Dockerfile Outdated
RUN bun run build
# Forwarded by publish-docker; the build uploads source maps to Sentry when set.
# Only this stage sees it: the final image copies dist/ alone.
ARG SENTRY_AUTH_TOKEN

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.

dont love this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, build-args end up in the exported cache (mode=max). Switched to a BuildKit secret mount via publish-docker's build-secrets, and dropped secrets: inherit on that job so the token isn't also forwarded as a build-arg. 6e629ee

@ecv ecv 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.

can we avoid token ARG

kevwilliams and others added 3 commits October 14, 2026 17:01
A build-arg is recorded in the history of the stage that uses it, and
publish-docker exports every stage to the Actions cache (mode=max),
so the token would have been readable by anyone who can restore that
cache.

The build now reads the token from a secret mount, which never lands
in a layer, history or cache. The consumer image job passes it through
publish-docker's build-secrets instead of secrets: inherit, which
would also have forwarded it as the SENTRY_AUTH_TOKEN build-arg.

Co-Authored-By: Claude <claude@anthropic.com>
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.

3 participants