Last active
March 5, 2023 01:52
-
-
Save kyo-ago/7d2a5332bb06428a52c2bb17862bc299 to your computer and use it in GitHub Desktop.
react-router v5 to v6 upgrade code-mod
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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