create-react-app (CRA) и создать собственный шаблон для React-приложений. Здесь речь пойдёт о преимуществах и недостатках CRA, а так же будет предложено решение, которое способно заменить create-react-app.
Что такое CRA?
Create React App — это набор инструментов, созданный и поддерживаемый разработчиками из Facebook. CRA предназначен для быстрого создания шаблонных проектов React-приложений. При использовании CRA база React-проекта создаётся с помощью одной команды.
Сильные стороны CRA
- CRA позволяет создать базу для React-проекта одной командой:
npx create-react-app my-app - Использование CRA избавляет разработчика от необходимости глубокого изучения вспомогательных инструментов. Разработчик может сосредоточиться на React и не беспокоиться о настройке Webpack, Babel и других служебных механизмов.
 - При применении CRA разработчику требуется лишь одна зависимость, имеющая отношение к сборке проекта. Это — 
react-scripts. Эта зависимость включает в себя все остальные зависимости сборки, в результате оказывается, что, например, для установки и обновления зависимостей достаточно одной команды:
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, как мне кажется, перегружен возможностями, которые, в каком-то проекте, вполне могут оказаться невостребованными. Например, заготовки приложений, создаваемые с помощью CRA, поддерживают SASS. То есть, если в проекте используется обычный CSS или Less, поддержка SASS окажется совершенно ненужной. Вот, если интересно, файл 
package.jsonCRA-приложения после командыeject. В этом файле «развёрнуты» зависимости, раньше представленныеreact-scripts. 
Альтернативой CRA может стать разработка собственного шаблона для быстрого создания базовых React-проектов.
Альтернатива CRA
Разрабатывая альтернативу CRA, мы собираемся оснастить её возможностью быстрого, с использованием лишь одной команды, создания базовых React-проектов. Это повторяет одну из полезных возможностей
create-react-app. А недостатки CRA мы, конечно, в нашу систему переносить не будем, самостоятельно устанавливая зависимости и настраивая проект. В наш проект не попадут две других полезных возможности CRA (избавление разработчика от необходимости изучения вспомогательных механизмов и схема «одной зависимости»), так как они несут с собой и недостатки (сокрытие внутренних механизмов вспомогательных подсистем от разработчика и сложность настройки собственных конфигураций сборки проектов).Вот репозиторий, в котором имеется весь код, который мы будем обсуждать в этом материале.
Начнём работу с инициализации проекта средствами
npm и с инициализации его git-репозитория:npm init
git init
Создадим файл
.gitignore следующего содержания:node_modules
build
Это позволит нам не включать в состав репозитория папки, имена которых присутствуют в файле.
Теперь поразмыслим о том, какие основные зависимости нам нужны для того чтобы собрать и запустить React-приложение.
Библиотеки react и react-dom
Это — единственные зависимости времени выполнения, которые нам нужны:
npm install react react-dom --save
Транспилятор (Babel)
Транспилятор Babel преобразует код, соответствующий стандартам ECMAScript 2015+, в код, который будет работать и в новых, и в устаревших браузерах. Babel, благодаря применению пресетов, используется и для обработки JSX-кода:
npm install @babel/core @babel/preset-env @babel/preset-react --save-dev
Вот как выглядит простая конфигурация Babel, предназначенная для подготовки к работе React-приложений. Эту конфигурацию можно добавить в файл
.babelrc или в package.json:{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react"
    ]
}
Babel поддерживает множество пресетов и плагинов. Их можно добавлять в проект по мере возникновения необходимости в них.
Бандлер (Webpack)
Бандлер Webpack отвечает за сборку проекта, формируя на основе кода проекта и кода его зависимостей единственный файл (бандл) приложения. При использовании таких техник оптимизации проектов, как разделение кода, в бандл приложения могут входить и несколько файлов.
npm install webpack webpack-cli webpack-dev-server babel-loader css-loader style-loader html-webpack-plugin --save-devПростая конфигурация Webpack, предназначенная для сборки пакетов React-приложений, выглядит так, как показано ниже:
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',
    }),
  ],
};Сюда, в соответствии с нуждами конкретного приложения, можно добавить различные загрузчики. Если вам эта тема интересна — взгляните на мой материал, где я рассказываю о конфигурациях Webpack, которыми можно воспользоваться для подготовки React-приложений к использованию в продакшне.
Это — все необходимые нам зависимости. Теперь давайте добавим в проект шаблонный HTML-файл и заготовку React-компонента.
Создадим в директории проекта папку
src и добавим в неё файл index.html:<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>React Boilerplate</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>
В той же папке создадим React-компонент
HelloWorld:import React from 'react';
const HelloWorld = () => {
  return (
      <h3>Hello World</h3>
  );
};
export default HelloWorld;
В ту же папку добавим файл
index.js:import React from 'react';
import { render } from 'react-dom';
import HelloWorld from './HelloWorld';
render(<HelloWorld />, document.getElementById('root'));
И наконец — добавим в
package.json описания скриптов для запуска (start) и сборки (build) проекта:"scripts": {
    "start": "webpack-dev-server --mode=development --open --hot",
    "build": "webpack --mode=production"
  }
