perf(studio): load motion's minimal entry instead of the full component library
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m48s
CI / tests-unit (push) Successful in 1m52s
CI / tests-ui (push) Successful in 2m44s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m34s

/admin/studio/furni sat at 94.9% of its initial-JS budget (427436 of
450560 gzip bytes), so the next feature would have broken the build. Of the
98228 gzip bytes unique to that route, a large part is framer-motion.

This file uses motion twice, for one thing: a 150ms opacity fade on the result
pane when viewMode changes. Importing `motion/react` to get it pulls in
framer-motion's complete component library — 73 internal modules — plus its
render components, drag/gesture and projection code, none of which is
rendered here.

`motion/react-m` ships only the element factories: 2 internal modules, and the
same initial/animate/transition props, so the fade is unchanged. It exports the
elements flat rather than under a `motion.` namespace, so the import becomes
`div as Mdiv` and the two JSX tags are renamed to match.

I could not measure the resulting bundle here: the local build is OOM-killed
(exit 137) with the running containers on the host, so the actual saving is
unverified. The CI build reports it in build-reports, and the number in this
commit message should be read as a hypothesis, not a measurement.

Verified: typecheck clean, lint clean, and the 10 studio UI tests pass —
including the pane and navigation specs that exercise the view switch.
This commit is contained in:
openhands committed 2026-10-03 19:21:02 +02:00
1 parent 8218039c64
commit 7f07c111ac
1 file changed
+9 -3
@@ -26,7 +26,13 @@ import {
Trash2,
X,
} from "lucide-react";
import { motion } from "motion/react";
// motion/react-m is the minimal entry. The full motion/react pulls in
// framer-motion's entire component library (73 internal modules) for what this
// file needs: one fade-in on the result pane. The minimal entry ships only the
// element factories (2 modules) and exposes the same initial/animate/transition
// props, so the fade behaves identically. Only the element factory is
// imported, since that is the single one this file renders.
import { div as Mdiv } from "motion/react-m";
import {
lazy,
Suspense,
@@ -2053,7 +2059,7 @@ export function StudioClient({
</p>
</div>
) : (
<motion.div
<Mdiv
key={viewMode}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
@@ -2497,7 +2503,7 @@ export function StudioClient({
</span>
)}
</div>
</motion.div>
</Mdiv>
)}
</div>