Skip to content

Instantly share code, notes, and snippets.

@scurker
Last active February 9, 2022 11:50
Show Gist options
  • Save scurker/56b10e32e2fceaeb8fda4b0ba1f5db8d to your computer and use it in GitHub Desktop.
Save scurker/56b10e32e2fceaeb8fda4b0ba1f5db8d to your computer and use it in GitHub Desktop.
Convert `DOM.div` element factories from `react-dom-factories` to `React.createElement('div', ...)`

React DOM Factories to React.createElement

A jscodeshift codemod

In

import DOM from 'react-dom-factories';

export default () => {
  return DOM.div({ className: 'foo' }, 'Hello World');
}

Out

import React from 'react';

export default () => {
  return React.createElement('div', { className: 'foo' }, 'Hello World');
}

Usage

> npx jscodeshift -t react-dom-factories-to-react-create-element.js path/to/files
export default function transformer(file, api, options) {
const j = api.jscodeshift;
const root = j(file.source);
let hasModifications = false;
const reactDomFactoriesModuleName = 'react-dom-factories';
const findReact = (j, root) => {
// Check for import
// i.e. `import React from 'react'`
let hasReactImport = Array.from(
root.find(j.ImportDeclaration, {
source: {
value: 'react'
}
}).__paths
)
.find(({ node }) => {
return node.specifiers[0].local.name;
});
hasReactImport = hasReactImport && hasReactImport.node.specifiers[0].local.name;
// Check for require
// i.e. `const React = require('react')`
let hasReactRequire = Array.from(
root.find(j.VariableDeclarator, {
init: {
type: 'CallExpression',
callee: {
name: 'require'
}
}
}).__paths
)
.find(({ node }) => {
return node.init.arguments[0].value === 'react' && node.id.name;
});
hasReactRequire = hasReactRequire && hasReactRequire.node.id.name;
return hasReactImport || hasReactRequire;
}
const hasReact = findReact(j, root);
const replaceDOMFactoriesImport = (j, root) => {
// Check for import
// i.e. `import DOM from 'react-dom-factories'`
// and replace with React if it does not exist
// i.e. `import React from 'react';
const domFactoriesImport = root
.find(j.ImportDeclaration, {
source: {
value: reactDomFactoriesModuleName
}
});
if(!hasReact) {
// import DOM from 'react-dom-factories' -> import React from 'react'
domFactoriesImport.insertBefore(
j.importDeclaration(
[j.importDefaultSpecifier(
j.identifier('React')
)],
j.literal('react')
)
)
}
domFactoriesImport.remove();
return domFactoriesImport.length;
};
const replaceDOMFactories = (j, root) => {
let hasModifications = false;
const replaceElementFactory = (name) => {
return root
.find(j.CallExpression, {
callee: {
name: name
}
})
.replaceWith(path => {
// Set element as first argument for call expression, i.e. div(... -> 'div'
path.value.arguments.unshift(j.literal(name));
// div(...) -> React.createElement('div', ...)
return j.callExpression(
j.memberExpression(
j.identifier(hasReact || 'React'),
j.identifier('createElement')
),
path.value.arguments
);
});
};
// Find DOM identifier from 'react-dom-factories'
let domFactories = root
.find(j.ImportDeclaration, {
source: {
value: reactDomFactoriesModuleName
}
})
.find(j.Identifier);
let domFactoriesIdentifier = domFactories.length ? domFactories.get(0).node.name : 'DOM';
// Check for any destructuring
// i.e. const { div } = DOM
const elementFactories = root
.find(j.VariableDeclarator, {
init: {
name: domFactoriesIdentifier
}
})
.find(j.Identifier)
.filter(path =>
path.parentPath.parentPath.node.type === 'ObjectPattern' && path.name === 'key'
)
.forEach(({ node }) => {
hasModifications = !!replaceElementFactory(node.name).length;
});
// Check for destructured element factory from 'react-dom-factories'
// i.e. import { div } from 'react-dom-factories'
root
.find(j.ImportDeclaration, {
source: {
value: reactDomFactoriesModuleName
}
})
.find(j.Identifier)
.forEach(({ node }) => {
hasModifications = !!replaceElementFactory(node.name).length;
});
// Check for DOM factories
// i.e. DOM.div(...
root
.find(j.CallExpression, {
callee: {
object: {
type: 'Identifier',
name: domFactoriesIdentifier
}
}
})
.replaceWith(path => {
hasModifications = true;
// Set element as first argument for call expression, i.e. DOM.div(... -> 'div'
path.value.arguments.unshift(j.literal(path.node.callee.property.name));
// DOM.div(...) -> React.createElement('div')
return j.callExpression(
j.memberExpression(
j.identifier(hasReact || 'React'),
j.identifier('createElement')
),
path.value.arguments
)
});
// Remove destructured DOM
// i.e. const { div } = DOM
root
.find(j.VariableDeclarator, {
init: {
name: domFactoriesIdentifier
}
}).remove();
return hasModifications;
};
// Only return the transformed source when we actually have modifications
hasModifications = replaceDOMFactories(j, root)
hasModifications = replaceDOMFactoriesImport(j, root) || hasModifications;
return hasModifications ? root.toSource({ quote: 'single' }) : null;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment