Ejecting и ручная настройка CRA

Архитектура CRA и скрытая конфигурация сборки

В основе Create React App лежит абстракция над инструментами сборки, где основную роль играет Webpack, Babel, ESLint и набор вспомогательных плагинов. Вся конфигурация инкапсулирована внутри пакета react-scripts, что избавляет от необходимости вручную настраивать окружение.

Внутри node_modules/react-scripts находится полностью готовая система сборки, включающая:

  • конфигурацию Webpack для development и production режимов
  • настройки Babel для трансформации JSX и современного JavaScript
  • конфигурацию ESLint
  • обработку CSS, изображений и статических ресурсов
  • dev server с hot reload

Основная идея заключается в том, что пользователь не взаимодействует с Webpack напрямую, а работает через ограниченный набор CLI-команд:

  • react-scripts start
  • react-scripts build
  • react-scripts test
  • react-scripts eject

Суть Ejecting и необратимость операции

Команда eject выполняет извлечение всей внутренней конфигурации CRA в проект пользователя. После выполнения происходит копирование всех скрытых конфигурационных файлов в корень проекта.

Ключевые последствия:

  • исчезает зависимость от react-scripts
  • появляется полный контроль над Webpack, Babel, ESLint
  • структура проекта становится более сложной
  • операция является необратимой без восстановления из git

После eject проект фактически превращается в классическое React-приложение с полностью ручной сборкой.

Что происходит при выполнении eject

При запуске:

npm run eject

или

yarn eject

выполняются следующие действия:

  1. Копируются конфигурации Webpack:

    • config/webpack.config.js
    • config/webpackDevServer.config.js
  2. Генерируются окружения сборки:

    • config/env.js
    • .env-обработка остается, но расширяется
  3. Переносятся скрипты сборки:

    • scripts/build.js
    • scripts/start.js
    • scripts/test.js
  4. Добавляются зависимости в package.json, которые ранее были скрыты внутри react-scripts

  5. Удаляется react-scripts как единая абстракция

Структура проекта после eject

После извлечения проект обычно принимает следующий вид:

project/
  config/
    webpack.config.js
    webpackDevServer.config.js
    paths.js
    modules.js
    env.js
  scripts/
    start.js
    build.js
    test.js
  src/
  package.json

Каждый файл конфигурации становится доступным для прямого редактирования, что радикально меняет модель разработки.

Основной Webpack-конфиг после eject

Файл webpack.config.js становится центральным элементом всей сборки. Он содержит несколько крупных секций:

Режимы сборки

Конфигурация разделяется на production и development:

  • development:

    • включён source-map
    • активен hot reload
    • отключена агрессивная минификация
  • production:

    • включена оптимизация бандлов
    • tree shaking
    • минификация через Terser
    • извлечение CSS

Entry point

entry: [
  isEnvDevelopment && require.resolve('react-dev-utils/webpackHotDevClient'),
  paths.appIndexJs,
].filter(Boolean)

Здесь видно, что dev-сборка включает дополнительный клиент для hot reload.

Output

output: {
  path: paths.appBuild,
  filename: 'static/js/[name].[contenthash:8].js',
  publicPath: '/',
}

Используется contenthash для кеширования в production-сборке.

Loaders

Основные loader-цепочки:

  • Babel loader:

    • обработка JSX
    • транспиляция ES6+
  • Style loaders:

    • CSS
    • SASS (если подключен)
    • PostCSS
  • File loader:

    • изображения
    • шрифты
    • медиа

Пример Babel rule

{
  test: /\.(js|jsx|ts|tsx)$/,
  include: paths.appSrc,
  loader: require.resolve('babel-loader'),
  options: {
    presets: [require.resolve('babel-preset-react-app')],
    cacheDirectory: true,
  },
}

Разделение конфигурации: dev и prod

CRA после eject использует функцию, возвращающую конфигурацию:

module.exports = function (webpackEnv) {
  const isEnvDevelopment = webpackEnv === 'development';
  const isEnvProduction = webpackEnv === 'production';

Это позволяет динамически строить конфиг в зависимости от режима.

Hot Module Replacement

В development режиме подключается HMR:

new webpack.HotModuleReplacementPlugin()

и dev client:

react-dev-utils/webpackHotDevClient

Это обеспечивает обновление модулей без полной перезагрузки страницы.

Source maps и отладка

CRA использует разные стратегии source maps:

  • development: cheap-module-source-map
  • production: source-map (или отключён в зависимости от флага)

Это влияет на скорость сборки и качество отладки.

ESLint после eject

Конфигурация ESLint также становится явной:

  • .eslintrc
  • встроенные правила React
  • поддержка hooks rules
  • строгая проверка зависимостей эффектов

CSS pipeline

Обработка CSS включает цепочку:

  1. style-loader или MiniCssExtractPlugin
  2. css-loader
  3. postcss-loader

PostCSS конфигурация включает:

  • autoprefixer
  • нормализацию CSS
  • оптимизацию в production

Оптимизация production сборки

Webpack после eject включает:

Code splitting

optimization: {
  splitChunks: {
    chunks: 'all',
  },
}

Это позволяет разделять vendor и application код.

Minification

Используется TerserPlugin:

  • удаление console.log (опционально)
  • минификация JS
  • tree shaking

CSS extraction

MiniCssExtractPlugin выносит стили в отдельные файлы:

static/css/main.[contenthash].css

Работа с alias и путями

CRA использует модуль paths.js, где определяются:

  • корень приложения
  • папка src
  • build директория
  • публичные ресурсы

Пример:

module.exports = {
  appSrc: resolveApp('src'),
  appBuild: resolveApp('build'),
}

Проблемы подхода eject

После извлечения конфигурации появляются системные сложности:

  • рост сложности поддержки Webpack-конфига
  • необходимость ручного обновления зависимостей
  • потеря стандартизированных обновлений CRA
  • дублирование логики сборки

Любое изменение теперь требует понимания всей цепочки сборки, а не только React-кода.

Альтернативы eject

Вместо полного eject часто используют частичную кастомизацию:

  • настройка через переменные окружения
  • расширение через сторонние обёртки Webpack
  • модификация конфигурации без извлечения (через override-слои)

Это позволяет сохранить управление CRA, не теряя обновляемость базовой системы.