[DevXP] Improve Linaria pre-build speed (#18382)
## Summary This PR improves Linaria/WYW pre-build speed and continues the migration of `twenty-ui` components away from runtime `ThemeContext` reads toward static CSS variables and theme constants. ### Linaria/WYW profiling plugin improvements (`twenty-shared`) - **Babel JIT warmup**: added a `buildStart` warmup step that triggers WYW's Babel JIT compilation before the real build starts, so the first real file doesn't pay the cold-start penalty - **`configResolved` hook**: detects dev vs prod mode and resolves the correct warmup file path relative to `config.root` - **Dev-only per-file logging**: slow file warnings are now gated behind `isDevMode`, keeping production/CI build output clean - **`closeBundle` summary**: moved the final top-slow-files report to `closeBundle` for accurate end-of-build reporting - **Removed noisy progress interval logging** in favor of the warmup log + final summary ### Migration from `ThemeContext` to static CSS variables / constants Across `twenty-ui`, replaced runtime `useTheme()` reads with: - `themeCssVariables` CSS custom properties (colors, spacing) - Hard-coded design-system constants (`ICON.size.md` → `16`, `ICON.stroke.sm` → `1.6`) so components no longer need a React context at render time — enabling Linaria static extraction **Components migrated:** - `Button`, `AnimatedButton`, `LightButton`, `LightIconButton`, `AnimatedLightIconButton`, `ButtonIcon`, `ButtonSoon` - `ProgressBar` (Framer Motion width animation → CSS `transition`) - `Info`, `HorizontalSeparator`, `LinkChip` - `MenuPicker`, `MenuItemLeftContent`, `MenuItemIconWithGripSwap`, `NavigationBarItem` - `JsonArrow`, `JsonNestedNode` - `ModalHeader` ### Other - Added `aria-valuenow` to `ProgressBar` for accessibility - `VisibilityHidden` component updated to inline accessibility styles
This commit is contained in:
@@ -7,7 +7,6 @@ import { JsonNodeValue } from '@ui/json-visualizer/components/internal/JsonNodeV
|
||||
import { JsonNode } from '@ui/json-visualizer/components/JsonNode';
|
||||
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
||||
import { type JsonNodeHighlighting } from '@ui/json-visualizer/types/JsonNodeHighlighting';
|
||||
import { ANIMATION } from '@ui/theme';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useState } from 'react';
|
||||
@@ -50,8 +49,6 @@ const StyledJsonListBase = styled.ul<{
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledJsonList = motion.create(StyledJsonListBase);
|
||||
|
||||
export const JsonNestedNode = ({
|
||||
label,
|
||||
Icon,
|
||||
@@ -80,45 +77,34 @@ export const JsonNestedNode = ({
|
||||
);
|
||||
|
||||
const renderedChildren = (
|
||||
<StyledJsonList
|
||||
initial={{
|
||||
height: 0,
|
||||
opacity: 0,
|
||||
overflowY: 'clip',
|
||||
}}
|
||||
animate={{
|
||||
height: 'auto',
|
||||
opacity: 1,
|
||||
overflowY: 'clip',
|
||||
}}
|
||||
exit={{
|
||||
height: 0,
|
||||
opacity: 0,
|
||||
overflowY: 'clip',
|
||||
}}
|
||||
transition={{ duration: ANIMATION.duration.normal }}
|
||||
depth={depth}
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0, overflow: 'clip' }}
|
||||
animate={{ height: 'auto', opacity: 1, overflow: 'clip' }}
|
||||
exit={{ height: 0, opacity: 0, overflow: 'clip' }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
{elements.length === 0 ? (
|
||||
<JsonNodeValue valueAsString={emptyElementsText} />
|
||||
) : (
|
||||
elements.map(({ id, label, value }) => {
|
||||
const nextKeyPath = isNonEmptyString(keyPath)
|
||||
? `${keyPath}.${id}`
|
||||
: String(id);
|
||||
<StyledJsonListBase depth={depth}>
|
||||
{elements.length === 0 ? (
|
||||
<JsonNodeValue valueAsString={emptyElementsText} />
|
||||
) : (
|
||||
elements.map(({ id, label, value }) => {
|
||||
const nextKeyPath = isNonEmptyString(keyPath)
|
||||
? `${keyPath}.${id}`
|
||||
: String(id);
|
||||
|
||||
return (
|
||||
<JsonNode
|
||||
key={id}
|
||||
label={label}
|
||||
value={value}
|
||||
depth={depth + 1}
|
||||
keyPath={nextKeyPath}
|
||||
/>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</StyledJsonList>
|
||||
return (
|
||||
<JsonNode
|
||||
key={id}
|
||||
label={label}
|
||||
value={value}
|
||||
depth={depth + 1}
|
||||
keyPath={nextKeyPath}
|
||||
/>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</StyledJsonListBase>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
const handleArrowClick = () => {
|
||||
|
||||
@@ -2,10 +2,8 @@ import { styled } from '@linaria/react';
|
||||
import { VisibilityHidden } from '@ui/accessibility';
|
||||
import { IconChevronDown } from '@ui/display';
|
||||
import { useJsonTreeContextOrThrow } from '@ui/json-visualizer/hooks/useJsonTreeContextOrThrow';
|
||||
import { ANIMATION, ThemeContext } from '@ui/theme';
|
||||
import { themeCssVariables } from '@ui/theme-constants';
|
||||
import { ICON_SIZES, themeCssVariables } from '@ui/theme-constants';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useContext } from 'react';
|
||||
|
||||
const StyledButton = styled.button<{
|
||||
variant?: 'blue' | 'red';
|
||||
@@ -31,8 +29,6 @@ const StyledButton = styled.button<{
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const MotionIconChevronDown = motion.create(IconChevronDown);
|
||||
|
||||
export const JsonArrow = ({
|
||||
isOpen,
|
||||
onClick,
|
||||
@@ -42,30 +38,29 @@ export const JsonArrow = ({
|
||||
onClick: () => void;
|
||||
variant?: 'blue' | 'red';
|
||||
}) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
const { arrowButtonCollapsedLabel, arrowButtonExpandedLabel } =
|
||||
useJsonTreeContextOrThrow();
|
||||
|
||||
const iconColor =
|
||||
variant === 'blue'
|
||||
? themeCssVariables.color.blue
|
||||
: variant === 'red'
|
||||
? themeCssVariables.font.color.danger
|
||||
: themeCssVariables.font.color.secondary;
|
||||
|
||||
return (
|
||||
<StyledButton variant={variant} onClick={onClick}>
|
||||
<VisibilityHidden>
|
||||
{isOpen ? arrowButtonExpandedLabel : arrowButtonCollapsedLabel}
|
||||
</VisibilityHidden>
|
||||
|
||||
<MotionIconChevronDown
|
||||
size={theme.icon.size.md}
|
||||
color={
|
||||
variant === 'blue'
|
||||
? theme.color.blue
|
||||
: variant === 'red'
|
||||
? theme.font.color.danger
|
||||
: theme.font.color.secondary
|
||||
}
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={{ rotate: isOpen ? 0 : -90 }}
|
||||
transition={{ duration: ANIMATION.duration.normal }}
|
||||
/>
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<IconChevronDown size={ICON_SIZES.md} color={iconColor} />
|
||||
</motion.div>
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user