create-react-app #

create-react-app (简称 CRA) 是一个流行的 React 脚手架,帮助开发者快速创建 React 单页面应用,而无需关心配置。

默认情况下,CRA 隐藏了大多数的配置(如 Webpack, Babel 等)。这意味着你的项目目录中不会直接看到这些配置文件。这种设计的目的是为了简化项目的结构和设置,允许开发者专注于编写 React 代码。

但在某些情况下,你可能需要更细粒度的配置控制。例如,你可能想要修改 Webpack 的加载器规则或插件,这时你就需要访问隐藏的配置文件。为了实现这一点,CRA 提供了一个命令:npm run eject

npm run eject 的详细解释:

  1. 不可逆操作:首先,应该明确的是,eject 是一个不可逆的操作。一旦你执行了这个命令,就没有回头路了。

  2. 显示配置:当你运行 npm run eject,CRA 将会「弹出」配置和依赖,将其从内部移动到你的项目目录中。这意味着所有的 Webpack 设置、Babel 配置、linters 等都会出现在你的项目目录中。

  3. 手动维护:从此,你将需要手动维护这些配置。如果在未来 React 脚手架有任何更新,你需要自己合并这些更改。

  4. 为什么使用 eject:尽管 eject 提供了很大的灵活性,但这并不意味着每个人都应该使用它。只有当你确实需要对 CRA 默认的配置进行修改时,才应该考虑使用 eject。

    使用方法:

  5. 为了安全起见,确保你的所有更改都已提交到 git(或其他版本控制系统)。

  6. 运行 npm run eject
  7. 查看项目目录,你会看到一些新的文件夹和文件,例如 configscripts

总结:

尽管 npm run eject 提供了更多的控制权,但它也带来了更多的责任。除非你有充分的理由并且了解你正在做什么,否则建议不要轻易使用这个命令。很多时候,你可以使用其他解决方案(如环境变量、或使用第三方插件)来达到目的,而不是完全弹出配置。

配置文件 #

webpack.config.js #

config\webpack.config.js

