Tom Valorsa

Custom templates with Create React App

July 11, 2020 - 10 min read

Templates are great. They provide a useful starting point for projects and can cut out common setup and configuration work. We can also enhance them with tools that promote good habits and processes by default, making it easier to make the “right” decisions. This saves time and mental bandwidth - with these tasks out of the way there’s more time to concentrate on actually writing code.

Templates can be extended and refined over time. By building on what we already have we can start further and further from “zero”. This, to me, sums up the process of technical progress - by packaging up something useful and making it easy to replicate we can direct our efforts to the “next” problem.

So yeah, if you haven’t guessed already I’m a big fan of templates. This post will walk through the process of creating a custom template for Create React App (CRA), using the official default template as a base.

Planning out the template

Templates of any kind should have a clear purpose. This helps to avoid a situation where we end up catering for too many possibilities and face difficult decisions on what should and shouldn’t be included. We’re trying to make things easier for ourselves after all!

We’re going to look at a more general baseline use case, so it will be lightweight with some utilities that would be useful for any project. If you had a more specific use case it would make sense to create a more specialised template with features and utilities relevant to the problem area.

What we’ll do:

  • use the official CRA default template as a starting point
  • remove some files
  • update some files
  • add some common utilities (Prettier, Source Map Explorer, testing coverage reporting with Jest)
  • test it out by generating a new project

Anatomy of a CRA template

A CRA template has two key elements:

/template

  • can contain anything you want and will form the basis of your newly created project
  • must contain some common files and folders as a bare minimum for react-scripts to work as expected

template.json

  • the config file for your template
  • currently supports a single key, "package", under which you can nest info that will be merged into the newly created project’s package.json file
  • any dependencies and scripts that you specify will be merged in with the default values in react-scripts (i.e. dependencies like React itself, and scripts that set your app up for development/build)
  • other values will just be directly copied over, replacing any defaults if they already exist

The template needs to have the following structure and files as a bare minimum, as laid out in the CRA docs:

README.md
template.json
package.json
template/
  README.md
  gitignore (this will be renamed to .gitignore during the init process)
  public/
    index.html
  src/
    index.js

Using the default template as a starting point

To ensure we’re meeting the minimum criteria above, we can use the official default template of CRA as a foundation for our own template.

In your terminal, navigate to the directory where this template should live and run the following commands:

# Clone the repo
git clone https://github.com/facebook/create-react-app.git

# Copy the template into the current directory
cp -r create-react-app/packages/cra-template .

# Clean up after ourselves
rm -rf create-react-app

Cleaning up

Let’s get rid of some unnecessary files and edit a few of the existing ones so that we’re left with only what we need.

  1. Delete App.css and logo.svg from the /template directory
  2. Update App.js:
import React from 'react';

const App = () => <h1>Hello, world</h1>;

export default App;
  1. Update App.test.js to reflect the change to <App />:
test('renders test heading', () => {
  render(<App />);
  const headingElement = screen.getByText(/hello, world/i);
  expect(headingElement).toBeInTheDocument();
});
  1. The last step here is to add the following to your package.json:
{
  ...
  "main": "template.json"
}

N.B. this is a necessary step until CRA v4 is released. A fix has already been made.

You may also want to update the name and info in README.md and package.json, and the name of the directory we’re working in, but I’ll leave that up to you.

Adding some common utilities

There are a couple of things that I always set up on new projects - this template is the perfect place to put these things.

We’re going to add:

  • Prettier to ensure our code stays nicely formatted
  • Source Map Explorer so we can keep an eye on the state of the app’s bundle
  • an npm script for code coverage reporting using Jest

A note on specifying dependencies

We’ll have to add a few dependencies in order to complete the next steps. We’re not actually going to install them, we just need to list what we need in template.json so that CRA knows what to install when we use this template to create a new project. The process we’ll use to do this is as follows:

  • go to the npm site
  • search for the package to add
  • copy the version number, and then add the package and version number to template.json with ^ as a prefix, for example:
{
  "package": {
    "dependencies": {
      "prettier": "^2.0.5"
    }
  }
}

The ^ symbol is a “caret”, and allows us to give npm permission to install newer minor or patch versions of the package - it’s like saying “feel free to install a newer version if there is one, but no breaking changes please”. It does rely on the package authors following semantic versioning but most major open source projects do so we should be fine, just be aware of this. This will mean that we can go for a longer period of time without needing to update the template’s dependencies (although this is something to revisit periodically so that we’re benefitting from the latest releases). If you want a specific version of a package then you can leave this off.

