create-react-app
(简称 CRA) 是一个流行的 React 脚手架,帮助开发者快速创建 React 单页面应用,而无需关心配置。
默认情况下,CRA 隐藏了大多数的配置(如 Webpack, Babel 等)。这意味着你的项目目录中不会直接看到这些配置文件。这种设计的目的是为了简化项目的结构和设置,允许开发者专注于编写 React 代码。
但在某些情况下,你可能需要更细粒度的配置控制。例如,你可能想要修改 Webpack 的加载器规则或插件,这时你就需要访问隐藏的配置文件。为了实现这一点,CRA 提供了一个命令:npm run eject
。
npm run eject
的详细解释:
不可逆操作:首先,应该明确的是,eject
是一个不可逆的操作。一旦你执行了这个命令,就没有回头路了。
显示配置:当你运行 npm run eject
,CRA 将会「弹出」配置和依赖,将其从内部移动到你的项目目录中。这意味着所有的 Webpack 设置、Babel 配置、linters 等都会出现在你的项目目录中。
手动维护:从此,你将需要手动维护这些配置。如果在未来 React 脚手架有任何更新,你需要自己合并这些更改。
为什么使用 eject:尽管 eject 提供了很大的灵活性,但这并不意味着每个人都应该使用它。只有当你确实需要对 CRA 默认的配置进行修改时,才应该考虑使用 eject。
使用方法:
为了安全起见,确保你的所有更改都已提交到 git(或其他版本控制系统)。
npm run eject
。config
和 scripts
。总结:
尽管 npm run eject
提供了更多的控制权,但它也带来了更多的责任。除非你有充分的理由并且了解你正在做什么,否则建议不要轻易使用这个命令。很多时候,你可以使用其他解决方案(如环境变量、或使用第三方插件)来达到目的,而不是完全弹出配置。
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
};
};
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;
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;
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();
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;
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));
}
};
};