// 引入文件系统模块
const fs = require('fs');
// 引入路径模块
const path = require('path');
// 引入webpack模块
const webpack = require('webpack');
// 引入resolve模块
const resolve = require('resolve');
// 引入HtmlWebpackPlugin模块
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 引入CaseSensitivePathsPlugin模块
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
// 引入InlineChunkHtmlPlugin模块
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
// 引入TerserPlugin模块
const TerserPlugin = require('terser-webpack-plugin');
// 引入MiniCssExtractPlugin模块
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// 引入CssMinimizerPlugin模块
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
// 引入WebpackManifestPlugin模块
const {
  WebpackManifestPlugin
} = require('webpack-manifest-plugin');
// 引入InterpolateHtmlPlugin模块
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
// 引入WorkboxWebpackPlugin模块
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
// 引入ModuleScopePlugin模块
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
// 引入getCSSModuleLocalIdent模块
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
// 引入ESLintPlugin模块
const ESLintPlugin = require('eslint-webpack-plugin');
// 引入paths模块
const paths = require('./paths');
// 引入modules模块
const modules = require('./modules');
// 引入getClientEnvironment模块
const getClientEnvironment = require('./env');
// 引入ModuleNotFoundPlugin模块
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
// 根据环境变量TSC_COMPILE_ON_ERROR的值选择引入的ForkTsCheckerWebpackPlugin模块
const ForkTsCheckerWebpackPlugin = process.env.TSC_COMPILE_ON_ERROR === 'true' ? require('react-dev-utils/ForkTsCheckerWarningWebpackPlugin') : require('react-dev-utils/ForkTsCheckerWebpackPlugin');
// 引入ReactRefreshWebpackPlugin模块
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
// 引入createEnvironmentHash模块
const createEnvironmentHash = require('./webpack/persistentCache/createEnvironmentHash');
// 根据环境变量GENERATE_SOURCEMAP的值决定是否使用源映射
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// 引入react-refresh/runtime模块
const reactRefreshRuntimeEntry = require.resolve('react-refresh/runtime');
// 引入@pmmmwh/react-refresh-webpack-plugin模块
const reactRefreshWebpackPluginRuntimeEntry = require.resolve('@pmmmwh/react-refresh-webpack-plugin');
// 引入babel-preset-react-app模块
const babelRuntimeEntry = require.resolve('babel-preset-react-app');
// 引入@babel/runtime/helpers/esm/assertThisInitialized模块
const babelRuntimeEntryHelpers = require.resolve('@babel/runtime/helpers/esm/assertThisInitialized', {
  paths: [babelRuntimeEntry]
});
// 引入@babel/runtime/regenerator模块
const babelRuntimeRegenerator = require.resolve('@babel/runtime/regenerator', {
  paths: [babelRuntimeEntry]
});
// 判断是否内联运行时块
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
// 判断是否将错误作为警告发出
const emitErrorsAsWarnings = process.env.ESLINT_NO_DEV_ERRORS === 'true';
// 判断是否禁用ESLintPlugin
const disableESLintPlugin = process.env.DISABLE_ESLINT_PLUGIN === 'true';
// 设置图片内联大小限制
const imageInlineSizeLimit = parseInt(process.env.IMAGE_INLINE_SIZE_LIMIT || '10000');
// 判断是否使用TypeScript
const useTypeScript = fs.existsSync(paths.appTsConfig);
// 判断是否使用Tailwind
const useTailwind = fs.existsSync(path.join(paths.appPath, 'tailwind.config.js'));
// 设置服务工作源路径
const swSrc = paths.swSrc;
// 设置css正则表达式
const cssRegex = /\.css$/;
// 设置css模块正则表达式
const cssModuleRegex = /\.module\.css$/;
// 设置sass正则表达式
const sassRegex = /\.(scss|sass)$/;
// 设置sass模块正则表达式
const sassModuleRegex = /\.module\.(scss|sass)$/;
// 判断是否有Jsx运行时
const hasJsxRuntime = (() => {
  if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') {
    return false;
  }
  try {
    require.resolve('react/jsx-runtime');
    return true;
  } catch (e) {
    return false;
  }
})();
module.exports = function (webpackEnv) {
  // 判断是否为开发环境
  const isEnvDevelopment = webpackEnv === 'development';
  // 判断是否为生产环境
  const isEnvProduction = webpackEnv === 'production';
  // 判断是否为生产环境配置文件
  const isEnvProductionProfile = isEnvProduction && process.argv.includes('--profile');
  // 获取客户端环境
  const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
  // 判断是否使用React刷新
  const shouldUseReactRefresh = env.raw.FAST_REFRESH;
  // 获取样式加载器
  const getStyleLoaders = (cssOptions, preProcessor) => {
    // 定义加载器
    const loaders = [
      // 判断是否为开发环境并引入样式加载器
      isEnvDevelopment && require.resolve('style-loader'),
      // 判断是否为生产环境并设置加载器选项
      isEnvProduction && {
        loader: MiniCssExtractPlugin.loader,
        options: paths.publicUrlOrPath.startsWith('.') ? {
          publicPath: '../../'
        } : {}
      },
      // 引入css加载器并设置选项
      {
        loader: require.resolve('css-loader'),
        options: cssOptions
      },
      // 引入postcss加载器并设置选项
      {
        loader: require.resolve('postcss-loader'),
        options: {
          postcssOptions: {
            ident: 'postcss',
            config: false,
            plugins: !useTailwind ? ['postcss-flexbugs-fixes', ['postcss-preset-env', {
              autoprefixer: {
                flexbox: 'no-2009'
              },
              stage: 3
            }], 'postcss-normalize'] : ['tailwindcss', 'postcss-flexbugs-fixes', ['postcss-preset-env', {
              autoprefixer: {
                flexbox: 'no-2009'
              },
              stage: 3
            }]]
          },
          // 判断是否使用源映射
          sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment
        }
      }
    ].filter(Boolean);
    // 如果存在预处理器
    if (preProcessor) {
      // 向加载器中添加选项
      loaders.push({
        loader: require.resolve('resolve-url-loader'),
        options: {
          sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
          root: paths.appSrc
        }
      },
        // 引入预处理器并设置选项
        {
          loader: require.resolve(preProcessor),
          options: {
            sourceMap: true
          }
        });
    }
    // 返回加载器
    return loaders;
  };
  return {
    // 设置目标为浏览器列表
    target: ['browserslist'],
    // 设置统计为错误警告
    stats: 'errors-warnings',
    // 根据环境设置模式
    mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
    // 如果是生产环境,设置bail为true
    bail: isEnvProduction,
    // 根据环境设置devtool
    devtool: isEnvProduction ? shouldUseSourceMap ? 'source-map' : false : isEnvDevelopment && 'cheap-module-source-map',
    // 设置入口为appIndexJs
    entry: paths.appIndexJs,
    // 设置输出
    output: {
      // 设置路径为appBuild
      path: paths.appBuild,
      // 如果是开发环境,设置pathinfo为true
      pathinfo: isEnvDevelopment,
      // 根据环境设置filename
      filename: isEnvProduction ? 'static/js/[name].[contenthash:8].js' : isEnvDevelopment && 'static/js/bundle.js',
      // 根据环境设置chunkFilename
      chunkFilename: isEnvProduction ? 'static/js/[name].[contenthash:8].chunk.js' : isEnvDevelopment && 'static/js/[name].chunk.js',
      // 设置assetModuleFilename
      assetModuleFilename: 'static/media/[name].[hash][ext]',
      // 设置publicPath
      publicPath: paths.publicUrlOrPath,
      // 根据环境设置devtoolModuleFilenameTemplate
      devtoolModuleFilenameTemplate: isEnvProduction ? info => path.relative(paths.appSrc, info.absoluteResourcePath).replace(/\\/g, '/') : isEnvDevelopment && (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'))
    },
    // 设置缓存
    cache: {
      // 设置类型为文件系统
      type: 'filesystem',
      // 设置版本为环境哈希
      version: createEnvironmentHash(env.raw),
      // 设置缓存目录为appWebpackCache
      cacheDirectory: paths.appWebpackCache,
      // 设置存储为pack
      store: 'pack',
      // 设置构建依赖
      buildDependencies: {
        defaultWebpack: ['webpack/lib/'],
        config: [__filename],
        tsconfig: [paths.appTsConfig, paths.appJsConfig].filter(f => fs.existsSync(f))
      }
    },
    // 设置基础设施日志
    infrastructureLogging: {
      // 设置级别为none
      level: 'none'
    },
    // 设置优化
    optimization: {
      // 如果是生产环境,设置minimize为true
      minimize: isEnvProduction,
      // 设置minimizer
      minimizer: [new TerserPlugin({
        terserOptions: {
          parse: {
            ecma: 8
          },
          compress: {
            ecma: 5,
            warnings: false,
            comparisons: false,
            inline: 2
          },
          mangle: {
            safari10: true
          },
          keep_classnames: isEnvProductionProfile,
          keep_fnames: isEnvProductionProfile,
          output: {
            ecma: 5,
            comments: false,
            ascii_only: true
          }
        }
      }), new CssMinimizerPlugin()]
    },
    // 设置解析
    resolve: {
      // 设置模块
      modules: ['node_modules', paths.appNodeModules].concat(modules.additionalModulePaths || []),
      // 设置扩展名
      extensions: paths.moduleFileExtensions.map(ext => `.${ext}`).filter(ext => useTypeScript || !ext.includes('ts')),
      // 设置别名
      alias: {
        'react-native': 'react-native-web',
        ...(isEnvProductionProfile && {
          'react-dom$': 'react-dom/profiling',
          'scheduler/tracing': 'scheduler/tracing-profiling'
        }),
        ...(modules.webpackAliases || {})
      },
      // 设置插件
      plugins: [new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson, reactRefreshRuntimeEntry, reactRefreshWebpackPluginRuntimeEntry, babelRuntimeEntry, babelRuntimeEntryHelpers, babelRuntimeRegenerator])]
    },
    module: {
      // 设置严格导出存在性为true
      strictExportPresence: true,
      // 设置规则
      rules: [
        // 如果应使用源映射,则强制执行'pre'
        shouldUseSourceMap && {
          enforce: 'pre',
          exclude: /@babel(?:\/|\\{1,2})runtime/,
          // 测试.js、.mjs、.jsx、.ts、.tsx、.css文件
          test: /\.(js|mjs|jsx|ts|tsx|css)$/,
          // 加载器设置为source-map-loader
          loader: require.resolve('source-map-loader')
        },
        {
          oneOf: [
            // 测试.avif文件
            {
              test: [/\.avif$/],
              type: 'asset',
              mimetype: 'image/avif',
              parser: {
                dataUrlCondition: {
                  // 设置最大尺寸为图片内联大小限制
                  maxSize: imageInlineSizeLimit
                }
              }
            },
            // 测试.bmp、.gif、.jpeg、.jpg、.png文件
            {
              test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
              type: 'asset',
              parser: {
                dataUrlCondition: {
                  // 设置最大尺寸为图片内联大小限制
                  maxSize: imageInlineSizeLimit
                }
              }
            },
            // 测试.svg文件
            {
              test: /\.svg$/,
              use: [
                // 加载器设置为@svgr/webpack
                {
                  loader: require.resolve('@svgr/webpack'),
                  options: {
                    prettier: false,
                    svgo: false,
                    svgoConfig: {
                      plugins: [
                        {
                          removeViewBox: false
                        }
                      ]
                    },
                    titleProp: true,
                    ref: true
                  }
                },
                // 加载器设置为file-loader
                {
                  loader: require.resolve('file-loader'),
                  options: {
                    name: 'static/media/[name].[hash].[ext]'
                  }
                }
              ],
              issuer: {
                and: [/\.(ts|tsx|js|jsx|md|mdx)$/]
              }
            },
            // 测试.js、.mjs、.jsx、.ts、.tsx文件
            {
              test: /\.(js|mjs|jsx|ts|tsx)$/,
              include: paths.appSrc,
              // 加载器设置为babel-loader
              loader: require.resolve('babel-loader'),
              options: {
                customize: require.resolve('babel-preset-react-app/webpack-overrides'),
                presets: [
                  [
                    require.resolve('babel-preset-react-app'),
                    {
                      // 运行时设置为有Jsx运行时 ? 'automatic' : 'classic'
                      runtime: hasJsxRuntime ? 'automatic' : 'classic',
                    }
                  ]
                ],
                plugins: [
                  // 如果是开发环境并且应使用React刷新,则加载器设置为react-refresh/babel
                  isEnvDevelopment && shouldUseReactRefresh && require.resolve('react-refresh/babel')
                ].filter(Boolean),
                cacheDirectory: true,
                cacheCompression: false,
                // 如果是生产环境,设置compact为true
                compact: isEnvProduction
              }
            },
            // 测试.js、.mjs文件
            {
              test: /\.(js|mjs)$/,
              exclude: /@babel(?:\/|\\{1,2})runtime/,
              // 加载器设置为babel-loader
              loader: require.resolve('babel-loader'),
              options: {
                babelrc: false,
                configFile: false,
                compact: false,
                presets: [
                  [
                    require.resolve('babel-preset-react-app/dependencies'),
                    {
                      helpers: true
                    }
                  ]
                ],
                cacheDirectory: true,
                cacheCompression: false,
                sourceMaps: shouldUseSourceMap,
                inputSourceMap: shouldUseSourceMap
              }
            },
            // 测试.css文件
            {
              test: cssRegex,
              exclude: cssModuleRegex,
              use: getStyleLoaders({
                importLoaders: 1,
                sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
                modules: {
                  mode: 'icss'
                }
              }),
              sideEffects: true
            },
            // 测试.module.css文件
            {
              test: cssModuleRegex,
              use: getStyleLoaders({
                importLoaders: 1,
                sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
                modules: {
                  mode: 'local',
                  getLocalIdent: getCSSModuleLocalIdent
                }
              })
            },
            // 测试.scss、.sass文件
            {
              test: sassRegex,
              exclude: sassModuleRegex,
              use: getStyleLoaders({
                importLoaders: 3,
                sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
                modules: {
                  mode: 'icss'
                }
              }, 'sass-loader'),
              sideEffects: true
            },
            // 测试.module.scss、.module.sass文件
            {
              test: sassModuleRegex,
              use: getStyleLoaders({
                importLoaders: 3,
                sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
                modules: {
                  mode: 'local',
                  getLocalIdent: getCSSModuleLocalIdent
                }
              }, 'sass-loader')
            },
            // 排除所有文件
            {
              exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
              type: 'asset/resource'
            }
          ]
        }
      ].filter(Boolean)
    },
    // 设置插件
    plugins: [
      // 新建HtmlWebpackPlugin插件
      new HtmlWebpackPlugin(Object.assign({}, {
        // 设置注入为true
        inject: true,
        // 设置模板为appHtml路径
        template: paths.appHtml
      }, isEnvProduction ? {
        // 如果是生产环境,设置压缩选项
        minify: {
          // 移除注释
          removeComments: true,
          // 折叠空白
          collapseWhitespace: true,
          // 移除冗余属性
          removeRedundantAttributes: true,
          // 使用短文档类型
          useShortDoctype: true,
          // 移除空属性
          removeEmptyAttributes: true,
          // 移除样式链接类型属性
          removeStyleLinkTypeAttributes: true,
          // 保持关闭斜杠
          keepClosingSlash: true,
          // 压缩JS
          minifyJS: true,
          // 压缩CSS
          minifyCSS: true,
          // 压缩URLs
          minifyURLs: true
        }
      } : undefined)),
      // 如果是生产环境并且应内联运行时块,则新建InlineChunkHtmlPlugin插件
      isEnvProduction && shouldInlineRuntimeChunk && new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
      // 新建InterpolateHtmlPlugin插件
      new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
      // 新建ModuleNotFoundPlugin插件
      new ModuleNotFoundPlugin(paths.appPath),
      // 新建webpack.DefinePlugin插件
      new webpack.DefinePlugin(env.stringified),
      // 如果是开发环境并且应使用React刷新,则新建ReactRefreshWebpackPlugin插件
      isEnvDevelopment && shouldUseReactRefresh && new ReactRefreshWebpackPlugin({
        // 设置覆盖为false
        overlay: false
      }),
      // 如果是开发环境,则新建CaseSensitivePathsPlugin插件
      isEnvDevelopment && new CaseSensitivePathsPlugin(),
      // 如果是生产环境,则新建MiniCssExtractPlugin插件
      isEnvProduction && new MiniCssExtractPlugin({
        // 设置文件名
        filename: 'static/css/[name].[contenthash:8].css',
        // 设置块文件名
        chunkFilename: 'static/css/[name].[contenthash:8].chunk.css'
      }),
      // 新建WebpackManifestPlugin插件
      new WebpackManifestPlugin({
        // 设置文件名为asset-manifest.json
        fileName: 'asset-manifest.json',
        // 设置公共路径
        publicPath: paths.publicUrlOrPath,
        // 设置生成函数
        generate: (seed, files, entrypoints) => {
          // 定义manifest文件
          const manifestFiles = files.reduce((manifest, file) => {
            // 设置manifest文件的路径
            manifest[file.name] = file.path;
            // 返回manifest
            return manifest;
          }, seed);
          // 定义入口点文件
          const entrypointFiles = entrypoints.main.filter(fileName => !fileName.endsWith('.map'));
          // 返回文件和入口点
          return {
            files: manifestFiles,
            entrypoints: entrypointFiles
          };
        }
      }),
      // 新建webpack.IgnorePlugin插件
      new webpack.IgnorePlugin({
        // 设置资源正则表达式为/^\.\/locale$/
        resourceRegExp: /^\.\/locale$/,
        // 设置上下文正则表达式为/moment$/
        contextRegExp: /moment$/
      }),
      // 如果是生产环境并且存在swSrc,则新建WorkboxWebpackPlugin.InjectManifest插件
      isEnvProduction && fs.existsSync(swSrc) && new WorkboxWebpackPlugin.InjectManifest({
        // 设置swSrc
        swSrc,
        // 设置dontCacheBustURLsMatching为/\.[0-9a-f]{8}\./
        dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./,
        // 设置排除项
        exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/],
        // 设置最大文件缓存大小为5 * 1024 * 1024
        maximumFileSizeToCacheInBytes: 5 * 1024 * 1024
      }),
      // 如果使用TypeScript,则新建ForkTsCheckerWebpackPlugin插件
      useTypeScript && new ForkTsCheckerWebpackPlugin({
        // 如果是开发环境,设置异步为true
        async: isEnvDevelopment,
        // 设置TypeScript选项
        typescript: {
          // 设置TypeScript路径
          typescriptPath: resolve.sync('typescript', {
            // 设置基础目录为appNodeModules
            basedir: paths.appNodeModules
          }),
          // 设置配置覆盖
          configOverwrite: {
            // 设置编译器选项
            compilerOptions: {
              // 根据环境设置源映射
              sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
              // 设置跳过库检查为true
              skipLibCheck: true,
              // 设置内联源映射为false
              inlineSourceMap: false,
              // 设置声明映射为false
              declarationMap: false,
              // 设置不发出为true
              noEmit: true,
              // 设置增量为true
              incremental: true,
              // 设置tsBuildInfoFile为appTsBuildInfoFile
              tsBuildInfoFile: paths.appTsBuildInfoFile
            }
          },
          // 设置上下文为appPath
          context: paths.appPath,
          // 设置诊断选项
          diagnosticOptions: {
            // 设置语法为true
            syntactic: true
          },
          // 设置模式为write-references
          mode: 'write-references'
        },
        // 设置问题
        issue: {
          // 设置包含项
          include: [{
            // 设置文件为'../**/src/**/*.{ts,tsx}'
            file: '../**/src/**/*.{ts,tsx}'
          }, {
            // 设置文件为'**/src/**/*.{ts,tsx}'
            file: '**/src/**/*.{ts,tsx}'
          }],
          // 设置排除项
          exclude: [{
            // 设置文件为'**/src/**/tests/**'
            file: '**/src/**/tests/**'
          }, {
            // 设置文件为'**/src/**/?(*.){spec|test}.*'
            file: '**/src/**/?(*.){spec|test}.*'
          }, {
            // 设置文件为'**/src/setupProxy.*'
            file: '**/src/setupProxy.*'
          }, {
            file: '**/src/setupTests.*'
          }]
        },
        // 设置日志记录器
        logger: {
          // 设置基础设施为'silent'
          infrastructure: 'silent'
        }
      }), 
      // 如果没有禁用ESLintPlugin,则新建ESLintPlugin插件
      !disableESLintPlugin && new ESLintPlugin({
        // 设置扩展名
        extensions: ['js', 'mjs', 'jsx', 'ts', 'tsx'],
        // 设置格式化器为'react-dev-utils/eslintFormatter'
        formatter: require.resolve('react-dev-utils/eslintFormatter'),
        // 设置ESLint路径为'eslint'
        eslintPath: require.resolve('eslint'),
        // 如果是开发环境并且将错误作为警告发出,则设置failOnError为false
        failOnError: !(isEnvDevelopment && emitErrorsAsWarnings),
        // 设置上下文为appSrc
        context: paths.appSrc,
        // 设置缓存为true
        cache: true,
        // 设置缓存位置为appNodeModules下的'.cache/.eslintcache'
        cacheLocation: path.resolve(paths.appNodeModules, '.cache/.eslintcache'),
        // 设置当前工作目录为appPath
        cwd: paths.appPath,
        // 设置相对于__dirname解析插件
        resolvePluginsRelativeTo: __dirname,
        // 设置基础配置
        baseConfig: {
          // 设置扩展为'eslint-config-react-app/base'
          extends: [require.resolve('eslint-config-react-app/base')],
          // 设置规则
          rules: {
            // 如果没有Jsx运行时,则设置'react/react-in-jsx-scope'为'error'
            ...(!hasJsxRuntime && {
              'react/react-in-jsx-scope': 'error'
            })
          }
        }
      })].filter(Boolean),
    // 设置性能为false
    performance: false
  };
};

env.js #

config\env.js

// 引入fs模块
const fs = require('fs');
// 引入path模块
const path = require('path');
// 引入paths模块
const paths = require('./paths');
// 删除paths模块的缓存
delete require.cache[require.resolve('./paths')];
// 设置环境变量NODE_ENV为'development'
process.env.NODE_ENV = 'development';
// 获取环境变量NODE_ENV的值
const NODE_ENV = process.env.NODE_ENV;
// 如果环境变量NODE_ENV不存在,抛出错误
if (!NODE_ENV) {
  throw new Error('The NODE_ENV environment variable is required but was not specified.');
}
// 定义dotenvFiles,根据NODE_ENV的值获取对应的配置文件路径
const dotenvFiles = [`${paths.dotenv}.${NODE_ENV}.local`, NODE_ENV !== 'test' && `${paths.dotenv}.local`, `${paths.dotenv}.${NODE_ENV}`, paths.dotenv].filter(Boolean);
// 遍历dotenvFiles,如果文件存在,就引入该文件
dotenvFiles.forEach(dotenvFile => {
  if (fs.existsSync(dotenvFile)) {
    require('dotenv-expand')(require('dotenv').config({
      path: dotenvFile
    }));
  }
});
// 获取当前工作目录的绝对路径
const appDirectory = fs.realpathSync(process.cwd());
// 设置环境变量NODE_PATH的值
process.env.NODE_PATH = (process.env.NODE_PATH || '').split(path.delimiter).filter(folder => folder && !path.isAbsolute(folder)).map(folder => path.resolve(appDirectory, folder)).join(path.delimiter);
// 定义REACT_APP正则表达式
const REACT_APP = /^REACT_APP_/i;
// 定义getClientEnvironment函数,用于获取客户端环境
function getClientEnvironment(publicUrl) {
  // 获取所有满足REACT_APP正则的环境变量
  const raw = Object.keys(process.env).filter(key => REACT_APP.test(key)).reduce((env, key) => {
    // 将环境变量添加到env对象中
    env[key] = process.env[key];
    // 返回env对象
    return env;
  }, {
    // 初始化env对象
    NODE_ENV: process.env.NODE_ENV || 'development',
    PUBLIC_URL: publicUrl,
    WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST,
    WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH,
    WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT,
    FAST_REFRESH: process.env.FAST_REFRESH !== 'false'
  });
  // 将raw对象中的值转为字符串
  const stringified = {
    'process.env': Object.keys(raw).reduce((env, key) => {
      env[key] = JSON.stringify(raw[key]);
      return env;
    }, {})
  };
  // 返回raw和stringified对象
  return {
    raw,
    stringified
  };
}
// 导出getClientEnvironment函数
module.exports = getClientEnvironment;