N.B. while you’d normally add these as devDependencies the current system for templates in CRA only supports listing them as regular dependencies. While this wasn’t considered a problem in the past, it now looks like this will be supported in a future release.

Adding Prettier

We’re going to add Prettier and run it with a pre-commit hook using Husky.

  1. Add prettier, pretty-quick, and husky to dependencies in template.json
  2. Create a file called prettier.config.js in /template and add config options:
// Some of these are defaults, but let's be explicit for other humans
module.exports = {
  tabWidth: 2,
  semi: true,
  singleQuote: true,
  bracketSpacing: true,
  printWidth: 80,
};
  1. Create a file called .prettierignore in /template (this can stay blank for now)
  2. Create a file called husky.config.js in /template and add the following:
module.exports = {
  hooks: {
    'pre-commit': 'npm run pre-commit',
  },
};
  1. In template.json add a "scripts" object to "package" with the following script:
{
  "package": {
    ...
    "scripts": {
      "pre-commit": "pretty-quick --staged"
    }
  }
}

N.B. you might also want to add Prettier to the actual CRA template we’re creating to ensure your template code is formatted as well.

Adding Source Map Explorer

Being able to see what actually goes into your bundles is useful when trying to reduce bundle size and save your user from downloading unnecessary bytes. To get some visibility on this we’re going to use Source Map Explorer.

  1. Add source-map-explorer to dependencies in template.json
  2. In template.json add the following to the "scripts" object:
{
  "package": {
    ...
    "scripts": {
      ...
      "analyze": "source-map-explorer 'build/static/js/*.js'"
    }
  }
}

That’s it! This command will only look at built files. If you want, you can prefix the command above npm run build && so that you don’t have to build as a separate step before running this.

Adding code coverage reporting with Jest

This is also quite straightforward. Jest has its own built-in coverage reporting functionality, and the package itself already comes with any app created using CRA so we don’t even need to add any dependencies.

In template.json add the following to the "scripts" object:

{
  "package": {
    ...
    "scripts": {
      ...
      "coverage": "npm test -- --coverage --watchAll=false"
    }
  }
}

Putting it all together

Now that we’ve added a bunch of useful stuff we need to make sure it works as expected. CRA allows to you specify the path to a custom template when creating a new app. For convenience you might want to add something like this to your .bash_profile:

# Maybe with a catchier name...
alias create-react-app-custom="npx create-react-app --template=file:/path/to/template"

Hint: a quick way to make sure you get the correct path is to type use pwd in your terminal and just copy/paste the result into the alias above.

Now you can just run the following every time you want to use this template:

create-react-app-custom <name> [options]

N.B. you’ll need to open up a new terminal window for this change to your .bash_profile to take effect.

In a new terminal window, try running the following command and looking at the output:

create-react-app-custom custom-app

The contents of the generated project should look familiar. This is the contents of /template, and if you look at the package.json for this project you will see that the dependencies and scripts we specified in template.json have been included.

To test that our additions have been included correctly you can do the following:

  • Prettier: mess up some formatting and commit the change to see the pre-commit handler tidy up for you (e.g. remove the semi-colons in App.js)
  • Source Map Explorer: run npm run build && npm run analyze to see a report in your browser
  • Test coverage: run npm run coverage to see a very basic coverage report based on the default <App> test we left in

    • when this command runs it also generates a new folder, /coverage
    • you can open ./coverage/lcov-report/index.html in your browser for a more interactive experience
    • N.B. you may receive some errors in your terminal related to this issue but the /coverage folder should still be created

And that’s it for the basic template! When using this template to create new projects we no longer have to delete stock files that we don’t need, and a few useful utilities are set up out of the box.

Next steps

If you weren’t already sold, I hope that over the course of reading this you’ve realised that templates can be incredibly useful. We’ve added some basic quality-of-life tools for new projects that use this template but there are a tonne of other things you could add depending on your use case - to name a few:

  • set up your preferred styling solution with a basic theme, default global styles (box-sizing anyone?)
  • react-axe and a11y plugins
  • change default icons and HTML in /public
  • i18n config
  • preferred folder structure
  • add more npm scripts that match your common workflows
  • common packages you always use, including your own utils

Wrapping up

We’ve looked at making our own custom template for Create React App by using the official starter as a base. This was as simple as removing some unwanted code and files, specifying some packages and scripts to include in new projects, and testing it out.

Finally, you should remember that while templates can help us to save time and cut out repetitive tasks it’s important to think about your use case and what to include. If you regularly end up in a situation where you’re editing or deleting the files and utilities generated by your template, you’ve probably gone a bit overboard.

Discuss on Twitter