How To Make A Template For CRA βš›οΈ

Cover image

Published on dev.to and medium.com

Recently, I've had much more free time than I used to. The spread of the COVID epidemic in the world (in my case, in France) has put forward the remote work (more than usual) ; and who says "working remotely" says more time for oneself. Indeed, by removing the daily journey constraint to go to your workplace, you can save a significant time to enjoy your family, play videogames (and/or read books), do sports (just enough) and of course... coding!

I've been evolving in the frontend developmentΒ world since several years now, with the help of theΒ essential ReactΒ frameworkΒ library. As often, to instantiate a viable project quickly (without worrying about the Webpack configuration), I use the create-react-app command-line tool. Unfortunately, every time, I need something extra, that may be: a dependency, an unsupported file format (by default), or a project architecture for scalable applications. So I decided to create my own scaffold project for my future developments. In this post, I suggest you follow, step by step, the development of what is commonly called a "boilerplate" for the React framework library.

NB: Before talking about the main topic, you should know that I spent a lot of time on Figma in order to design some SVGs essential to my development base (in particular, I chose the Monument Valley game as a visuel theme). I might talk about it more in a spin-off post...

1st Step: Project Initialization

The easiest way to create a new React project is to use the command-line tool:

npx create-react-app my-awesome-project

One more time, create-react-app makes things much easier. It generates a React project tree including the main dependencies (react and react-dom), while mystifying some technical dependencies (Webpack, Jest, ESLint, etc...) as well as their configuration file. When we choose this way to generate code, the entire configuration of our application is based on the react-scripts dependency. However, its operation allows the developer to easily overload some libraries, this is the case for ESLint.

Latest versions of react-scripts go even further, since this library is now pre-configured to accept additional dependencies without more configuration. This is the case for the Sass language support, or even for the implementation of the typed JavaScript superset: TypeScript. For my part, I chose to use the .scss format in my scaffold project, simply by adding the appropriate package:

npm install node-sass

NB: If you want to see what is hidden behind the react-scripts dependency, I invite you to eject the project via the command below. Please note, this action is irrevocable...

npm run eject

2nd Step: Adding Dependencies

I've already started adding dependencies with node-sass. Great! With that, I'll already be able to edit .scss and import it directly into my .js and .jsx files.

Then, I wanted to set up "linting" rules, to make my code more cleaner (even if it's a subjective concept) but above all for it to be properly formatted. So I added everything about the Prettier dependency:

npm install prettier eslint-config-prettier eslint-plugin-prettier

Knowing that react-scripts already includes ESLint, I didn't need to add this dependency. However, I had to edit the package.json of my project:

  • To add the lint script
  • To complete the ESLint configuration
"scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test",
  "lint": "eslint --ext .js,.jsx src"
},
"eslintConfig": {
  "extends": ["react-app", "prettier", "prettier/react"],
  "plugins": ["prettier"]
}

For the formatter, I didn't stop there. Since I decided to have a precise configuration for formatting my code, I had to create a Prettier configuration file (prettier.config.js) with all of these options:

module.exports = {
  printWidth: 120,
  singleQuote: true,
  trailingComma: "none",
  jsxBracketSameLine: true,
  arrowParens: "avoid"
};

Finally, I added this same configuration at the ESLint level:

"eslintConfig": {
  "extends": ["react-app", "prettier", "prettier/react"],
  "plugins": ["prettier"],
  "rules": {
    "prettier/prettier": [
      "warn",
      {
        "printWidth": 120,
        "singleQuote": true,
        "trailingComma": "none",
        "jsxBracketSameLine": true,
        "arrowParens": "avoid"
      }
    ]
  }
}

So, with the right plugin in VSCode (Prettier - Code Formatter), Prettier formats the code when saving files, and ESLint controls the formatting. Q.E.D!

NB: You can also externalize ESLint parameters, but I didn't find a way to delete them (or prevent their writing) in the package.json when using the template, to keep the .eslintrc.js file only... Therefore, I advise you to keep them in the package.json at the risk of having two separate ESLint configurations.

To finish with dependencies required for my scaffold project; latest addition: prop-types! You might already know it, but it's essential to control the typing of component properties during your developments (especially for large collaborative projects). React has chosen to extract this package from its core, but I continue to use it as being a good practice!

npm install prop-types

NB: About unit tests, I chose to keep dependencies related to @testing-library in my package.json. You should know that when using the template, these packages disappear, only react, react-dom and react-scripts persist. So, you might keep these test libraries for the future.

Bonus

Once the linting process implemented, I said to myself that it would be interesting to automate it, to not have to execute the npm run lint command, only when really needed. This is how I came across two new finds:

npm install husky lint-staged

The combination of these two dependencies allows me to play the task of formatting and checking the syntax of my code at each commit. To do this, a small configuration is required into the package.json file:

"husky": {
  "hooks": {
    "pre-commit": "lint-staged"
  }
},
"lint-staged": {
  "*.{js,jsx}": [
    "prettier --write",
    "eslint --fix"
  ]
}

3rd Step: Code Organization

I took advantage of my scaffold project implementation to review the code organization. Here is the structure generated by create-react-app:

