Skip to content

Instantly share code, notes, and snippets.

@kyo-ago
Last active March 5, 2023 01:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kyo-ago/7d2a5332bb06428a52c2bb17862bc299 to your computer and use it in GitHub Desktop.
Save kyo-ago/7d2a5332bb06428a52c2bb17862bc299 to your computer and use it in GitHub Desktop.
react-router v5 to v6 upgrade code-mod
import {Transform} from "jscodeshift";
const transform: Transform = (fileInfo, api, options) => {
const j = api.jscodeshift;
const root = j(fileInfo.source);
root
.find(j.ImportDeclaration)
.filter(path => path.node.source.value === 'react-router' || path.node.source.value === 'react-router-dom')
.find(j.ImportSpecifier)
.filter(path => path.node.imported.name === 'useHistory')
.replaceWith(() =>
j.importSpecifier(j.identifier("useNavigate"))
)
;
root
.find(j.VariableDeclarator, {
init: {
type: "CallExpression",
callee: { name: "useHistory" }
}
})
.replaceWith(() =>
j.variableDeclarator(
j.identifier("navigate"),
j.callExpression(j.identifier("useNavigate"), [])
)
);
root
.find(j.CallExpression, {
callee: {
type: "MemberExpression",
object: { name: "history" },
property: { name: "replace" }
},
})
.replaceWith(({ node }) =>
j.callExpression(j.identifier("navigate"), [
node.arguments[0],
j.objectExpression([j.property("init", j.identifier("replace"), j.literal(true))])
])
);
root
.find(j.CallExpression, {
callee: {
object: { name: 'history' },
property: { name: 'push' }
},
})
.replaceWith(({node}) =>
j.callExpression(j.identifier('navigate'), [node.arguments[0]])
);
root
.find(j.CallExpression, {
callee: {
object: { name: 'history' },
property: { name: 'goBack' }
}
})
.replaceWith(() => j.callExpression(j.identifier('navigate'), [j.literal(-1)]));
root
.find(j.Identifier, { name: "history" })
.filter(path => {
// ignore `window.history.replaceState`
const parent = path.parent.node;
if (parent.type !== 'MemberExpression') {
return true;
}
if (parent.object.name !== 'window') {
return true;
}
if (parent.property.name !== 'history') {
return true;
}
if (parent.computed) {
return true;
}
return false;
})
.replaceWith(j.identifier("navigate"));
root
.find(j.ImportDeclaration)
.filter(path => path.node.source.value === 'react-router' || path.node.source.value === 'react-router-dom')
.find(j.ImportSpecifier)
.filter(path => path.node.imported.name === 'Redirect')
.replaceWith(() =>
j.importSpecifier(j.identifier("Navigate"))
)
;
root
.find(j.JSXElement, {
openingElement: {
name: { name: 'Redirect' },
}
})
.replaceWith(({node}) => {
const elem = j.jsxElement(
j.jsxOpeningElement(j.jsxIdentifier('Navigate'), [
...node.openingElement.attributes,
j.jsxAttribute(j.jsxIdentifier('replace'))
]),
node.closingElement,
node.children
);
elem.openingElement.selfClosing = node.openingElement.selfClosing;
return elem;
});
root.find(j.JSXOpeningElement, {
name: { name: 'Route' },
})
.replaceWith(({node}) => {
const exact = node.attributes.find(attr => attr.type === "JSXAttribute" && attr.name.name === 'exact');
node.attributes = node.attributes.filter(attr => attr.type === "JSXAttribute" && attr.name.name !== 'exact');
node.attributes.forEach(attr => {
if (attr.type !== "JSXAttribute") {
return;
}
if (attr.name.name !== 'path') {
return;
}
if (attr.value.type !== "StringLiteral") {
throw new Error(`path props: ${attr.value.type}`)
}
if (exact) {
return;
}
if (attr.value.value !== "*") {
return;
}
attr.value.value = `${attr.value.value}*`;
});
node.attributes.forEach(attr => {
if (attr.type !== "JSXAttribute") {
return;
}
if (attr.name.name !== 'component') {
return;
}
if (attr.value.type !== "JSXExpressionContainer") {
throw new Error(`component props: ${attr.value.type}`)
}
attr.name.name = 'element';
if (attr.value.expression.type === "Identifier") {
const elem = j.jsxOpeningElement(j.jsxIdentifier(attr.value.expression.name), []);
elem.selfClosing = true;
attr.value = j.jsxExpressionContainer(j.jsxElement(elem, null, []));
} else if (attr.value.expression.type !== "CallExpression") {
throw new Error(`component expression props: ${attr.value.expression.type}`)
}
});
node.attributes.forEach(attr => {
if (attr.type !== "JSXAttribute") {
return;
}
if (attr.name.name !== 'render') {
return;
}
attr.name.name = 'children';
});
return node;
});
root
.find(j.ImportDeclaration)
.filter(path => path.node.source.value === 'react-router' || path.node.source.value === 'react-router-dom')
.find(j.ImportSpecifier)
.filter(path => path.node.imported.name === 'Switch')
.replaceWith(() =>
j.importSpecifier(j.identifier("Routes"))
)
;
root.find(j.JSXOpeningElement, {
name: {
name: 'Switch'
}
}).replaceWith(({node}) => {
(node.name as any).name = 'Routes';
return node;
});
root.find(j.JSXClosingElement, {
name: {
name: 'Switch'
}
}).replaceWith(({node}) => {
(node.name as any).name = 'Routes';
return node;
});
return root.toSource();
};
export default transform;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment