mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge pull request #4767 from spicyj/com
Add react-to-react-dom codemod
This commit is contained in:
@@ -45,10 +45,19 @@ are using the master version (>0.13.1) of React as it is using
|
||||
if you are using or planning to use [Flow](http://flowtype.org/). Also make
|
||||
sure you are not calling `setState` anywhere outside of your component.
|
||||
|
||||
All scripts take an option `--no-explicit-require=true` if you don't have a
|
||||
These three scripts take an option `--no-explicit-require=true` if you don't have a
|
||||
`require('React')` statement in your code files and if you access React as a
|
||||
global.
|
||||
|
||||
`react-to-react-dom` updates code for the split of the `react` and `react-dom`
|
||||
packages (e.g., `React.render` to `ReactDOM.render`). It looks for
|
||||
`require('react')` and replaces the appropriate property accesses using
|
||||
`require('react-dom')`. It does not support ES6 modules or other non-CommonJS
|
||||
systems.
|
||||
|
||||
* `jscodeshift -t react/packages/react-codemod/transforms/react-to-react-dom.js <file>`
|
||||
* After running the automated codemod, you may want to run a regex-based find-and-replace to remove extra whitespace between the added requires, such as `codemod.py -m -d src --extensions js '(var React\s*=\s*require\(.react.\);)\n\n(\s*var ReactDOM)' '\1\n\2'` using https://github.com/facebook/codemod.
|
||||
|
||||
### Explanation of the ES2015 class transform
|
||||
|
||||
* Ignore components with calls to deprecated APIs. This is very defensive, if
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var CORE_PROPERTIES = [
|
||||
'Children',
|
||||
'Component',
|
||||
'createElement',
|
||||
'cloneElement',
|
||||
'isValidElement',
|
||||
'PropTypes',
|
||||
'createClass',
|
||||
'createFactory',
|
||||
'createMixin',
|
||||
'DOM',
|
||||
'__spread',
|
||||
];
|
||||
|
||||
var DOM_PROPERTIES = [
|
||||
'findDOMNode',
|
||||
'render',
|
||||
'unmountComponentAtNode',
|
||||
'unstable_batchedUpdates',
|
||||
'unstable_renderSubtreeIntoContainer',
|
||||
];
|
||||
|
||||
var DOM_SERVER_PROPERTIES = [
|
||||
'renderToString',
|
||||
'renderToStaticMarkup',
|
||||
];
|
||||
|
||||
function reportError(node, error) {
|
||||
throw new Error(
|
||||
`At ${node.loc.start.line}:${node.loc.start.column}: ${error}`
|
||||
);
|
||||
}
|
||||
|
||||
function isRequire(path, moduleName) {
|
||||
return (
|
||||
path.value.type === 'CallExpression' &&
|
||||
path.value.callee.type === 'Identifier' &&
|
||||
path.value.callee.name === 'require' &&
|
||||
path.value.arguments.length === 1 &&
|
||||
path.value.arguments[0].type === 'Literal' &&
|
||||
path.value.arguments[0].value === moduleName
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = function(file, api) {
|
||||
var j = api.jscodeshift;
|
||||
var root = j(file.source);
|
||||
|
||||
[
|
||||
['React', 'ReactDOM', 'ReactDOMServer'],
|
||||
['react', 'react-dom', 'react-dom/server'],
|
||||
].forEach(function(pair) {
|
||||
var coreModuleName = pair[0];
|
||||
var domModuleName = pair[1];
|
||||
var domServerModuleName = pair[2];
|
||||
|
||||
var domAlreadyDeclared = false;
|
||||
var domServerAlreadyDeclared = false;
|
||||
|
||||
var coreRequireDeclarator;
|
||||
root
|
||||
.find(j.CallExpression)
|
||||
.filter(p => isRequire(p, coreModuleName))
|
||||
.forEach(p => {
|
||||
if (p.parent.value.type === 'VariableDeclarator') {
|
||||
if (p.parent.value.id.type === 'ObjectPattern') {
|
||||
var pattern = p.parent.value.id;
|
||||
var all = pattern.properties.every(function(prop) {
|
||||
if (prop.key.type === 'Identifier') {
|
||||
var name = prop.key.name;
|
||||
return CORE_PROPERTIES.indexOf(name) !== -1;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (all) {
|
||||
// var {PropTypes} = require('React'); so leave alone
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (coreRequireDeclarator) {
|
||||
reportError(
|
||||
p.value,
|
||||
'Multiple declarations of React'
|
||||
);
|
||||
}
|
||||
if (p.parent.value.id.type !== 'Identifier') {
|
||||
reportError(
|
||||
p.value,
|
||||
'Unexpected destructuring in require of ' + coreModuleName
|
||||
);
|
||||
}
|
||||
var name = p.parent.value.id.name;
|
||||
var scope = p.scope.lookup(name);
|
||||
if (scope.declares('ReactDOM')) {
|
||||
console.log('Using existing ReactDOM var in ' + file.path);
|
||||
domAlreadyDeclared = true;
|
||||
}
|
||||
if (scope.declares('ReactDOMServer')) {
|
||||
console.log('Using existing ReactDOMServer var in ' + file.path);
|
||||
domServerAlreadyDeclared = true;
|
||||
}
|
||||
coreRequireDeclarator = p.parent;
|
||||
} else if (p.parent.value.type === 'AssignmentExpression') {
|
||||
if (p.parent.value.left.type !== 'Identifier') {
|
||||
reportError(
|
||||
p.value,
|
||||
'Unexpected destructuring in require of ' + coreModuleName
|
||||
);
|
||||
}
|
||||
var name = p.parent.value.left.name;
|
||||
var scope = p.scope.lookup(name);
|
||||
var reactBindings = scope.getBindings()[name];
|
||||
if (reactBindings.length !== 1) {
|
||||
throw new Error(
|
||||
'Unexpected number of bindings: ' + reactBindings.length
|
||||
);
|
||||
}
|
||||
coreRequireDeclarator = reactBindings[0].parent;
|
||||
if (coreRequireDeclarator.value.init &&
|
||||
!isRequire(coreRequireDeclarator.get('init'), coreModuleName)) {
|
||||
reportError(
|
||||
coreRequireDeclarator.value,
|
||||
'Unexpected initialization of ' + coreModuleName
|
||||
);
|
||||
}
|
||||
if (scope.declares('ReactDOM')) {
|
||||
console.log('Using existing ReactDOM var in ' + file.path);
|
||||
domAlreadyDeclared = true;
|
||||
}
|
||||
if (scope.declares('ReactDOMServer')) {
|
||||
console.log('Using existing ReactDOMServer var in ' + file.path);
|
||||
domServerAlreadyDeclared = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!coreRequireDeclarator) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!domAlreadyDeclared &&
|
||||
root.find(j.Identifier, {name: 'ReactDOM'}).size() > 0) {
|
||||
throw new Error(
|
||||
'ReactDOM is already defined in a different scope than React'
|
||||
);
|
||||
}
|
||||
if (!domServerAlreadyDeclared &&
|
||||
root.find(j.Identifier, {name: 'ReactDOMServer'}).size() > 0) {
|
||||
throw new Error(
|
||||
'ReactDOMServer is already defined in a different scope than React'
|
||||
);
|
||||
}
|
||||
|
||||
var coreName = coreRequireDeclarator.value.id.name;
|
||||
|
||||
var processed = new Set();
|
||||
var requireAssignments = [];
|
||||
var coreUses = 0;
|
||||
var domUses = 0;
|
||||
var domServerUses = 0;
|
||||
|
||||
root
|
||||
.find(j.Identifier, {name: coreName})
|
||||
.forEach(p => {
|
||||
if (processed.has(p.value)) {
|
||||
// https://github.com/facebook/jscodeshift/issues/36
|
||||
return;
|
||||
}
|
||||
processed.add(p.value);
|
||||
if (p.parent.value.type === 'MemberExpression' ||
|
||||
p.parent.value.type === 'QualifiedTypeIdentifier') {
|
||||
var left;
|
||||
var right;
|
||||
if (p.parent.value.type === 'MemberExpression') {
|
||||
left = p.parent.value.object;
|
||||
right = p.parent.value.property;
|
||||
} else {
|
||||
left = p.parent.value.qualification;
|
||||
right = p.parent.value.id;
|
||||
}
|
||||
if (left === p.value) {
|
||||
// React.foo (or React[foo])
|
||||
if (right.type === 'Identifier') {
|
||||
var name = right.name;
|
||||
if (CORE_PROPERTIES.indexOf(name) !== -1) {
|
||||
coreUses++;
|
||||
} else if (DOM_PROPERTIES.indexOf(name) !== -1) {
|
||||
domUses++;
|
||||
j(p).replaceWith(j.identifier('ReactDOM'));
|
||||
} else if (DOM_SERVER_PROPERTIES.indexOf(name) !== -1) {
|
||||
domServerUses++;
|
||||
j(p).replaceWith(j.identifier('ReactDOMServer'));
|
||||
} else {
|
||||
throw new Error('Unknown property React.' + name);
|
||||
}
|
||||
}
|
||||
} else if (right === p.value) {
|
||||
// foo.React, no need to transform
|
||||
} else {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
} else if (p.parent.value.type === 'VariableDeclarator') {
|
||||
if (p.parent.value.id === p.value) {
|
||||
// var React = ...;
|
||||
} else if (p.parent.value.init === p.value) {
|
||||
// var ... = React;
|
||||
var pattern = p.parent.value.id;
|
||||
if (pattern.type === 'ObjectPattern') {
|
||||
// var {PropTypes} = React;
|
||||
// Most of these cases will just be looking at {PropTypes} so this
|
||||
// is usually a no-op.
|
||||
var coreProperties = [];
|
||||
var domProperties = [];
|
||||
pattern.properties.forEach(function(prop) {
|
||||
if (prop.key.type === 'Identifier') {
|
||||
var key = prop.key.name;
|
||||
if (CORE_PROPERTIES.indexOf(key) !== -1) {
|
||||
coreProperties.push(prop);
|
||||
} else if (DOM_PROPERTIES.indexOf(key) !== -1) {
|
||||
domProperties.push(prop);
|
||||
} else {
|
||||
throw new Error(
|
||||
'Unknown property React.' + key + ' while destructuring'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
});
|
||||
var domDeclarator = j.variableDeclarator(
|
||||
j.objectPattern(domProperties),
|
||||
j.identifier('ReactDOM')
|
||||
);
|
||||
if (coreProperties.length && !domProperties.length) {
|
||||
// nothing to do
|
||||
coreUses++;
|
||||
} else if (domProperties.length && !coreProperties.length) {
|
||||
domUses++;
|
||||
j(p.parent).replaceWith(domDeclarator);
|
||||
} else {
|
||||
coreUses++;
|
||||
domUses++;
|
||||
var decl = j(p).closest(j.VariableDeclaration);
|
||||
decl.insertAfter(j.variableDeclaration(
|
||||
decl.get().value.kind,
|
||||
[domDeclarator]
|
||||
));
|
||||
}
|
||||
} else {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
} else {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
} else if (p.parent.value.type === 'AssignmentExpression') {
|
||||
if (p.parent.value.left === p.value) {
|
||||
if (isRequire(p.parent.get('right'), coreModuleName)) {
|
||||
requireAssignments.push(p.parent);
|
||||
} else {
|
||||
reportError(
|
||||
p.parent.value,
|
||||
'Unexpected assignment to ' + coreModuleName
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
} else {
|
||||
reportError(p.value, 'unimplemented ' + p.parent.value.type);
|
||||
}
|
||||
});
|
||||
|
||||
coreUses += root.find(j.JSXElement).size();
|
||||
|
||||
function insertRequire(name, path) {
|
||||
var req = j.callExpression(
|
||||
j.identifier('require'),
|
||||
[j.literal(path)]
|
||||
);
|
||||
requireAssignments.forEach(function(requireAssignment) {
|
||||
requireAssignment.parent.insertAfter(
|
||||
j.expressionStatement(
|
||||
j.assignmentExpression('=', j.identifier(name), req)
|
||||
)
|
||||
);
|
||||
});
|
||||
coreRequireDeclarator.parent.insertAfter(j.variableDeclaration(
|
||||
coreRequireDeclarator.parent.value.kind,
|
||||
[j.variableDeclarator(
|
||||
j.identifier(name),
|
||||
coreRequireDeclarator.value.init ? req : null
|
||||
)]
|
||||
));
|
||||
}
|
||||
|
||||
if (domServerUses > 0 && !domServerAlreadyDeclared) {
|
||||
insertRequire('ReactDOMServer', domServerModuleName);
|
||||
}
|
||||
if (domUses > 0 && !domAlreadyDeclared) {
|
||||
insertRequire('ReactDOM', domModuleName);
|
||||
}
|
||||
if ((domUses > 0 || domServerUses > 0) && coreUses === 0) {
|
||||
j(coreRequireDeclarator).remove();
|
||||
requireAssignments.forEach(r => j(r).remove());
|
||||
}
|
||||
});
|
||||
|
||||
return root.toSource({quote: 'single'});
|
||||
};
|
||||
Reference in New Issue
Block a user