getHttpsConfig.js #

config\getHttpsConfig.js

// 引入fs模块
const fs = require('fs');
// 引入path模块
const path = require('path');
// 引入crypto模块
const crypto = require('crypto');
// 引入chalk模块
const chalk = require('react-dev-utils/chalk');
// 引入paths模块
const paths = require('./paths');
// 定义validateKeyAndCerts函数
function validateKeyAndCerts({
  cert,
  key,
  keyFile,
  crtFile
}) {
  // 定义encrypted变量
  let encrypted;
  // 尝试执行以下代码
  try {
    // 使用公钥加密测试字符串
    encrypted = crypto.publicEncrypt(cert, Buffer.from('test'));
  // 如果出现错误,抛出异常
  } catch (err) {
    throw new Error(`The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`);
  }
  // 尝试执行以下代码
  try {
    // 使用私钥解密encrypted
    crypto.privateDecrypt(key, encrypted);
  // 如果出现错误,抛出异常
  } catch (err) {
    throw new Error(`The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${err.message}`);
  }
}
// 定义readEnvFile函数
function readEnvFile(file, type) {
  // 如果文件不存在,抛出异常
  if (!fs.existsSync(file)) {
    throw new Error(`You specified ${chalk.cyan(type)} in your env, but the file "${chalk.yellow(file)}" can't be found.`);
  }
  // 返回文件的内容
  return fs.readFileSync(file);
}
// 定义getHttpsConfig函数
function getHttpsConfig() {
  // 从环境变量中获取SSL_CRT_FILE、SSL_KEY_FILE和HTTPS的值
  const {
    SSL_CRT_FILE,
    SSL_KEY_FILE,
    HTTPS
  } = process.env;
  // 判断是否启用https
  const isHttps = HTTPS === 'true';
  // 如果启用了https并且SSL_CRT_FILE和SSL_KEY_FILE都存在
  if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) {
    // 获取证书文件和密钥文件的路径
    const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE);
    const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE);
    // 定义config对象
    const config = {
      // 读取证书文件的内容
      cert: readEnvFile(crtFile, 'SSL_CRT_FILE'),
      // 读取密钥文件的内容
      key: readEnvFile(keyFile, 'SSL_KEY_FILE')
    };
    // 验证证书和密钥
    validateKeyAndCerts({
      ...config,
      keyFile,
      crtFile
    });
    // 返回config对象
    return config;
  }
  // 返回isHttps
  return isHttps;
}
// 导出getHttpsConfig函数
module.exports = getHttpsConfig;

