Skip to content

Instantly share code, notes, and snippets.

@NoahCardoza
Created March 14, 2025 21:42
Show Gist options
  • Save NoahCardoza/4897e2d49de5998f032df289df4844e5 to your computer and use it in GitHub Desktop.
Save NoahCardoza/4897e2d49de5998f032df289df4844e5 to your computer and use it in GitHub Desktop.
tss/makeStyles vs MUI sx prop
import React, { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
import { Box, Button, Typography, Paper } from '@mui/material';
import { makeStyles } from 'tss-react/mui';
import { Profiler } from 'react';
import { sum } from 'lodash';
// Number of components to render in each test
const COMPONENT_COUNT = 1000;
// Number of update cycles to run
const UPDATE_CYCLES = 500;
// Number of warm-up cycles before measuring
const WARMUP_CYCLES = 50;
// Performance metrics storage
const metrics = {
tss: {
renderTimes: [],
memoryUsage: [],
isWarmup: true,
},
sx: {
renderTimes: [],
memoryUsage: [],
isWarmup: true,
}
};
// TSS make styles implementation
const useStyles = makeStyles()((theme) => ({
root: {
display: 'flex',
flexDirection: 'column',
padding: theme.spacing(2),
margin: theme.spacing(1),
backgroundColor: theme.palette.background.paper,
borderRadius: theme.shape.borderRadius,
boxShadow: theme.shadows[1],
'&:hover': {
boxShadow: theme.shadows[3],
},
},
title: {
color: theme.palette.primary.main,
fontWeight: 'bold',
marginBottom: theme.spacing(1),
},
content: {
color: theme.palette.text.secondary,
},
}));
// Component using TSS make styles
const TSSStyledComponent = ({ index, updateCounter }) => {
const { classes } = useStyles();
return (
<Paper className={classes.root}>
<Typography className={classes.title}>
TSS Item #{index} (Update: {updateCounter})
</Typography>
<Typography className={classes.content}>
This component uses TSS make styles for styling
</Typography>
</Paper>
);
};
// Component using sx prop
const SXStyledComponent = ({ index, updateCounter }) => {
return (
<Paper
sx={{
display: 'flex',
flexDirection: 'column',
padding: 2,
margin: 1,
bgcolor: 'background.paper',
borderRadius: 'shape.borderRadius',
boxShadow: 1,
'&:hover': {
boxShadow: 3,
},
}}
>
<Typography
sx={{
color: 'primary.main',
fontWeight: 'bold',
mb: 1,
}}
>
SX Item #{index} (Update: {updateCounter})
</Typography>
<Typography
sx={{
color: 'text.secondary',
}}
>
This component uses the sx prop for styling
</Typography>
</Paper>
);
};
// Test container for TSS components
const TSSComponentsTest = ({ updateCounter }) => {
return (
<Profiler id="tss-components" onRender={onRenderCallback}>
<Box>
{Array.from({ length: COMPONENT_COUNT }).map((_, index) => (
<TSSStyledComponent
key={index}
index={index}
updateCounter={updateCounter}
/>
))}
</Box>
</Profiler>
);
};
// Test container for SX components
const SXComponentsTest = ({ updateCounter }) => {
return (
<Profiler id="sx-components" onRender={onRenderCallback}>
<Box>
{Array.from({ length: COMPONENT_COUNT }).map((_, index) => (
<SXStyledComponent
key={index}
index={index}
updateCounter={updateCounter}
/>
))}
</Box>
</Profiler>
);
};
// Statistical helper functions
const calculateMedian = (values) => {
const sorted = [...values].sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
return sorted.length % 2 === 0
? (sorted[middle - 1] + sorted[middle]) / 2
: sorted[middle];
};
const calculateStdDev = (values, mean) => {
if (values.length <= 1) return 0;
const variance = values.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / (values.length - 1);
return Math.sqrt(variance);
};
// TSS make styles implementation and component definitions remain the same
// ...existing code...
// Modified Profiler callback to handle warm-up phase
function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) {
// Skip recording metrics during warmup phase
if ((id === 'tss-components' && metrics.tss.isWarmup) ||
(id === 'sx-components' && metrics.sx.isWarmup)) {
return;
}
if (id === 'tss-components') {
metrics.tss.renderTimes.push(actualDuration);
} else if (id === 'sx-components') {
metrics.sx.renderTimes.push(actualDuration);
}
// Collect memory usage if possible
if (window.performance && window.performance.memory) {
const memory = window.performance.memory.usedJSHeapSize / (1024 * 1024);
if (id === 'tss-components') {
metrics.tss.memoryUsage.push(memory);
} else if (id === 'sx-components') {
metrics.sx.memoryUsage.push(memory);
}
}
}
// Calculate and format results with added statistics
const calculateResults = (setResults) => {
// Average calculations
const tssAvgRenderTime = metrics.tss.renderTimes.reduce((a, b) => a + b, 0) / metrics.tss.renderTimes.length;
const sxAvgRenderTime = metrics.sx.renderTimes.reduce((a, b) => a + b, 0) / metrics.sx.renderTimes.length;
// Min/Max calculations
const tssMaxRenderTime = Math.max(...metrics.tss.renderTimes);
const sxMaxRenderTime = Math.max(...metrics.sx.renderTimes);
const tssMinRenderTime = Math.min(...metrics.tss.renderTimes);
const sxMinRenderTime = Math.min(...metrics.sx.renderTimes);
// Median calculations
const tssMedianRenderTime = calculateMedian(metrics.tss.renderTimes);
const sxMedianRenderTime = calculateMedian(metrics.sx.renderTimes);
// Standard deviation calculations
const tssStdDevRenderTime = calculateStdDev(metrics.tss.renderTimes, tssAvgRenderTime);
const sxStdDevRenderTime = calculateStdDev(metrics.sx.renderTimes, sxAvgRenderTime);
// Memory calculations
let tssAvgMemory = 0;
let sxAvgMemory = 0;
if (metrics.tss.memoryUsage.length > 0 && metrics.sx.memoryUsage.length > 0) {
tssAvgMemory = (sum(metrics.tss.memoryUsage) / metrics.tss.memoryUsage.length)
sxAvgMemory = (sum(metrics.sx.memoryUsage) / metrics.sx.memoryUsage.length)
}
// Percentage differences (fixed maxPercentDifference calculation)
const minPercentDifference = ((sxMinRenderTime - tssMinRenderTime) / sxMinRenderTime * 100).toFixed(2);
const averagePercentDifference = ((sxAvgRenderTime - tssAvgRenderTime) / sxAvgRenderTime * 100).toFixed(2);
const maxPercentDifference = ((sxMaxRenderTime - tssMaxRenderTime) / sxMaxRenderTime * 100).toFixed(2);
const medianPercentDifference = ((sxMedianRenderTime - tssMedianRenderTime) / sxMedianRenderTime * 100).toFixed(2);
const memoryPercentDifference = ((sxAvgMemory - tssAvgMemory) / sxAvgMemory * 100).toFixed(2);
setResults({
tss: {
avgRenderTime: tssAvgRenderTime.toFixed(2),
maxRenderTime: tssMaxRenderTime.toFixed(2),
minRenderTime: tssMinRenderTime.toFixed(2),
medianRenderTime: tssMedianRenderTime.toFixed(2),
stdDevRenderTime: tssStdDevRenderTime.toFixed(2),
avgMemory: tssAvgMemory.toFixed(2),
},
sx: {
avgRenderTime: sxAvgRenderTime.toFixed(2),
maxRenderTime: sxMaxRenderTime.toFixed(2),
minRenderTime: sxMinRenderTime.toFixed(2),
medianRenderTime: sxMedianRenderTime.toFixed(2),
stdDevRenderTime: sxStdDevRenderTime.toFixed(2),
avgMemory: sxAvgMemory.toFixed(2),
},
comparison: {
minPercentDifference,
averagePercentDifference,
maxPercentDifference,
medianPercentDifference,
memoryPercentDifference,
}
});
};
// Benchmark App
const BenchmarkApp = () => {
const [testState, setTestState] = useState('idle');
// States: idle, warmup-tss, warmup-sx, running-tss, running-sx, complete
const [updateCounter, setUpdateCounter] = useState(0);
const [results, setResults] = useState(null);
const [sxFirst] = useState(() => Math.random() >= 0.5); // Randomize test order
// Run update cycles
useEffect(() => {
let updateInterval;
let cycleCount = 0;
const isWarmupPhase = testState === 'warmup-tss' || testState === 'warmup-sx';
const isRunningPhase = testState === 'running-tss' || testState === 'running-sx';
if (isWarmupPhase || isRunningPhase) {
const maxCycles = isWarmupPhase ? WARMUP_CYCLES : UPDATE_CYCLES;
updateInterval = setInterval(() => {
setUpdateCounter(prev => prev + 1);
cycleCount++;
if (cycleCount >= maxCycles) {
clearInterval(updateInterval);
if (testState === 'warmup-tss') {
// Mark warmup complete and start collecting real metrics
metrics.tss.isWarmup = false;
setUpdateCounter(0);
setTestState('running-tss');
}
else if (testState === 'warmup-sx') {
// Mark warmup complete and start collecting real metrics
metrics.sx.isWarmup = false;
setUpdateCounter(0);
setTestState('running-sx');
}
else if (testState === 'running-tss') {
// Move to the next test
setTimeout(() => {
if (sxFirst) {
setTestState('complete');
calculateResults(setResults);
} else {
metrics.sx.isWarmup = true;
setUpdateCounter(0);
setTestState('warmup-sx');
}
}, 1000);
}
else if (testState === 'running-sx') {
setTimeout(() => {
if (sxFirst) {
metrics.tss.isWarmup = true;
setUpdateCounter(0);
setTestState('warmup-tss');
} else {
setTestState('complete');
calculateResults(setResults);
}
}, 1000);
}
}
}, 50); // Update every 50ms
}
return () => {
if (updateInterval) clearInterval(updateInterval);
};
}, [testState]);
const startBenchmark = () => {
// Reset metrics
metrics.tss.renderTimes = [];
metrics.tss.memoryUsage = [];
metrics.sx.renderTimes = [];
metrics.sx.memoryUsage = [];
metrics.tss.isWarmup = true;
metrics.sx.isWarmup = true;
setUpdateCounter(0);
setResults(null);
// Start with either TSS or SX based on randomization
const firstTest = sxFirst ? 'warmup-sx' : 'warmup-tss';
setTestState(firstTest);
};
return (
<Box sx={{ p: 4, maxWidth: 800, mx: 'auto' }}>
<Typography variant="h4" gutterBottom>
MUI Styling Benchmark: sx prop vs. TSS make styles
</Typography>
<Typography variant="body1" paragraph>
This benchmark renders {COMPONENT_COUNT} components and updates them {UPDATE_CYCLES} times to measure performance differences.
{sxFirst ? " Running SX first, then TSS." : " Running TSS first, then SX."}
</Typography>
{(testState === 'idle' || testState === 'complete') && (
<Button
variant="contained"
color="primary"
onClick={startBenchmark}
sx={{ mb: 4 }}
>
Start Benchmark
</Button>
)}
{testState === 'warmup-tss' && (
<>
<Typography variant="h6">Warming up TSS make styles...</Typography>
<Typography variant="body2">Warm-up cycle: {updateCounter}/{WARMUP_CYCLES}</Typography>
<TSSComponentsTest updateCounter={updateCounter} />
</>
)}
{testState === 'running-tss' && (
<>
<Typography variant="h6">Running TSS make styles test...</Typography>
<Typography variant="body2">Update cycle: {updateCounter}/{UPDATE_CYCLES}</Typography>
<TSSComponentsTest updateCounter={updateCounter} />
</>
)}
{testState === 'warmup-sx' && (
<>
<Typography variant="h6">Warming up SX prop...</Typography>
<Typography variant="body2">Warm-up cycle: {updateCounter}/{WARMUP_CYCLES}</Typography>
<SXComponentsTest updateCounter={updateCounter} />
</>
)}
{testState === 'running-sx' && (
<>
<Typography variant="h6">Running SX prop test...</Typography>
<Typography variant="body2">Update cycle: {updateCounter}/{UPDATE_CYCLES}</Typography>
<SXComponentsTest updateCounter={updateCounter} />
</>
)}
{results && (
<Paper sx={{ p: 3, mt: 3 }}>
<Typography variant="h5" gutterBottom>Results</Typography>
<Box sx={{ display: 'flex', gap: 4, mb: 3 }}>
<Box>
<Typography variant="h6">TSS make styles</Typography>
<Typography>Min Render Time: {results.tss.minRenderTime} ms</Typography>
<Typography>Median Render Time: {results.tss.medianRenderTime} ms</Typography>
<Typography>Avg Render Time: {results.tss.avgRenderTime} ms</Typography>
<Typography>Max Render Time: {results.tss.maxRenderTime} ms</Typography>
<Typography>Std Dev: {results.tss.stdDevRenderTime} ms</Typography>
<Typography>Avg Memory Usage: {results.tss.avgMemory} MB</Typography>
</Box>
<Box>
<Typography variant="h6">SX prop</Typography>
<Typography>Min Render Time: {results.sx.minRenderTime} ms</Typography>
<Typography>Median Render Time: {results.sx.medianRenderTime} ms</Typography>
<Typography>Avg Render Time: {results.sx.avgRenderTime} ms</Typography>
<Typography>Max Render Time: {results.sx.maxRenderTime} ms</Typography>
<Typography>Std Dev: {results.sx.stdDevRenderTime} ms</Typography>
<Typography>Avg Memory Usage: {results.sx.avgMemory} MB</Typography>
</Box>
</Box>
<Typography variant="h6">Comparing TSS/makeStyles vs MUI's SX</Typography>
<Typography>
Min: {results.comparison.minPercentDifference}%
</Typography>
<Typography>
Median: {results.comparison.medianPercentDifference}%
</Typography>
<Typography>
Average: {results.comparison.averagePercentDifference}%
</Typography>
<Typography>
Max: {results.comparison.maxPercentDifference}%
</Typography>
<Typography>
Memory: {results.comparison.memoryPercentDifference}%
</Typography>
<Typography variant="body2" color="textSecondary" sx={{ mt: 2 }}>
Note: Memory usage may not be accurate in all browsers. Positive percentages indicate TSS is faster/uses less memory.
</Typography>
</Paper>
)}
</Box>
);
};
// Render the benchmark app
const root = createRoot(document.getElementById('app'));
root.render(<BenchmarkApp />);
// Export a function to run the benchmark programmatically
export const runBenchmark = () => {
const app = document.createElement('div');
app.id = 'benchmark-root';
document.body.appendChild(app);
const benchmarkRoot = createRoot(app);
benchmarkRoot.render(<BenchmarkApp />);
return {
cleanup: () => {
benchmarkRoot.unmount();
app.remove();
}
};
};
@NoahCardoza
Copy link
Author

Results from a incognito Brave Browser window with no extensions enabled.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment