Create custom CRA (create-react-app) template with tailwindcss, twin.macro and goober

Have you ever felt the pain of starting a new React project?

Create the app via CRA, add your tools, add you common utilities, hooks, configs, structure, linters, etcetera etcetera.

It's frustrating having to start from ground zero each and every time. ๐Ÿ˜ 

If only there was a better way?!

Being a developer I'm plagued with the same disease as everyone else. I hate repetitive tasks and if I can automate something, you can bet I will.

In this step by step guide we are going to explore how we can rewire React's CRA template to create a custom template bundled with our unique setups.

What is a CRA (create-react-app)?

Create React App is an official way to create single-page React applications.
Basically it is a zero-config toolchain that takes away all the hustle with hot reloading, live server and webpack configuration. Itโ€™s a one size fits all solution with only the bare minimum to get you up and running as fast as possible.

By default React team (shout out for an amazing work โค๏ธ) has created two templates for us, a basic javascript template cra-template and a basic typescript template crate-template-typescript.

For the purpose of this guide I will be using a typescript template, but you can choose whatever suits you the most.

The only real difference between the two is typescript.

We will start by taking a look at CRA template provided by the React team.
It contains a template folder and two files template.json and package.json.

Template folder shares the same structure as a normal react app.
It has a public folder with a basic logo, manifest and robots files as well as an index.html file and a src folder where all your source code is.

As far as files go template.json is a file that represents how our package will look (actual package.json when app is created) it contains all our dependencies and scripts.

On the other hand, although a bit counter intuitive for beginners, the package.json file is just a representation of template information not app information. It contains template name, version and template files.

Now that we have covered the basics we can start building our own template.

We will start by creating our project via CRA by running the following command:

npx create-react-app --template typescript cra-template-tailwind-twin-goober

We are creating a clean CRA application so we can have a testing environment for our custom tooling instead of cloning one of the two templates locally and modifying it.

Keep in mind that the naming convention has to follow this standard: cra-template-[your_template_name] or in our case cra-template-tailwind-twin-goober.

This way CRA will know that it's an actual template instead of an app. That is a reason why typescript template is named cra-template-typescript.

Note that during installation cra-template prefix is comitted as seen with typescript template.

Let's start modifying ๐Ÿ‘ท๐Ÿผ

Navigate to package.json and add the following scripts under scripts section:

// package.json
...
   "cleanup-template": "rm -rf ./_template_",
   "generate-dirs": "mkdir _template_ && cd _template_ && mkdir template && cd template && mkdir src && mkdir public && cd .. && cd ..",
   "copy-resources": "cp -a ./src/. _template_/template/src &&  cp -a ./public/. _template_/template/public && cp template.json _template_/ && cp template-package.json _template_/package.json && cp .gitignore _template_/template/gitignore",
   "generate-template": "npm run cleanup-template && npm run generate-dirs && npm run copy-resources"
...

These scripts will help us generate our custom template on demand.

Now let's examine each script and what it does.

First on our list is cleanup-template script. This script's job is to clean up the template directory in our project. This one will be very useful for generating new templates over and over.
Next script is generate-dirs. This script is used to generate our folders, starting with template which is our template's route folder then template folder inside of it with source and public folders.
Next up is the copy-resources script. The job of this script is to copy all our files and move them to the template folder following CRA structure.
Last on our list is generate-template script, this one just combines previous scripts into one single execution.
This script will be used each time we want to publish/update our template on npm.

Beginner Tip: you run these scripts by typing the npm run command followed by script name. Ex. npm run generate-template

Keep in mind that these scripts are not final and we will be updating them as we progress further in this guide.

In order for the CRA template to work we will need to add two new files template.json and package.json. We will start by creating a template.json file in our root directory and copying the content of template.json of our CRA template of choice.

Next up we are going to create a package.json file in our root directory but since we already have one created by CRA we are going to name this one template-package.json.
Same as with template.json we are going to copy package.json found in CRA template.

We now effectively have the exact same copy of the CRA template as the one used to create our app initially.

Let's give it a go ๐Ÿ˜„

You can run locally your template with the following commands:

// generates template
npm run generate-template

// creates a new react app with your custom template
npx create-react-app --template=file:/path/to/your/dir/cra-template-tailwind-twin-goober/_template_

This is it guys now we have a starting point for our template. Now we can slowly add our tools and customize the template we have already created.

Adding ESLint, Prettier and Husky

We are going to start by installing ESLint globally with the following command:

npm i eslint -g

We can initialize eslint by running:

npx eslint --init

You will be prompted with some questions on how you plan to use ESLint.
As this is not really the subject of this guide I will just leave my answers down below.
Feel free to comment down below if you have any troubles with the setup.

How would you like to use ESLint?
A: To check syntax, find problems, and enforce code style
What type of modules does your project use?
A: JavaScript modules (import/export)
Which framework does your project use?
A: React
Does your project use TypeScript?
A: Yes
Where does your code run?
A: Browser
How would you like to define a style for your project?
A: Use a popular style guide
Which style guide do you want to follow?
A: Airbnb (matches closely my code style)
What format do you want your config file to be in?
A: JSON

That's it ๐Ÿ˜Š We have completed setting up our linter. All we need to do is include it now in our template resource script. If you navigate to your project root you can see an .eslintrc.json file. This file contains your linting rules.

We are going to add ESLint to our template by modifying our copy-resources script like this:

โ€ฆ
"copy-resources": "cp -a ./src/. _template_/template/src &&  cp -a ./public/. _template_/template/public && cp template.json _template_/ && cp template-package.json _template_/package.json && cp .gitignore _template_/template/gitignore && cp .eslintrc.json _template_/template/",
...

