Ditching create-react-app and creating your own template for React apps

The author of the article, the translation of which we publish today, invites React developers to move away from use create-react-app(CRA) and create their own template for React applications. Here we will talk about the advantages and disadvantages of CRA, as well as a solution will be proposed that can replace create-react-app.







What is CRA?



Create React App is a suite of tools built and maintained by the developers at Facebook. CRA is for quickly creating templated React applications. With CRA, a React project base is created with a single command.



Strengths of CRA



  • CRA allows you to create a base for a React project with one command:



    npx create-react-app my-app
    
  • The use of a CRA saves the developer from the need for in-depth study of auxiliary tools. The developer can focus on React and not worry about setting up Webpack, Babel, and other utility mechanisms.
  • When applying the CRA, the developer only needs one dependency that is relevant to building the project. This is react-scripts. This dependency includes all other assembly dependencies, as a result it turns out that, for example, one command is enough to install and update dependencies:



    npm install react-scripts@latest
    


CRA



  • CRA . eject, CRA- . customize-cra react-app-rewired, .
  • CRA . React- , React- . CRA Β« Β», , react-scripts β€” , React-. , , , react-scripts β€” , , «» (Babel) (Webpack), React- . , , , .
  • CRA, it seems to me, is overloaded with opportunities that, in some project, may well turn out to be unclaimed. For example, application stubs created with CRA support SASS. That is, if your project uses regular CSS or Less, SASS support will be completely unnecessary. Here , if you're interested, is the package.jsonCRA application file after the command eject. This file "unrolled" the dependencies previously presented react-scripts.


An alternative to CRA is to develop your own template for quickly creating basic React projects.



Alternative to CRA



In developing an alternative to CRA, we are going to equip it with the ability to quickly create basic React projects using just one command. This repeats one of the useful features create-react-app. And we, of course, will not transfer the disadvantages of CRA to our system, independently installing dependencies and setting up the project. Our project will not include two other useful CRA features (relieving the developer from the need to study auxiliary mechanisms and the "one dependency" scheme), since they also carry disadvantages (hiding the internal mechanisms of auxiliary subsystems from the developer and the complexity of customizing their own project build configurations) ...



Here is the repository that contains all the code that we will discuss in this article.



Let's start by initializing the project with tools npmand initializing its git repository:



npm init
git init


Let's create a file with the .gitignorefollowing content:



node_modules
build


This will allow us not to include in the repository the folders whose names are present in the file.



Now let's think about what basic dependencies we need in order to build and run a React application.



React and react-dom libraries



These are the only runtime dependencies we need:



npm install react react-dom --save


Transpiler (Babel)



The Babel transpiler converts ECMAScript 2015+ standards-compliant code into code that will work in both new and legacy browsers. Babel, thanks to the use of presets, is also used to process JSX code:



npm install @babel/core @babel/preset-env @babel/preset-react --save-dev


Here's what a simple Babel configuration looks like to get your React applications up and running. This configuration can be added to a file .babelrcor to package.json:



{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react"
    ]
}


Babel supports many presets and plugins . They can be added to the project as and when the need arises.



Bandler (Webpack)



The Webpack bundler is responsible for building the project, forming a single application file (bundle) based on the project code and the code of its dependencies. When using project optimization techniques such as code splitting, an application bundle can include multiple files.



npm install webpack webpack-cli webpack-dev-server babel-loader css-loader style-loader html-webpack-plugin --save-dev


A simple Webpack configuration for building React app packages looks like the following:



const path = require('path');
const HtmlWebPackPlugin = require('html-webpack-plugin');

module.exports = {
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: 'bundle.js',
  },
  resolve: {
    modules: [path.join(__dirname, 'src'), 'node_modules'],
    alias: {
      react: path.join(__dirname, 'node_modules', 'react'),
    },
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: 'style-loader',
          },
          {
            loader: 'css-loader',
          },
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebPackPlugin({
      template: './src/index.html',
    }),
  ],
};


Various bootloaders can be added here, according to the needs of a particular application . If you are interested in this topic, take a look at my article , where I talk about Webpack configurations that you can use to prepare React applications for use in production.



These are all the dependencies we need. Now let's add a template HTML file and a stub React component to the project.



Create a folder in the project directory srcand add a file to it index.html:



<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>React Boilerplate</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>


Let's create a React component in the same folder HelloWorld:



import React from 'react';

const HelloWorld = () => {
  return (
      <h3>Hello World</h3>
  );
};

export default HelloWorld;


Add the file to the same folder index.js:



import React from 'react';
import { render } from 'react-dom';

import HelloWorld from './HelloWorld';

render(<HelloWorld />, document.getElementById('root'));


And finally, add to the package.jsondescriptions of scripts for launching ( start) and building ( build) the project:



"scripts": {
    "start": "webpack-dev-server --mode=development --open --hot",
    "build": "webpack --mode=production"
  }


That's all. We now have a workable stub React application at our disposal. You can verify this by running the commands npm startand npm run build.



Now let's equip our system with the ability to prepare a project template with a single command. That is, we will recreate one of the strengths of the CRA. We are going to use an executable JS file that will be called when you enter the appropriate command on the command line. For example, a command like this might look like this:



reactjs-boilerplate new-project


To implement this idea, we are going to use the bin section of the file package.json.



First, install the fs-extra package :



npm i fs-extra