modules.js #

config\modules.js

// 引入fs模块
const fs = require('fs');
// 引入path模块
const path = require('path');
// 引入paths模块
const paths = require('./paths');
// 引入chalk模块
const chalk = require('react-dev-utils/chalk');
// 引入resolve模块
const resolve = require('resolve');
// 定义getAdditionalModulePaths函数
function getAdditionalModulePaths(options = {}) {
  // 获取baseUrl
  const baseUrl = options.baseUrl;
  // 如果baseUrl不存在,返回空字符串
  if (!baseUrl) {
    return '';
  }
  // 解析baseUrl的路径
  const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
  // 如果baseUrlResolved相对于paths.appNodeModules的路径为空,返回null
  if (path.relative(paths.appNodeModules, baseUrlResolved) === '') {
    return null;
  }
  // 如果baseUrlResolved相对于paths.appSrc的路径为空,返回[paths.appSrc]
  if (path.relative(paths.appSrc, baseUrlResolved) === '') {
    return [paths.appSrc];
  }
  // 如果baseUrlResolved相对于paths.appPath的路径为空,返回null
  if (path.relative(paths.appPath, baseUrlResolved) === '') {
    return null;
  }
  // 抛出错误
  throw new Error(chalk.red.bold("Your project's `baseUrl` can only be set to `src` or `node_modules`." + ' Create React App does not support other values at this time.'));
}
// 定义getWebpackAliases函数
function getWebpackAliases(options = {}) {
  // 获取baseUrl
  const baseUrl = options.baseUrl;
  // 如果baseUrl不存在,返回空对象
  if (!baseUrl) {
    return {};
  }
  // 解析baseUrl的路径
  const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
  // 如果baseUrlResolved相对于paths.appPath的路径为空,返回{src: paths.appSrc}
  if (path.relative(paths.appPath, baseUrlResolved) === '') {
    return {
      src: paths.appSrc
    };
  }
}
// 定义getJestAliases函数
function getJestAliases(options = {}) {
  // 获取baseUrl
  const baseUrl = options.baseUrl;
  // 如果baseUrl不存在,返回空对象
  if (!baseUrl) {
    return {};
  }
  // 解析baseUrl的路径
  const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
  // 如果baseUrlResolved相对于paths.appPath的路径为空,返回{'^src/(.*)$': '<rootDir>/src/$1'}
  if (path.relative(paths.appPath, baseUrlResolved) === '') {
    return {
      '^src/(.*)$': '<rootDir>/src/$1'
    };
  }
}
// 定义getModules函数
function getModules() {
  // 检查paths.appTsConfig文件是否存在
  const hasTsConfig = fs.existsSync(paths.appTsConfig);
  // 检查paths.appJsConfig文件是否存在
  const hasJsConfig = fs.existsSync(paths.appJsConfig);
  // 如果paths.appTsConfig和paths.appJsConfig文件都存在,抛出错误
  if (hasTsConfig && hasJsConfig) {
    throw new Error('You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.');
  }
  // 定义config变量
  let config;
  // 如果paths.appTsConfig文件存在,读取该文件的配置
  if (hasTsConfig) {
    const ts = require(resolve.sync('typescript', {
      basedir: paths.appNodeModules
    }));
    config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config;
  } else if (hasJsConfig) {
    // 如果paths.appJsConfig文件存在,读取该文件的配置
    config = require(paths.appJsConfig);
  }
  // 如果config不存在,设置为空对象
  config = config || {};
  // 获取compilerOptions
  const options = config.compilerOptions || {};
  // 获取additionalModulePaths
  const additionalModulePaths = getAdditionalModulePaths(options);
  // 返回结果
  return {
    additionalModulePaths: additionalModulePaths,
    webpackAliases: getWebpackAliases(options),
    jestAliases: getJestAliases(options),
    hasTsConfig
  };
}
// 导出getModules函数的结果
module.exports = getModules();