Вот и всё. Теперь в нашем распоряжении имеется работоспособная заготовка React-приложения. Убедиться в этом можно, выполнив команды
npm start и npm run build.Теперь давайте оснастим нашу систему возможностью подготовки шаблона проекта с помощью единственной команды. То есть — воссоздадим одну из сильных сторон CRA. Мы собираемся использовать исполняемый JS-файл, который будет вызываться при вводе соответствующей команды в командной строке. Например, подобная команда может выглядеть так:
reactjs-boilerplate new-projectДля реализации этой идеи мы собираемся воспользоваться разделом bin файла
package.json.Сначала установим пакет fs-extra:
npm i fs-extraТеперь создадим исполняемый JS-файл
start.js, который будет расположен в папке bin нашего проекта. Поместим в этот файл следующий код:#!/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));
      }
    );
  }
);
Теперь давайте свяжем этот исполняемый JS-файл с командой из
package.json:"bin": {
    "your-boilerplate-name": "./bin/start.js"
  }
Создадим локальную связь для пакета:
npm link
Теперь, после выполнения этой команды, если в терминале выполнить команду вида
your-boilerplate-name my-app, будет вызван наш исполняемый файл start.js. Он создаст новую папку с именем my-app, скопирует в неё файлы package.json, webpack.config.js и .gitignore, а так же папку src, и установит зависимости нового проекта my-app.Замечательно. Теперь всё это работает на вашем компьютере и позволяет вам, пользуясь единственной командой, создавать базовые React-проекты, обладающие собственной конфигурацией сборки.
Вы можете пойти дальше и опубликовать свой шаблон в реестре npm. Для того чтобы это сделать, сначала надо отправить проект в GitHub-репозиторий. А дальше — следуйте этим инструкциям.
Примите поздравления! Только что мы, буквально за несколько минут, создали альтернативу
create-react-app. Наше решение не перегружено ненужными возможностями (в проекты, созданные на его основе, можно добавлять зависимости по мере возникновения необходимости в них). Пользуясь им, можно легко подстраивать конфигурацию сборки проекта под свои нужды.Конечно, наше решение минималистично. Проекты, создаваемые на его основе, нельзя признать готовыми к использованию в продакшне. Для того чтобы подготовить их к реальной работе, нужно оснастить наш шаблон некоторыми настройками Webpack, ответственными за оптимизацию сборок проектов.
Я подготовил шаблон reactjs-boilerplate, позволяющий создавать проекты, готовые к работе в продакшне. Тут используется соответствующая конфигурация сборки, линтинг и хуки, ответственные за проверку проекта перед созданием коммитов. Испытайте этот шаблон. Если у вас появятся какие-то идеи по его совершенствованию, или если вы решите сделать вклад в его разработку — присоединяйтесь к работе над ним.
Итоги
Вот о чём мы говорили в этом материале:
- Мы разобрали плюсы и минусы 
create-react-app. - Мы реализовали в своём проекте полезную возможность CRA по созданию заготовок React-приложений одной командой. А от недостатков CRA мы избавились.
 - Мы оснастили свой проект минимальными конфигурациями Webpack и Babel, необходимыми для сборки и запуска React-приложений.
 - Мы создали React-компонент 
HelloWorld.js, предусмотрели возможность сборки проекта и его запуска с использованием сервера разработки. - Мы создали исполняемый JS-файл и связали его с соответствующей командой, воспользовавшись разделом 
binфайлаpackage.json. - Мы воспользовались командой 
npm linkс целью создания локальной связи для нашего проекта и получения возможности создавать новые базовые проекты на его основе с помощью единственной команды. 
Надеюсь, этот материал поможет вам в деле разработки проектов, основанных на React.
Пользуетесь ли вы create-react-app при создании новых React-проектов?

          
 
stardust_kid
Зачем мучиться с webpack, когда есть parcel.js?
Kroid
А вторая версия уже вышла? В первой немало проблем было для не совсем стандартных случаев, вроде тех, когда нужно поддерживать несколько index.html страниц.