Now let's create an executable JS file start.js, which will be located in binour project folder . Let's put the following code in this file:



#!/usr/bin/env node
const fs = require("fs-extra");
const path = require("path");
const https = require("https");
const { exec } = require("child_process");

const packageJson = require("../package.json");

const scripts = `"start": "webpack-dev-server --mode=development --open --hot",
"build": "webpack --mode=production"`;

const babel = `"babel": ${JSON.stringify(packageJson.babel)}`;

const getDeps = (deps) =>
  Object.entries(deps)
    .map((dep) => `${dep[0]}@${dep[1]}`)
    .toString()
    .replace(/,/g, " ")
    .replace(/^/g, "")
    //  ,     ,    
    .replace(/fs-extra[^\s]+/g, "");

console.log("Initializing project..");

//     npm-
exec(
  `mkdir ${process.argv[2]} && cd ${process.argv[2]} && npm init -f`,
  (initErr, initStdout, initStderr) => {
    if (initErr) {
      console.error(`Everything was fine, then it wasn't:
    ${initErr}`);
      return;
    }
    const packageJSON = `${process.argv[2]}/package.json`;
    //  ,   
    fs.readFile(packageJSON, (err, file) => {
      if (err) throw err;
      const data = file
        .toString()
        .replace(
          '"test": "echo \\"Error: no test specified\\" && exit 1"',
          scripts
        )
        .replace('"keywords": []', babel);
      fs.writeFile(packageJSON, data, (err2) => err2 || true);
    });

    const filesToCopy = ["webpack.config.js"];

    for (let i = 0; i < filesToCopy.length; i += 1) {
      fs.createReadStream(path.join(__dirname, `../${filesToCopy[i]}`)).pipe(
        fs.createWriteStream(`${process.argv[2]}/${filesToCopy[i]}`)
      );
    }
    // npm,   ,   .gitignore,        ;    .     GitHub-  raw- .gitignore
    https.get(
      "https://raw.githubusercontent.com/Nikhil-Kumaran/reactjs-boilerplate/master/.gitignore",
      (res) => {
        res.setEncoding("utf8");
        let body = "";
        res.on("data", (data) => {
          body += data;
        });
        res.on("end", () => {
          fs.writeFile(
            `${process.argv[2]}/.gitignore`,
            body,
            { encoding: "utf-8" },
            (err) => {
              if (err) throw err;
            }
          );
        });
      }
    );

    console.log("npm init -- done\n");

    //  
    console.log("Installing deps -- it might take a few minutes..");
    const devDeps = getDeps(packageJson.devDependencies);
    const deps = getDeps(packageJson.dependencies);
    exec(
      `cd ${process.argv[2]} && git init && node -v && npm -v && npm i -D ${devDeps} && npm i -S ${deps}`,
      (npmErr, npmStdout, npmStderr) => {
        if (npmErr) {
          console.error(`Some error while installing dependencies
      ${npmErr}`);
          return;
        }
        console.log(npmStdout);
        console.log("Dependencies installed");

        console.log("Copying additional files..");
        //     
        fs.copy(path.join(__dirname, "../src"), `${process.argv[2]}/src`)
          .then(() =>
            console.log(
              `All done!\n\nYour project is now ready\n\nUse the below command to run the app.\n\ncd ${process.argv[2]}\nnpm start`
            )
          )
          .catch((err) => console.error(err));
      }
    );
  }
);


Now, let's link this JS executable with the command from package.json:



"bin": {
    "your-boilerplate-name": "./bin/start.js"
  }


Let's create a local link for the package:



npm link


Now, after executing this command, if we execute a command of the form in the terminal your-boilerplate-name my-app, our executable file will be called start.js. It will create a new folder with a name my-app, copy the files package.json, webpack.config.jsand .gitignore, as well as the folder src, and install the dependencies of the new project my-app.



Wonderful. Now it all runs on your computer and allows you to create basic React projects with their own build configuration with a single command.



You can go ahead and publish your template to the npm registry . To do this, you first need to submit the project to the GitHub repository. Then follow these instructions.



Congratulations! We have just created an alternative in just a few minutes create-react-app. Our solution is not overloaded with unnecessary features (dependencies can be added to projects based on it as and when the need arises). Using it, you can easily adjust the project build configuration to suit your needs.



Of course, our solution is minimalistic. Projects created on its basis cannot be considered ready for use in production. In order to prepare them for real work, we need to equip our template with some Webpack settings responsible for optimizing project builds.



I have prepared reactjs-boilerplate templatethat allows you to create projects ready for work in production. It uses the appropriate build configuration, linting and hooks responsible for checking the project before creating commits. Try this template. If you have any ideas for improving it, or if you decide to contribute to its development, join the work on it.



Outcome



Here's what we talked about in this article:



  • We've sorted out the pros and cons create-react-app.
  • We have implemented in our project a useful CRA feature for creating blank React applications with one command. And we got rid of the shortcomings of CRA.
  • We have equipped our project with the minimal Webpack and Babel configurations required to build and run React applications.
  • We created a React component HelloWorld.js, provided the ability to build a project and run it using the development server.
  • We created an executable JS file and linked it to the appropriate command using the binfile section package.json.
  • We used the team npm linkto create a local link for our project and be able to create new baseline projects from it with a single team.


I hope this material will help you in developing projects based on React.



Do you use create-react-app when creating new React projects?






All Articles