A couple weeks ago, we discussed the difference between two React component styles, React.createClass
and ES6 classes.
Given current trends, we decided to update our book to use components built with ES6 classes as opposed to React.createClass
. Fortunately, there's tooling that made this a cinch.
jscodeshift is a toolkit for performing code-level modifications to JavaScript files. The tool enables you to go beyond basic find-and-replace. You can make major automated updates to your codebase, like changing the signature of a function and then rewrite the code accordingly everywhere that function is called.
For our purposes, we'll see how jscodeshift works by using it to update a set of example React.createClass
components to ES6 classes.
react-codemod
In jscodeshift, a codemod is a transform that jscodeshift executes against your codebase. You export one or more functions that jscodeshift will invoke. Here's the example codemod that jscodeshift supplies in its README:
/**
* This replaces every occurrence of variable "foo" with "bar"
*/
module.exports = function(fileInfo, api) {
return api
.jscodeshift(fileInfo.source)
.findVariableDeclarators("foo")
.renameTo("bar")
.toSource();
};
react-codemod is a set of codemods for jscodeshift that help automate common tasks that React developers might want to perform. There are a few handy codemods in that repo. We'll be working with the transform inside transforms/class.js
.
Transforming a React.createClass
component
We'll transform this React.createClass()
component:
const ToggleCheckbox = React.createClass({
getInitialState() {
return {
checked: false
};
},
toggleChecked() {
this.setState(prevState => ({ checked: !prevState.checked }));
},
render() {
const className = this.state.checked
? "toggle checkbox checked"
: "toggle checkbox";
return (
<div className={className}>
<input type="checkbox" name="public" onClick={this.toggleChecked} />
<label>Subscribe to weekly newsletter</label>
</div>
);
}
});
First, we need to install jscodeshift. We'll install it globally:
$ npm install -g jscodeshift
Next, we need to clone react-codemod:
$ cd ~/work
$ git clone [email protected]:reactjs/react-codemod.git
Now, we can run jscodeshift
using the following syntax:
jscodeshift -t <path-to-transform> <path-to-file>
Our transform is inside react-codemod, transforms/class.js
. So, after changing to the directory where our component is located, we can run the class transform like this:
$ jscodeshift -t ~/work/react-codemod/transforms/class.js ToggleCheckbox.js
That magic will re-write ToggleCheckbox.js
so that it now declares an ES6 class component:
class ToggleCheckbox extends React.Component {
state = {
checked: false
};
toggleChecked = () => {
this.setState(prevState => ({ checked: !prevState.checked }));
};
render() {
const className = this.state.checked
? "toggle checkbox checked"
: "toggle checkbox";
return (
<div className={className}>
<input type="checkbox" name="public" onClick={this.toggleChecked} />
<label>Subscribe to weekly newsletter</label>
</div>
);
}
}
react-codemod uses property initializers whenever it can.
You can run jscodeshift over a whole directory of files:
jscodeshift -t ~/work/react-codemod/transforms/class.js src/**.js
What if it skips everything?
Depending on your setup, you may run this transform on your JavaScript files and see that jscodeshift simply skipped all of them like this:
jscodeshift -t ~/work/react-codemod/transforms/class.js src/**.js
Sending 15 files to free worker...
All done.
Results:
0 errors
0 unmodified
15 skipped
0 ok
When running transforms/class.js
, by default react-codemod will only modify files that explicitly import React:
import React from "react";
If it doesn't see React being imported, it won't attempt to run the transform on the file.
If you're, say, loading the react
library via script
tags, you won't have these explicit require statements. So, to prevent the transform from skipping your files, pass in the flag --explicit-require=false
like this:
jscodeshift -t ~/work/react-codemod/transforms/class.js src/**.js --explicit-require=false
Dry runs
You can specify you'd like jscodeshift to perform a dry run. This will prevent any of your files from being modified. Instead, you can specify that you'd like jscodeshift to print the output to the console. The -d
flag specifies you'd like a dry run. Pairing it with the -p
flag prints the dry run to the console:
jscodeshift -t ~/work/react-codemod/transforms/class.js -d -p ToggleCheckbox.js
Keep your JavaScript current
JavaScript is constantly in motion and React is moving at a quick clip. Using jscodeshift, your codebase can adopt new syntax and style as the JavaScript/React ecosystems evolve.