Create React App (CRA) is great. Recently updated to version 2, it now supports Sass out of the box and a bunch of other cool stuff.
But what if you want to customize the file structure it gives you?
One way would be to manually modify the files. I’ve done this a lot. Delete some files, maybe create some directories, move some files around, til it looks like a decent React project structure. It gets tiring.
Another way would be to use a custom react-scripts package, but that might be more hassle than it’s worth.
Or you could hack together a quick shell script to do your bidding. That’s what we’re gonna do here.
Tools and Assumptions
I primarily use macOS and the plain old Bash shell that comes with it. If you’re on Linux, or use a different shell, or use Windows – these instructions will probably need some tweaking. The concepts transfer though: create little scripts to automate work and save yourself time. Plus it’s fun. Maybe too fun…
I also want to note that here I’m talking about customizing the way Create React App creates a project directory and its files – not customizing CRA to add Babel plugins or anything like that. (though I’ve got a video on customizing CRA without ejecting and you’ll probably need an additional library with CRA v2)
Begin With the End in Mind
I often find myself spinning up CRA projects to quickly test something, or to create an example for this blog or the book. And in most cases, I don’t need the code that comes with it – I want to start from a blank file and build something up from scratch.
My creation process, in commands, looks something like this:
create-react-app new-thing
cd new-thing
rm src/App* src/serviceWorker* src/logo.svg
vim src/index.js # and then delete everything in it
So that’s the “end” I want to get to. A blank CRA project with the bare essentials.
Probably, your “end” is different.
Maybe you want a default project structure with some directories like components
and containers
.
Maybe you always use Redux and so you want to yarn install redux react-redux
and set up the basic store and Provider.
Figure out what you want to do before writing a script to do it. If you have no idea what you want to do, don’t write any scripts yet ;)
A Place for Everything…
I’d like the script to be accessible anywhere. We’ll create a new command called cra-blank
that will take a project name and set up a blank-slate CRA project.
For this, I’m gonna create a bin
directory in my home directory, and add it to my PATH
so that I can run these commands from anywhere.
(by the way: in Bash and most other shells, the tilde symbol ~
gets replaced by the full path to your home directory – so ~/bin
is equivalent to /Users/dave/bin
if your username is “dave” and you’re on macOS ;)
mkdir ~/bin
Then open up your .bash_profile
file in your favorite editor. On my Mac, this is at ~/.bash_profile
– create it if it doesn’t exist – and add the new bin
directory to your PATH
:
export PATH=~/bin:$PATH
If you already have an export PATH=...
line, you can tack ~/bin:
(with the colon) on the front of it. Or add this new line. Either way will accomplish the same thing.
Now close your terminal and re-open it, or source your new profile by running:
source ~/.bash_profile
Create the Script
Create a new file in ~/bin
with the name of your new command, and open it up in your favorite editor.
vim ~/bin/cra-blank
Into this file, place the commands you want to run. (In Bash, $1
refers to the first argument)
create-react-app $1
cd $1
rm src/App.* src/serviceWorker.js src/logo.svg
> src/index.js
> src/index.css
(those last two lines are a nifty Bash trick to erase the contents of those files)
Then we need to mark the file as executable, otherwise we can’t run it. Back at the shell:
chmod +x ~/bin/cra-blank
Great! Let’s try it out. Just run the command and give it a project name…
cra-blank test
And… it’s installing! But wait. It didn’t change directories? Hmm.
“cd” doesn’t work in shell scripts
As it turns out, “cd” doesn’t work in shell scripts because the script is actually running in a separate shell from the one where you ran the command. Our script did in fact run as requested, but since it did so in its own little universe, the change of directory didn’t appear to happen.
If you look into the project, though, it did get cleaned up as we asked:
$ ls test/src
index.css index.js
There are a couple ways we could fix this.
Source It
If we run the script by sourcing it – prefixing it with the source
or .
command – that will cause it to run in the current shell and the “cd” command will work.
This would look like:
source cra-blank test
Or…
. cra-blank test
The two versions are equivalent. The second is just easier to type. But both of these come with a downside: you have to remember to run it that way every time, or the “cd” won’t work.
Write a Function
Another option (and the better one IMO) is to put the commands into a Bash function. Those run inside the shell where you invoke them.
To do this, open up your ~/.bash_profile
file again and write the function at the bottom:
function cra() {
create-react-app $1
cd $1
rm src/App.* src/serviceWorker.js src/logo.svg
> src/index.js
> src/index.css
}
The body of the function is the same set of commands that we put in ~/bin/cra-blank
. I gave it a different name here to avoid the name collision, but you can also just delete the file in ~/bin.
With that change made, close and re-open your terminal or re-source the profile:
source ~/.bash_profile
And now you should be able to use the new function to create a React app:
cra test2
Woohoo!
A Little Error Handling
Our function has a sort of dangerous bug in it. Can you spot it?
…
Hint: If you run it with no argument, what will it do?
create-react-app <nothing>
will do nothingcd <nothing>
will do nothingrm src/App.* src/serviceWorker.js src/logo.svg
will… delete files from thesrc
directory, if one exists! (and not in the CRA project, because that command failed!)
Always watch out for things like this. You can protect against it here by chaining the commands with &&
, or by explicitly checking that the argument exists:
# Option 1: don't run the later commands
# unless the earlier ones succeed
function cra() {
create-react-app $1 && cd $1 && rm src/App.* src/serviceWorker.js src/logo.svg
}
# Option 2: Check for an argument. Return if it's missing.
function cra() {
if [ -z $1 ]; then
echo "Usage: cra <project-name>"
return
fi
create-react-app $1
cd $1
rm src/App.* src/serviceWorker.js src/logo.svg
}
I like Option 2, since the rest of the script can remain untouched. You only need the one check at the top.
There are probably lots of other ways to solve this in Bash, and I’m not sure if this is the most bulletproof way to do it, but it protects against one’s own forgetfulness and that’s all I’m really going for here.
A Little Refactoring
If you have a lot of these little functions, you might not want them cluttering up your ~/.bash_profile
. Instead, you can extract them to their own file, and source that other file from within ~/.bash_profile
.
You could create a file ~/bin/useful-hacks.sh
and then add a line to ~/.bash_profile
to load them:
source ~/.bin/useful-hacks.sh
Go Forth and Automate
Got some commands that you type all the time? Now you know how to bundle them up into little scripts or functions (depending on which execution context they need!) and save yourself some time.
Have fun. Just remember that XKCD comic…