Skip to content

Instantly share code, notes, and snippets.

@rozsival
Last active January 30, 2025 12:05
Show Gist options
  • Save rozsival/99c261ba90263ab67e28c3128589bb76 to your computer and use it in GitHub Desktop.
Save rozsival/99c261ba90263ab67e28c3128589bb76 to your computer and use it in GitHub Desktop.
MUI `Grid2` Codemod

Codemod to convert deprecated MUI Grid component usage to Grid2. See https://mui.com/material-ui/migration/migration-grid-v2/ for more information.

This codemod works with tsx files and:

  1. Locates all Grid imports
  2. Transforms them to Grid2 as Grid
  3. Removes item prop if previously assigned to Grid
  4. Transforms all responsive props (xs, sm etc.) to new size prop.

Usage:

Copy mui-grid2-codemod.ts to your TSX project (e.g. /scripts folder), then run:

npm install --save-dev jscodeshift @types/jscodeshit ast-types
npx jscodeshift -t scripts/mui-grid2-codemod.ts --extensions=tsx --parser=tsx src/
npx prettier --write src/**/*
npm uninstall jscodeshift @types/jscodeshift ast-types
import type { ExpressionKind } from 'ast-types/gen/kinds';
import type { API, FileInfo, JSXAttribute, ObjectProperty } from 'jscodeshift';
export default function transformer(file: FileInfo, api: API) {
const index_ = api.jscodeshift;
const root = index_(file.source);
// Step 1: Transform JSX Grid props
root.findJSXElements('Grid').forEach((path) => {
const attributes = path.node.openingElement.attributes ?? [];
const newAttributes: JSXAttribute[] = [];
const sizeProps: ObjectProperty[] = [];
attributes.forEach((attribute) => {
if (attribute.type === 'JSXAttribute' && attribute.name.name) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
const name = attribute.name.name.toString();
// Remove `item` prop
if (name === 'item') {
return;
}
// Move responsive props to `sizes` object
if (['xs', 'sm', 'md', 'lg', 'xl'].includes(name)) {
if (attribute.value && attribute.value.type === 'JSXExpressionContainer') {
sizeProps.push(
index_.objectProperty(index_.identifier(name), attribute.value.expression as ExpressionKind),
);
}
} else {
newAttributes.push(attribute);
}
}
});
// Add `size={{ xs: ..., sm: ... }}` if any responsive props exist
if (sizeProps.length > 0) {
newAttributes.push(
index_.jsxAttribute(
index_.jsxIdentifier('size'),
index_.jsxExpressionContainer(index_.objectExpression(sizeProps)),
),
);
}
// Replace old attributes with new ones
path.node.openingElement.attributes = newAttributes;
});
// Step 2: Update imports from @mui/material
root.find(index_.ImportDeclaration).forEach((importPath) => {
const importNode = importPath.node;
if (importNode.source.value === '@mui/material') {
const specifiers = importNode.specifiers ?? [];
let hasGrid = false as boolean;
specifiers.forEach((specifier, index) => {
if (specifier.type === 'ImportSpecifier' && specifier.imported.name === 'Grid') {
hasGrid = true;
// Replace "Grid" with "Grid2 as Grid"
specifiers[index] = index_.importSpecifier(index_.identifier('Grid2'), index_.identifier('Grid'));
}
});
// Replace the specifiers with the updated version
if (hasGrid) {
importNode.specifiers = specifiers;
}
}
});
return root.toSource();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment