Skip to content

Instantly share code, notes, and snippets.

@sskoopa
Last active February 9, 2022 11:54
Show Gist options
  • Save sskoopa/ba106555779f02b44d33b3b12a2178f4 to your computer and use it in GitHub Desktop.
Save sskoopa/ba106555779f02b44d33b3b12a2178f4 to your computer and use it in GitHub Desktop.
react -> preact codemod
// use via
// jscodeshift -t all.js src
//
const sourceOptions = { quote: 'single' }
/**
* Removes the named import: PropTypes
*/
const removeNamedImportPropTypes = (file, api) => {
const j = api.jscodeshift
const root = j(file.source)
root
.find(j.ImportSpecifier)
.filter(n => n.value.imported.name === 'PropTypes')
.remove()
root
.find(j.ImportDeclaration)
.filter(n => n.value.source.value.indexOf('react-prop-types') > -1)
.remove()
return root.toSource(sourceOptions)
}
/**
* Removes unnecessary empty import declarations
*/
const removeEmptyImports = (file, api) => {
const j = api.jscodeshift
const root = j(file.source)
root
.find(j.ImportDeclaration)
.filter(n => !n.value.specifiers.length)
.remove()
return root.toSource(sourceOptions)
}
/**
* Removes all propTypes assignments
*/
const removePropTypesAssignments = (file, api) => {
const j = api.jscodeshift
const root = j(file.source)
root
.find(j.AssignmentExpression)
.filter(n => n.value.left.property && n.value.left.property.name === 'propTypes')
.remove()
return root.toSource(sourceOptions)
}
/**
* Removes all propTypes declarations
*/
const removePropTypesDeclarations = (file, api) => {
const j = api.jscodeshift
const root = j(file.source)
root
.findVariableDeclarators('propTypes')
.remove()
return root.toSource(sourceOptions)
}
/**
* Removes all propTypes assignments
*/
const removeContextTypesAssignments = (file, api) => {
const j = api.jscodeshift
const root = j(file.source)
root
.find(j.AssignmentExpression)
.filter(n => n.value.left.property && n.value.left.property.name === 'contextTypes')
.remove()
root
.find(j.AssignmentExpression)
.filter(n => n.value.left.property && n.value.left.property.name === 'childContextTypes')
.remove()
return root.toSource(sourceOptions)
}
/**
* Removes all propTypes declarations
*/
const removeContextTypesDeclarations = (file, api) => {
const j = api.jscodeshift
const root = j(file.source)
root
.findVariableDeclarators('contextTypes')
.remove()
root
.findVariableDeclarators('childContextTypes')
.remove()
return root.toSource(sourceOptions)
}
/**
* Creates and returns an import declaration
*
* @param {String} source - The source module
* @param {String[]} values - The named values to import
* @return {Node}
*/
const importDeclaration = (source, values) => {
const node = {
type: 'ImportDeclaration',
importKind: 'value',
source: { type: 'Literal', value: source },
specifiers: [],
comments: [{
type: 'CommentBlock',
value: '* @jsx h',
leading: false,
trailing: true
}]
}
values.forEach(v => {
node.specifiers.push({
type: 'ImportSpecifier',
imported: { type: 'Identifier', name: v },
local: { type: 'Identifier', name: v }
})
})
return node
}
/**
* Transforms React imports to Preact imports
*/
const updateImports = (file, api) => {
const j = api.jscodeshift
const root = j(file.source)
const namedImports = new Set(['h'])
// should import preact.Component?
root
.find(j.MemberExpression)
.filter(n => n.value.object.name === 'React' && n.value.property.name === 'createClass')
.forEach(n => {
namedImports.add('Component')
})
root
.find(j.MemberExpression)
.filter(n => n.value.object.name === 'React' && n.value.property.name === 'Component')
.forEach(n => {
namedImports.add('Component')
})
.replaceWith(nodePath => {
// extends React.Component becomes just Component
return j.identifier('Component')
})
// should import preact.render?
root
.find(j.MemberExpression)
.filter(n => n.value.object && n.value.object.name === 'ReactDOM' && n.value.property.name === 'render')
.forEach(n => {
namedImports.add('render')
})
root
.find(j.Property)
.filter(n => n.key && n.key.name === 'render')
.forEach(n => {
namedImports.add('render')
})
// remove ReactDOM
root
.findVariableDeclarators('React')
.closest(j.VariableDeclaration)
.remove()
// remove ReactDOM
root
.findVariableDeclarators('ReactDOM')
.closest(j.VariableDeclaration)
.remove()
root.find(j.ImportDeclaration, {
source: {
type: 'Literal',
value: 'react'
}
})
.remove()
// console.log('namedImports.size =', namedImports.size)
if (namedImports.size > 1) {
const preactImports = root.find(j.ImportDeclaration, {
source: {
type: 'Literal',
value: 'preact'
}
})
// console.log('preactImports.size =', preactImports.size())
if (preactImports.size() === 0) {
const body = root.get().value.program.body
body.unshift(importDeclaration('preact', namedImports))
}
}
return root.toSource(sourceOptions)
}
/**
* Transforms React imports to Preact imports
*/
const fixChildren = (file, api) => {
const j = api.jscodeshift
const root = j(file.source)
root
.find(j.MemberExpression)
.filter(n => n.value.object.name === 'React' && n.value.property.name === 'Children')
.replaceWith(nodePath => {
// extends React.Component becomes just Component
return j.identifier('children')
})
.closest(j.CallExpression)
.find(j.Identifier)
.filter(n => n.value.name === 'children' && n.parent.value.type === 'CallExpression')
.remove()
return root.toSource(sourceOptions)
}
module.exports = (file, api) => {
let source = file.source
source = removeNamedImportPropTypes({ source }, api)
source = removePropTypesAssignments({ source }, api)
source = removePropTypesDeclarations({ source }, api)
source = removeContextTypesAssignments({ source }, api)
source = removeContextTypesDeclarations({ source }, api)
source = updateImports({ source }, api)
source = fixChildren({ source }, api)
source = removeEmptyImports({ source }, api)
return source
}
// root
// .findVariableDeclarators('defaultProps')
// .closest(j.VariableDeclaration)
// .forEach(n => {
// console.log('found ', n.value)
// })
// .replaceWith(nodePath => {
// const { node } = nodePath
// // node.id.name = 'scott'
// return node
// })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment