Customize Create React App Structure with Bash

By Dave Ceddia Comment

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…

Automation

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 nothing
  • cd <nothing> will do nothing
  • rm src/App.* src/serviceWorker.js src/logo.svg will… delete files from the src 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…

Learning React can be a struggle -- so many libraries and tools!
My advice? Ignore all of them :)
For a step-by-step approach, read my book Pure React.

Loved it! Very well written and put together. Love that you focused only on React. Wish I had stumbled onto your book first before I messed around with all those scary "boilerplate" projects.
— John Lyon-Smith
comments powered by Disqus