paths.js #

config\paths.js

// 引入path模块
const path = require('path');
// 引入fs模块
const fs = require('fs');
// 引入getPublicUrlOrPath模块
const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath');
// 获取当前工作目录的实际路径
const appDirectory = fs.realpathSync(process.cwd());
// 定义resolveApp函数,用于解析相对路径
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
// 获取公共URL或路径
const publicUrlOrPath = getPublicUrlOrPath(process.env.NODE_ENV === 'development', require(resolveApp('package.json')).homepage, process.env.PUBLIC_URL);
// 获取构建路径
const buildPath = process.env.BUILD_PATH || 'build';
// 定义模块文件扩展名数组
const moduleFileExtensions = ['web.mjs', 'mjs', 'web.js', 'js', 'web.ts', 'ts', 'web.tsx', 'tsx', 'json', 'web.jsx', 'jsx'];
// 定义resolveModule函数,用于解析模块路径
const resolveModule = (resolveFn, filePath) => {
  // 查找存在的扩展名
  const extension = moduleFileExtensions.find(extension => fs.existsSync(resolveFn(`${filePath}.${extension}`)));
  // 如果找到扩展名,返回解析后的文件路径
  if (extension) {
    return resolveFn(`${filePath}.${extension}`);
  }
  // 如果没有找到扩展名,返回.js文件路径
  return resolveFn(`${filePath}.js`);
};
// 导出模块
module.exports = {
  // .env文件路径
  dotenv: resolveApp('.env'),
  // 应用路径
  appPath: resolveApp('.'),
  // 构建路径
  appBuild: resolveApp(buildPath),
  // 公共路径
  appPublic: resolveApp('public'),
  // index.html文件路径
  appHtml: resolveApp('public/index.html'),
  // index.js文件路径
  appIndexJs: resolveModule(resolveApp, 'src/index'),
  // package.json文件路径
  appPackageJson: resolveApp('package.json'),
  // 源代码路径
  appSrc: resolveApp('src'),
  // tsconfig.json文件路径
  appTsConfig: resolveApp('tsconfig.json'),
  // jsconfig.json文件路径
  appJsConfig: resolveApp('jsconfig.json'),
  // yarn.lock文件路径
  yarnLockFile: resolveApp('yarn.lock'),
  // 测试设置文件路径
  testsSetup: resolveModule(resolveApp, 'src/setupTests'),
  // 代理设置文件路径
  proxySetup: resolveApp('src/setupProxy.js'),
  // node_modules路径
  appNodeModules: resolveApp('node_modules'),
  // webpack缓存路径
  appWebpackCache: resolveApp('node_modules/.cache'),
  // tsconfig.tsbuildinfo文件路径
  appTsBuildInfoFile: resolveApp('node_modules/.cache/tsconfig.tsbuildinfo'),
  // service-worker文件路径
  swSrc: resolveModule(resolveApp, 'src/service-worker'),
  // 公共URL或路径
  publicUrlOrPath
};
// 导出模块文件扩展名
module.exports.moduleFileExtensions = moduleFileExtensions;