Since ESLint installed some dependencies in our project we also need to include them in our template.
We can modify our projects dependencies by modifying template.json file
Navigate to template.json and create a new field called devDependencies and copy the same named field in package.json.
Also since we are running our custom linter we can remove eslintConfig field from template.json.
After these changes your template.json should look like this:

Let's quickly add Prettier by running:

npm install -D prettier

Once the installation is done navigate to the root directory and add .prettierrc.json file.

Prettier configuration is going to depend on your coding style, for the sake of simplicity i will share a link to mine.

We need to modify the copy-resources script as well as template.json and add the prettier with all its dependencies as a resource.

...
"copy-resources": "cp -a ./src/. _template_/template/src &&  cp -a ./public/. _template_/template/public && cp template.json _template_/ && cp template-package.json _template_/package.json && cp .gitignore _template_/template/gitignore && cp ./{.eslintrc.json,.prettierrc.json} _template_/template/ ",
...

Last on our list is husky. We will use husky together with the power of git hooks to automatically format and fix our files on every commit. Since we don't want all our files to be linted on every commit we will install a small package called lint-staged. Thanks to this package we can lint only staged files.

To install husky and lint-staged run the following command:

npm i -D husky lint-staged

After installation update template.json with your new dependencies.

...
"husky": {
      "hooks": {
        "pre-commit": "lint-staged"
      }
    },
"lint-staged": {
      "./src/**/*.{ts,js,jsx,tsx}": [
        "npm run lint --fix",
        "npm run format"
      ]
    },
...

As we can see from the code we added two new fields called husky and lint-staged.
These two fields in conjunction with one another will allow us to achieve our desired effect.

Cool ๐ŸŽ‰ ๐ŸŽ‰. Now whenever we use this template our code style tooling will be ready to go out of the box.

Adding tailwindcss with twin.macro and goober

We are going to use tailwindcss due to the fact that it is a utility first css framework, fully configurable and customisable.
It plays really well with Reactโ€™s philosophy on component composition hence the reason why it's my css framework of choice.
On the other hand twin.macro unlocks the full potential of tailwind css by allowing us to generate styled components from tailwind utilities. Since this is just a babel plugin it leaves no code behind.

We will start by installing tailwind like so:

npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

In order to complete tailwindcss setup we will need a small yet powerful library called craco.
Craco will allow us to reconfigure CRA as needed. We will use it to run postcss with autoprefixer and tailwindcss.

Let's install craco:

npm i @craco/craco

Now let's navigate to template.json. We are going to modify a couple of things here.
First we will add craco as our dependency and then move under scripts section and add the following scripts:

...
 "start": "craco start",
 "build": "craco build",
 "test": "craco test",
 "eject": "react-scripts eject",
...

Note: You need to modify package.json as well with the same changes!

Besides that now we need to create a new file called craco.config.js and add the following code:

// craco.config.js
module.exports = {
  style: {
    postcss: {
      plugins: [
        require('tailwindcss'),
        require('autoprefixer'),
      ],
    },
  },
}

Next step is going to be initializing tailwind css. Run the following command in your terminal:

npx tailwindcss-cli@latest init

This command will create a basic tailwind.config.js file.

Now would be a good time to update our copy-resources script with tailwind and craco configs:

...
"copy-resources": "cp -a ./src/. _template_/template/src &&  cp -a ./public/. _template_/template/public && cp template.json _template_/ && cp template-package.json _template_/package.json && cp .gitignore _template_/template/gitignore && cp ./{.eslintrc.json,.prettierrc.json,craco.config.js,tailwind.config.js} _template_/template/ ",
...

Lets install twin.macro now by running the following command:

npm i twin.macro

In order for twin.macro to do its magic we need babel as well as babel macro plugin. We will install them by running the following command:

npm i -D babel-plugin-macros @babel/core @agney/babel-plugin-goober-css-prop babel-plugin-twin

Last but not least install goober by running:

npm i goober

Now would be a good moment to update our dependencies in template.json and add a new field called babelMacros like this:

...
 "babelMacros": {
      "twin": {
        "config": "tailwind.config.js",
        "preset": "goober"
      }
    }
...

To complete twin.macro setup we will create a .babelrc.json file with the following code:

// .babelrc.json
module.exports = {
  plugins: [
    '@agney/babel-plugin-goober-css-prop',
    'babel-plugin-macros',
    'babel-plugin-twin',
  ],
};

Don't forget to add this file to copy-resources script as such:

...
"copy-resources": "cp -a ./src/. _template_/template/src &&  cp -a ./public/. _template_/template/public && cp template.json _template_/ && cp template-package.json _template_/package.json && cp .gitignore _template_/template/gitignore && cp ./{.eslintrc.json,.prettierrc.json,craco.config.js,tailwind.config.js,babel.config.json} _template_/template/ ",",
...

Now navigate to src/index.ts file and add the following line to import tailwind css base.
While we are here we are going to set up goober as well.

import React from 'react';
import ReactDOM from 'react-dom';
import { setup } from 'goober';
import 'tailwindcss/dist/base.min.css';
import App from './App';

// setup goober
setup(React.createElement);

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

This is it guys. We have finished our template with custom boilerplate code.

Publishing template to npm

This is it people. We are in the endgame now (any MCU fans?).

As the final step let's deploy our template to npm.

Navigate to npm and create an account.(It's free)

After creating an account open up your terminal and run generate-template script.

Once the template is generated we can navigate to template folder in our terminal.

Type the following command to login to npm:

npm login

Once that is done now we can publish our package like so:

npm publish --access public

Thatโ€™s it guys. Our custom template is ready to be installed.

If you have any questions feel free to comment down below and I will get back to you as soon as I can.

Happy hacking guys ๐Ÿ˜Š

19