+-- public/                    # 'index.html' Is Here
+-- src/
  +-- App.css
  +-- App.js
  +-- App.test.js
  +-- index.css
  +-- index.js
  +-- logo.svg
  +-- serviceWorker.js
  +-- setupTests.js
+-- .gitignore
+-- package.json

I really appreciate my project when it's organized by component folders. As the same, I consider a component file to be suffixed by the .jsx extension and not .js (even if this assertion is also valid), visually I find my components more quickly. Here is the structure that I propose to you:

+-- public/                    # 'index.html' Is Here
+-- src/
  +-- components/
  +-- __tests__
    +-- App.test.js
    +-- Content.test.js
  +-- vectors/                 # SVGs Are Here
  +-- App.jsx
  +-- Content.jsx
  +-- index.js
  +-- index.scss
  +-- setupTests.js
+-- .gitignore
+-- package.json
+-- prettier.config.js

Note the deletion of the serviceWorker.js file. Indeed, in my case, I didn't need to implement PWA, so I preferred to remove this part to lighten the code. In addition, we quickly notice the appearance of the __tests__ folder which allow me to group my unit tests (from the same directory), in a single place.

I'm aware that my organization may seem a little bit too directive, and thus allow less freedom when developing applications, but it's just a proposal. After all, the base provided by create-react-app is quite "flat" to make it what we want!

4th Step: Template Assembly

Once your React project is functional and it satisfies you (personally, I changed the whole graphic part), you can start building the template. For that, some rules are essential:

  • The template must be named according to a convention (for example, cra-template-my-awesome-project)
  • The template must respect a specific architecture
+-- cra-template-my-awesome-project/
  +-- template/                # Previous Project Goes Here
    +-- public/
    +-- src/
    +-- gitignore
    +-- README.md
  +-- package.json
  +-- README.md
  +-- template.json

The structure above describes the template organization. Just tell yourself that your previous project is nothing more than the template directory (it must keep this label, this is essential).

NB: As the same, the .gitignore file must be renamed to gitignore, otherwise the template will not be able to work.

According to the code organization that I presented to you previously, the final structure should look something like this:

+-- cra-template-my-awesome-project/
  +-- template/
    +-- public/                # 'index.html' Is Here
    +-- src/
      +-- components/
        +-- __tests__
          +-- App.test.js
          +-- Content.test.js
        +-- vectors/           # SVGs Are Here
        +-- App.jsx
        +-- Content.jsx
      +-- gitignore
      +-- index.js
      +-- index.scss
      +-- prettier.config.js
      +-- setupTests.js
  +-- package.json
  +-- README.md
  +-- template.json

Once these changes have been made, you'll have to move the dependencies, configurations and scripts (except those related to react-scripts) present in your package.json to the template.json file as follows:

{
  "package": {
    "dependencies": {
      "@testing-library/jest-dom": "~4.2.0",
      "@testing-library/react": "~9.3.0",
      "eslint-config-prettier": "~6.10.0",
      "eslint-plugin-prettier": "~3.1.0",
      "husky": "~4.2.0",
      "lint-staged": "~10.1.0",
      "node-sass": "~4.13.0",
      "prettier": "~2.0.0",
      "prop-types": "~15.7.0"
    },
    "scripts": {
      "lint": "eslint --ext .js,.jsx src"
    },
    "eslintConfig": {
      "extends": ["react-app", "prettier", "prettier/react"],
      "plugins": ["prettier"],
      "rules": {
        "prettier/prettier": [
          "warn",
          {
            "printWidth": 120,
            "singleQuote": true,
            "trailingComma": "none",
            "jsxBracketSameLine": true,
            "arrowParens": "avoid"
          }
        ]
      }
    },
    "husky": {
      "hooks": {
        "pre-commit": "lint-staged"
      }
    },
    "lint-staged": {
      "*.{js,jsx}": ["prettier --write", "eslint --fix"]
    }
  }
}

Don't forget to complete the package.json for your future template / library for create-react-app, as well as the two README.md files (one for your NPM dependency, the other for the project generated by this template). It now remains to test your template locally. To do this, simply pointing the command-line tool usage to your cra-template-my-awesome-project directory, and see the result:

npx create-react-app my-awesome-project --template file:./path/to/cra-template-my-awesome-project
cd my-awesome-project
npm run start

Here we are! If you're fully satisfied with the final result, all you have to do is publish your template on NPM. For my part, you will find my template embedding all the libraries presented above, and responding to the visual below, directly on NPM (the source code is available on GitLab).

Before After

I had a lot of fun doing this project (besides more on the design part than on the code itself). Possibilities offered by the command-line tool are more and more extensive (note that custom templates are only available since version 3.3.0 of the react-scripts dependency). I plan to (re)do other boilerplates for professional use cases this time. For example, a scaffold project with some of pre-instantiated custom hooks, or another one boilerplate which implements a Redux base for scalable applications.

Given the orientation that React has taken in last years (especially with the hooks birth, or even with the create-react-app flexibility), the JavaScript library for building interfaces turns out to be my first choice when I have to create a new web application; it may also be my love for functional programming that makes me think that... Anyway, I hope one day to see some of these concepts embedded in other component-oriented frameworks (maybe Vue, maybe Svelte). Wait and see...