webpackDevServer.config.js #

config\webpackDevServer.config.js

// 引入fs模块
const fs = require('fs');
// 引入react-dev-utils/evalSourceMapMiddleware模块
const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
// 引入react-dev-utils/noopServiceWorkerMiddleware模块
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
// 引入react-dev-utils/ignoredFiles模块
const ignoredFiles = require('react-dev-utils/ignoredFiles');
// 引入react-dev-utils/redirectServedPathMiddleware模块
const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware');
// 引入./paths模块
const paths = require('./paths');
// 引入./getHttpsConfig模块
const getHttpsConfig = require('./getHttpsConfig');
// 定义host变量,值为环境变量HOST或'0.0.0.0'
const host = process.env.HOST || '0.0.0.0';
// 定义sockHost变量,值为环境变量WDS_SOCKET_HOST
const sockHost = process.env.WDS_SOCKET_HOST;
// 定义sockPath变量,值为环境变量WDS_SOCKET_PATH
const sockPath = process.env.WDS_SOCKET_PATH;
// 定义sockPort变量,值为环境变量WDS_SOCKET_PORT
const sockPort = process.env.WDS_SOCKET_PORT;
// 导出一个函数
module.exports = function (proxy, allowedHost) {
  // 定义disableFirewall变量,值为!proxy或环境变量DANGEROUSLY_DISABLE_HOST_CHECK等于'true'
  const disableFirewall = !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true';
  // 返回一个对象
  return {
    // 对象的allowedHosts属性,值为disableFirewall为真时的'all',否则为[allowedHost]
    allowedHosts: disableFirewall ? 'all' : [allowedHost],
    // 对象的headers属性,值为一个对象
    headers: {
      // 对象的'Access-Control-Allow-Origin'属性,值为'*'
      'Access-Control-Allow-Origin': '*',
      // 对象的'Access-Control-Allow-Methods'属性,值为'*'
      'Access-Control-Allow-Methods': '*',
      // 对象的'Access-Control-Allow-Headers'属性,值为'*'
      'Access-Control-Allow-Headers': '*'
    },
    // 对象的compress属性,值为true
    compress: true,
    // 对象的static属性,值为一个对象
    static: {
      // 对象的directory属性,值为paths.appPublic
      directory: paths.appPublic,
      // 对象的publicPath属性,值为[paths.publicUrlOrPath]
      publicPath: [paths.publicUrlOrPath],
      // 对象的watch属性,值为一个对象
      watch: {
        // 对象的ignored属性,值为ignoredFiles(paths.appSrc)
        ignored: ignoredFiles(paths.appSrc)
      }
    },
    // 对象的client属性,值为一个对象
    client: {
      // 对象的webSocketURL属性,值为一个对象
      webSocketURL: {
        // 对象的hostname属性,值为sockHost
        hostname: sockHost,
        // 对象的pathname属性,值为sockPath
        pathname: sockPath,
        // 对象的port属性,值为sockPort
        port: sockPort
      },
      // 对象的overlay属性,值为一个对象
      overlay: {
        // 对象的errors属性,值为true
        errors: true,
        // 对象的warnings属性,值为false
        warnings: false
      }
    },
    // 对象的devMiddleware属性,值为一个对象
    devMiddleware: {
      // 对象的publicPath属性,值为paths.publicUrlOrPath.slice(0, -1)
      publicPath: paths.publicUrlOrPath.slice(0, -1)
    },
    // 对象的https属性,值为getHttpsConfig()
    https: getHttpsConfig(),
    // 对象的host属性,值为host
    host,
    // 对象的historyApiFallback属性,值为一个对象
    historyApiFallback: {
      // 对象的disableDotRule属性,值为true
      disableDotRule: true,
      // 对象的index属性,值为paths.publicUrlOrPath
      index: paths.publicUrlOrPath
    },
    // 对象的proxy属性,值为proxy
    proxy,
    // 对象的onBeforeSetupMiddleware属性,值为一个函数
    onBeforeSetupMiddleware(devServer) {
      // 函数体:使用evalSourceMapMiddleware(devServer)中间件
      devServer.app.use(evalSourceMapMiddleware(devServer));
      // 函数体:如果paths.proxySetup存在,则执行require(paths.proxySetup)(devServer.app)
      if (fs.existsSync(paths.proxySetup)) {
        require(paths.proxySetup)(devServer.app);
      }
    },
    // 对象的onAfterSetupMiddleware属性,值为一个函数
    onAfterSetupMiddleware(devServer) {
      // 函数体:使用redirectServedPath(paths.publicUrlOrPath)中间件
      devServer.app.use(redirectServedPath(paths.publicUrlOrPath));
      // 函数体:使用noopServiceWorkerMiddleware(paths.publicUrlOrPath)中间件
      devServer.app.use(noopServiceWorkerMiddleware(paths.publicUrlOrPath));
    }
  };
};