Repository navigation
chore(frontend): cut frontend bundle size (code-shiki, nextstepjs, api-client) - #4864
Conversation
Two eagerly-bundled dependencies dominated first-load JS: - @lexical/code-shiki ships a single ~8.7 MB file (every Shiki grammar + theme inlined as data, not tree-shakeable). Two static imports forced it into the synchronous first-load graph of every editor-bearing page, so evaluation results / testset detail / evaluators playground / annotation queue each shipped ~10 MB of raw JS up front. Convert the tokenizer and DynamicCodeBlock imports to lazy import(); both already had Prism/plaintext fallbacks, so highlighting upgrades to Shiki once the async chunk resolves. First-load raw JS on those pages drops ~8.5 MB. - @agentaai/nextstepjs (~136 kB: NextStepReact + motion/spotlight) sat in the shared _app chunk on every page though tours only run during onboarding. It locates targets via document.querySelector and renders through a portal, so it does not need to wrap the app tree: render children directly and mount the renderer as a lazy childless sibling via a local split-point module. Mark the package sideEffects:false (pnpm patch) so the lightweight NextStepProvider context import no longer drags the renderer back into _app via the barrel. Shared first-load JS: 1.07 MB -> 1.02 MB; _app 906 kB -> 859 kB.
The Fern-generated root AgentaApiClient statically imports all 27 resource clients, so every reference to it bundled ~515 kB. It was instantiated at module scope in _app purely to pin the host, dragging the whole client into the shared first-load chunk on every page. Resource clients self-normalize auth in their own constructors (they accept the same BaseClientOptions as the root client), so they can be constructed directly without the aggregator. Changes: - @agentaai/api-client: mark sideEffects:false and expose ./resources/* subpath exports so individual resource clients are importable and tree-shakeable. - @agenta/sdk: add config.ts (host pin via configureAgentaSdk + buildClientOptions, no AgentaApiClient import) and resources.ts (per-resource lazy accessors that import one resource client each). init/getAgentaSdkClient stay for compatibility. - _app pins the host via configureAgentaSdk (config-only entry) instead of constructing the monolith. - Migrate the entities api modules (trace, gatewayTool, secret, event, workflow, testset) and the evaluations consumers to per-resource accessors. api-client bytes in the shared _app chunk drop 515 kB -> 233 kB (only the 6 resource clients actually reachable from _app's import graph remain; the other 21 are no longer bundled at all). _app parsed 3.06 MB -> 2.79 MB.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…nd lazy-loading playground state
[4726] fix(docs): update sparse-checkout command to include DB-init SQL
docs: add Sanket2329 as a contributor for platform, and code
|
|
…ources getters PR Agenta-AI#4864 split the api-client per resource: trace/api and workflow/api now resolve their Fern clients via getTracesClient/getWorkflowsClient from @agenta/sdk/resources instead of getAgentaSdkClient from @agenta/sdk. The unit tests still mocked the bare @agenta/sdk module, so the mocks never intercepted and 22 tests failed on big-agents with real 404s. retrieveWorkflowRevision.test.ts keeps a bare @agenta/sdk mock too, since workflow/api/api.ts still uses getAgentaSdkClient for fetchSimpleApplication.
…9KB parsed) The June bundle work (PR Agenta-AI#4864) kept the monolithic AgentaApiClient (all 27 resource clients) out of _app via per-resource accessors. One line written since then regressed it: fetchSimpleApplication in workflow/api/api.ts imported getAgentaSdkClient from the @agenta/sdk root, whose barrel statically pulls the monolith - webpack concatenated 22 resource-client modules (~326KB parsed) back into _app. Measured: _app chunk 2672KB -> 2443KB parsed after re-pointing that call through a new getApplicationsClient() accessor. Prevention, since this is the second occurrence: - web/AGENTS.md Fern pattern now shows @agenta/sdk/resources accessors and names the root-barrel import as an anti-pattern (the old doc example was the template the regression copied) - packages eslint: no-restricted-imports error on the @agenta/sdk root (tests unaffected - package lint covers src/ only) - SessionInspector/api.ts (the only other root importer, async-chunk) migrated to getSessionsClient/getMountsClient accessors
What
Independent fixes that cut first-load JS, each found via an
ANALYZE=trueproduction build ofweb/oss. Split into two waves: the first three target the heaviest editor/onboarding/api chunks; the next three evict the page-specific provider, auth, and serialization code that was riding the shared_appchunk on every page.1. Lazy-load
@lexical/code-shiki(the big one)Its published prod build is a single ~8.7 MB file with every Shiki grammar + theme inlined as data (not tree-shakeable). Two static imports forced it into the synchronous first-load graph of every editor-bearing page. Converted the tokenizer (
@agenta/ui) andDynamicCodeBlockimports to lazyimport(); both already had Prism/plaintext fallbacks, so highlighting upgrades to Shiki once the async chunk resolves.2. Lazy-load
@agentaai/nextstepjsThe onboarding tour renderer (~136 kB: NextStepReact + motion/spotlight) sat in the shared
_appchunk on every page, though tours only run during onboarding. It locates targets viadocument.querySelectorand renders through a portal, so it does not need to wrap the app tree — children now render directly and the renderer mounts as a lazy childless sibling. The lightweightNextStepProvidercontext stays static. Required marking the packagesideEffects: false(pnpm patch) so the provider's barrel import no longer drags the renderer back into_app.3. Split
@agentaai/api-clientper-resourceThe Fern-generated root
AgentaApiClientstatically imports all 27 resource clients (~515 kB), and it was instantiated at module scope in_apppurely to pin the host — pulling the whole client into the shared chunk. Resource clients self-normalize auth in their own constructors (sameBaseClientOptions), so they can be constructed directly. AddedsideEffects: false+./resources/*subpath exports to the client, aconfig/resourceslayer in@agenta/sdk(host pin viaconfigureAgentaSdk+ per-resource lazy accessors, no monolith import), and migrated_app+ the entities api modules to per-resource accessors.init/getAgentaSdkClientstay for compatibility.4. Defer the playground/entity boot cluster off
_appGlobalStateProviderandAppGlobalWrappersstatically pulled a heavy registration graph into_appon every page:WebWorkerProvider→ playground execution state, theworkflowEntityBridgeside-effect import, the selection-system init + four modal adapters, and the playground/variant URL-sync slices. Every one of these is consumed only at user-action time (worker bridge, commit/archive callbacks, selection adapters resolved via a runtime Map), so they need to land before the first relevant interaction, not before render. Moved them into a single childlessDeferredAppBootmodule loaded vianext/dynamic({ssr:false})(mounted on first client paint), and made the two URL-sync slices lazyimport()behind a queue. The registries already tolerate "not yet registered" (lookups returnundefined), so the worst case is a skipped orchestration, never a crash.5. Lazy-load the SuperTokens
frontendConfigfrontendConfigstatically imports the emailpassword/passwordless/thirdparty recipes (the prebuilt-UI-bearing modules).SuperTokensReact.initalready runs post-mount inside an effect gated byisInitialized, so importing the config lazily there keeps those recipes out of_appwith zero behavioural change. The session recipe stays eager viauseSession.6. Move YAML serialization out of the helper barrel
getYamlOrJsonlived inlib/helpers/utils.ts— a widely-imported barrel — so its staticjs-yamlimport (~37 kB) rode_appeverywhere. Extracted it tolib/helpers/yamlFormat.tsand repointed its sole caller;js-yamlnow loads only with the drawers/editors that format YAML.Impact (Next.js "First Load JS shared by all", gzipped — the every-page download)
mainbaselineEditor pages (eval results, testset detail, evaluators playground, annotation queue) drop from ~10.7 MB to ~2.35 MB raw first-load, since the 8.5 MB code-shiki chunk is now async.
_appparsed size: 3.20 MB (main) → ~2.44 MB.Verification
ANALYZE=true next buildexits 0;tsc --noEmitclean on@agenta/sdkand@agenta/entities;pnpm lint-fixclean._appchunk to confirm the target module left it (0 markers) and reading the regenerated First Load number.Why we stopped here — the app-query / entity-state core was left untouched (deliberately)
~500 kB parsed (~50–80 kB gzipped) still sits in
_app: the workflow/testset entity molecules, the app-list query atoms (state/app/atoms/fetcher.ts→appWorkflowsListQueryAtom), the app-levelInfiniteVirtualTable, and the eval-run atoms. We left it on purpose:@agenta/entities/workflowbarrel (store + molecule + api), and the entity paginated stores pull the table. Evicting it requires splitting that barrel so the lightweight query doesn't drag the whole graph — a multi-file package refactor touching 60+ consumers, with added latency on the apps page every user hits. Deferring just the selection-system init was tried earlier and reverted (moved ~50 kB with picker-registration risk).@agenta/sdk's resource barrel moved the evaluations/workflows/testsets Fern clients out of_app(−101 kB parsed), but only −0.5 kB gzipped — Fern codegen is highly repetitive and gzip already crushes it. Not worth the churn for the download metric, so it was reverted. The lesson now baked in: rank bundle targets by gzipped delta, not parsed bytes.The wins in this PR are exactly the inverse — real, high-entropy third-party libraries (code-shiki, nextstepjs, supertokens recipes, js-yaml) and deferrable registration code, where parsed and gzipped savings align.