1、编辑webpack.config.js,安装相关依赖:(ps.复用react-scripts/config/webpack.config.js)
npm i -D html-webpack-plugin case-sensitive-paths-webpack-plugin react-dev-utils mini-css-extract-plugin optimize-css-assets-webpack-plugin @babel/core @babel/plugin-proposal-class-properties @babel/preset-env @babel/preset-react babel-loader babel-plugin-named-asset-import case-sensitive-paths-webpack-plugin css-loader dotenv dotenv-expand eslint eslint-loader eslint-plugin-react file-loader less less-loader postcss-flexbugs-fixes postcss-loader postcss-normalize postcss-preset-env postcss-safe-parser react-dev-utils resolve-url-loader style-loader terser-webpack-plugin@2.3.8 url-loader eslint-webpack-plugin
1.1、编辑webpack.config.js:
const PnpWebpackPlugin = require('pnp-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const safePostCssParser = require('postcss-safe-parser');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const paths = require('./paths');
const plugins = require('./plugins');
const devServer = require('./devServer');
const postcssNormalize = require('postcss-normalize');
const appPackageJson = require(paths.appPackageJson);
//源映射是资源密集型的,可能会导致大型源文件的内存不足问题。
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
const reactRefreshOverlayEntry = require.resolve('react-dev-utils/refreshOverlayInterop');
const isEnvDevelopment = process.env.NODE_ENV === 'development';
const isEnvProduction = process.env.NODE_ENV === 'production';
//用于在生产中启用分析的变量
//传入alias对象。如果传递到生成命令中,则使用标志
const isEnvProductionProfile = isEnvProduction && process.argv.includes('--profile');
//样式文件正则表达式
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/;
//获取样式加载器的通用函数
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = [
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
loader: MiniCssExtractPlugin.loader,
//css位于“static/css”中,使用“../../”定位索引.html文件夹
//生产中`路径.publicUrlOrPath`可以是相对路径
options: paths.publicUrlOrPath.startsWith('.') ? { publicPath: '../../' } : {}
},
{
loader: require.resolve('css-loader'),
options: cssOptions
},
{
//邮政编码选项,因为我们引用这些选项两次
//根据中指定的浏览器支持添加供应商前缀
// 包.json
loader: require.resolve('postcss-loader'),
options: {
//外部CSS导入工作所必需的
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009'
},
stage: 3
}),
//添加postcs Normalize作为带有默认选项的重置css,
//所以它尊重浏览器列表配置包.json
//反过来,用户可以根据自己的需要定制目标行为。
postcssNormalize()
],
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;
};
//这是生产和开发配置。
//它关注于开发人员的体验、快速重建和最小的捆绑包
module.exports = {
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
//在生产初期停止编译
bail: isEnvProduction,
devtool: isEnvProduction
? shouldUseSourceMap
? 'source-map'
: false
: isEnvDevelopment && 'cheap-module-source-map',
//这些是我们应用程序的“入口点”。
//这意味着它们将是JS包中包含的“根”导入。
entry: paths.appIndexJs,
output: {
//生成文件夹。
path: isEnvProduction ? paths.appBuild : undefined,
//将/*filename*/comments添加到输出中生成的require()s中。
pathinfo: isEnvDevelopment,
//将有一个主包,每个异步块有一个文件。
//在开发过程中,它不会生成真正的文件。
filename: isEnvProduction
? 'static/js/[name].[contenthash:8].js'
: isEnvDevelopment && 'static/js/bundle.js',
//如果使用代码拆分,还有其他JS块文件。
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
//webpack使用“publicPath”确定应用程序从何处获得服务。
//它需要一个尾部斜杠,否则文件资源将获得不正确的路径。
//我们从主页推断出“公共路径”(例如/或/我的项目)。
publicPath: paths.publicUrlOrPath,
//防止多个Web包运行时发生冲突(来自不同的应用程序)
//在同一页上使用。
jsonpFunction: `webpackJsonp${appPackageJson.name}`,
//默认为“窗口”,但如果将其设置为“此”,则
//构建的模块块也可以在webworkers中工作。
globalObject: 'this'
},
optimization: {
minimize: isEnvProduction,
minimizer: [
//仅在生产模式下使用
new TerserPlugin({
terserOptions: {
parse: {
//我们希望terser解析ecma8代码。但是,我们不想要
//应用任何缩小步骤来转换有效的ecma5代码
//变成无效的ecma 5代码。这就是为什么“压缩”和“输出”
//节仅应用ecma 5安全的转换
// https://github.com/facebook/create-react-app/pull/4234
ecma: 8
},
compress: {
ecma: 5,
warnings: false,
//由于一个丑陋的破坏看似有效的代码的问题而被禁用:
// https://github.com/facebook/create-react-app/issues/2376
//待进一步调查:
// https://github.com/mishoo/UglifyJS2/issues/2011
comparisons: false,
//由于Terser中断有效代码的问题而禁用:
// https://github.com/facebook/create-react-app/issues/5250
//待进一步调查:
// https://github.com/terser-js/terser/issues/120
inline: 2
},
mangle: {
safari10: true
},
//添加用于在devtools中进行分析
keep_classnames: isEnvProductionProfile,
keep_fnames: isEnvProductionProfile,
output: {
ecma: 5,
comments: false,
//启用此选项是因为emoji和regex未使用默认值正确缩小
// https://github.com/facebook/create-react-app/issues/2488
ascii_only: true
}
},
sourceMap: shouldUseSourceMap
}),
//仅在生产模式下使用
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
parser: safePostCssParser,
map: shouldUseSourceMap
? {
//`inline:false`强制将源映射输出到
//单独文件
inline: false,
//`annotation:true`将sourceMappingURL追加到
//css文件,帮助浏览器查找源映射
annotation: true
}
: false
},
cssProcessorPluginOptions: {
preset: ['default', { minifyFontValues: { removeQuotes: false } }]
}
})
],
//自动拆分供应商和共享项
// https://twitter.com/wSokra/status/969633336732905474
// https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366
splitChunks: {
chunks: 'all',
name: false
},
//保持运行时块的分隔以启用长期缓存
// https://twitter.com/wSokra/status/969679223278505985
// https://github.com/facebook/create-react-app/issues/5358
runtimeChunk: {
name: (entrypoint) => `runtime-${entrypoint.name}`
}
},
resolve: {
//这允许您为webpack应该查找模块的位置设置后备。
//我们将这些路径放在第二位,因为我们希望“node\u modules”能够“获胜”
//如果有任何冲突。这与节点解析机制相匹配。
// https://github.com/facebook/create-react-app/issues/253
modules: ['node_modules', paths.appNodeModules],
//这些是节点生态系统支持的合理默认值。
//我们还将JSX作为一个通用的组件文件扩展名来支持
//有些工具,尽管我们不建议使用,请参见:
// https://github.com/facebook/create-react-app/issues/290
//添加了“web”扩展前缀以获得更好的支持
//对于React Native Web。
extensions: paths.moduleFileExtensions,
alias: {
...(isEnvProductionProfile && {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling'
}),
'@': paths.appSrc
},
plugins: [
//添加了对“即插即用”安装的支持,从而加快了安装和添加
//防止被遗忘的依赖等。
PnpWebpackPlugin,
//防止用户从src/(或node_modules/)外部导入文件。
//这通常会导致混淆,因为我们只处理src/with-babel中的文件。
//为了解决这个问题,我们阻止您从src/--如果您愿意,
//请将这些文件链接到您的节点_modules/中,并让模块解析生效。
//确保源文件已编译,因为它们不会以任何方式进行处理。
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson, reactRefreshOverlayEntry])
]
},
resolveLoader: {
plugins: [
//也与即插即用有关,但这次它告诉webpack加载加载程序
//从当前包中。
PnpWebpackPlugin.moduleLoader(module)
]
},
module: {
strictExportPresence: true,
rules: [
//禁用要求。确保因为它不是一个标准的语言功能。
{ parser: { requireEnsure: false } },
{
//“其中一个”将遍历所有后续装载机,直到其中一个
//符合要求。当没有装载机匹配时,它会掉下来
//回到加载程序列表末尾的“文件”加载程序。
oneOf: [
//TODO:在mime数据库中“image/avif”后合并此配置
// https://github.com/jshttp/mime-db
{
test: [/\.avif$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
mimetype: 'image/avif',
name: 'static/media/[name].[hash:8].[ext]'
}
},
//“url”加载器的工作原理与“file”加载器类似,只是它嵌入了资产
//小于数据URL的指定限制(以字节为单位),以避免请求。
//缺少“test”相当于匹配。
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]'
}
},
//使用Babel处理应用程序JS。
//预置包括JSX、Flow、TypeScript和一些ESnext特性。
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
plugins: [
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent: '@svgr/webpack?-svgo,+titleProp,+ref![path]'
}
}
}
]
].filter(Boolean),
//这是webpack的“babel loader”的一个特性(而不是babel本身)。
//它允许在./node\umodules/.cache/babel loader中缓存结果/
//快速重建目录。
cacheDirectory: true,
//有关为什么禁用cacheCompression的上下文,请参见#6846
cacheCompression: false,
compact: isEnvProduction
}
},
//用Babel处理应用程序之外的任何JS。
//与应用程序JS不同,我们只编译标准的ES特性。
{
test: /\.(js|mjs)$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve('babel-loader'),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: ['@babel/preset-env'],
cacheDirectory: true,
//有关为什么禁用cacheCompression的上下文,请参见#6846
cacheCompression: false,
//在node\u模块中调试需要Babel源映射
//代码。如果没有下面的选项,调试器就会喜欢VSCode
//显示错误代码并在错误的行上设置断点。
sourceMaps: shouldUseSourceMap,
inputSourceMap: shouldUseSourceMap
}
},
//“postcs”加载器将autoprefixer应用于我们的CSS。
//“css”加载程序解析css中的路径并将资产作为依赖项添加。
//“style”loader将CSS转换成JS模块,注入<style>标记。
//在生产中,我们使用MiniCSSExtractPlugin来提取CSS
//到一个文件,但在开发中“样式”加载程序启用热编辑
//CSS的。
//默认情况下,我们支持带有扩展名的CSS模块。模块.css
{
test: cssRegex,
exclude: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment
}),
//不要认为CSS导入死代码,即使
//包含包装声称没有副作用。
//当Web包为此添加警告或错误时删除此项。
//看到了吗https://github.com/webpack/webpack/issues/6571
sideEffects: true
},
//添加对CSS模块的支持(https://github.com/css-modules/css-modules)
//使用扩展名。模块.css
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
modules: {
getLocalIdent: getCSSModuleLocalIdent
}
})
},
//选择支持SASS(使用.scss或.SASS扩展名)。
//默认情况下,我们支持带有
//扩展。模块.scss或者。模块.sass
{
test: lessRegex,
exclude: lessModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment
},
'less-loader'
),
//不要认为CSS导入死代码,即使
//包含包装声称没有副作用。
//当Web包为此添加警告或错误时删除此项。
//看到了吗https://github.com/webpack/webpack/issues/6571
sideEffects: true
},
//添加对CSS模块的支持,但使用SASS
//使用扩展名。模块.scss或者。模块.sass
{
test: lessModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
modules: {
getLocalIdent: getCSSModuleLocalIdent
}
},
'less-loader'
)
},
//“文件”加载程序确保这些资产由WebpackDevServer提供服务。
//当你“导入”一个资产时,你会得到它的(虚拟)文件名。
//在生产中,它们会被复制到“build”文件夹中。
//此加载程序不使用“test”,因此它将捕获所有模块
//从其他装载机上掉下来。
{
loader: require.resolve('file-loader'),
//排除`js`文件以保持“css”加载程序在注入时正常工作
//它的运行时,否则将通过“文件”加载器处理。
//还要排除“html”和“json”扩展,以便对它们进行处理
//内部加载程序。
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
options: {
name: 'static/media/[name].[hash:8].[ext]'
}
}
//**停止**您正在添加新的加载程序吗?
//确保在“文件”加载程序之前添加新的加载程序。
]
}
]
},
plugins: plugins(isEnvDevelopment, isEnvProduction),
//有些库导入节点模块,但不在浏览器中使用它们。
//告诉webpack为它们提供空的模拟,以便导入它们。
node: {
module: 'empty',
dgram: 'empty',
dns: 'mock',
fs: 'empty',
http2: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
},
//关闭性能处理,因为我们利用
//我们通过FileSizeReporter提供的提示
performance: false,
devServer
};
2、编辑paths.js:
const paths = require('./paths');
const getClientEnvironment = require('./env');
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
//有些应用程序不需要保存Web请求的好处,因此无需内联代码块
//使构建过程更加顺畅。
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
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 (isEnvDevelopment, isEnvProduction) {
const plugins = [
//生成`索引.html`注入<script>的文件。
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
template: paths.appHtml
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
}
}
: undefined
)
),
//公开网址在index.html中以%PUBLIC_URL%的形式提供,例如:
// <link rel =“ icon” href =“%PUBLIC_URL%/ favicon.ico”>
//除非您指定“主页”,否则它将是一个空字符串
//在“ package.json”中,在这种情况下,它将是该URL的路径名。
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
//这为未找到模块的错误提供了一些必要的上下文,例如
//请求资源。
new ModuleNotFoundPlugin(paths.appPath),
//使一些环境变量可用于JS代码,例如:
//如果(进程.env.NODE_env==='生产'){。。。}. 见`/环境js`.
//将NODE_ENV设置为生产环境是绝对必要的
//在生产构建期间。
//否则React将以非常慢的开发模式编译。
new webpack.DefinePlugin(env.stringified),
// 力矩.js是一个非常流行的库,它绑定了大型区域设置文件
//默认情况下,由于webpack如何解释其代码。这是一个实用程序
//需要用户选择导入特定区域设置的解决方案。
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
//如果不使用,可以将其删除力矩.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new ESLintPlugin({
// Plugin options
extensions: ['js', 'mjs', 'jsx', 'ts', 'tsx'],
formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'),
context: paths.appSrc,
// ESLint class options
cwd: paths.appPath,
resolvePluginsRelativeTo: __dirname,
baseConfig: {
extends: [paths.appEslintConfig],
rules: {
...(!hasJsxRuntime && {
'react/react-in-jsx-scope': 'error'
})
}
}
})
];
if (isEnvDevelopment) {
plugins.push(
//这对于发出热更新(CSS和快速刷新)是必需的:
new webpack.HotModuleReplacementPlugin(),
//如果你在路径中输入了错误的case,Watcher就不能很好地工作
//一个插件,当您尝试执行此操作时会打印错误。
//看到了吗https://github.com/facebook/create-react-app/issues/240
new CaseSensitivePathsPlugin(),
//如果您需要一个丢失的模块,然后使用“npm install”命令,那么仍然需要
//重新启动Web包的开发服务器以发现它。这个插件
//使发现自动进行,因此您不必重新启动。
//看到了吗https://github.com/facebook/create-react-app/issues/186
new WatchMissingNodeModulesPlugin(paths.appNodeModules)
);
}
if (isEnvProduction) {
plugins.push(
new MiniCssExtractPlugin({
//选项类似于webpackOptions.output中的相同选项
//两个选项都是可选的
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css'
})
);
if (shouldInlineRuntimeChunk) {
plugins.push(new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]));
}
}
return plugins;
};
4、编辑plugins.js:
const paths = require('./paths');
const getClientEnvironment = require('./env');
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
//有些应用程序不需要保存Web请求的好处,因此无需内联代码块
//使构建过程更加顺畅。
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';
module.exports = function (isEnvDevelopment, isEnvProduction) {
const plugins = [
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
template: paths.appHtml
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
}
}
: undefined
)
),
//在index.html中提供一些环境变量。
//公开网址在index.html中以%PUBLIC_URL%的形式提供,例如:
// <link rel =“ icon” href =“%PUBLIC_URL%/ favicon.ico”>
//除非您指定“主页”,否则它将是一个空字符串
//在“ package.json”中,在这种情况下,它将是该URL的路径名。
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
//这为未找到模块的错误提供了一些必要的上下文,例如
//请求资源。
new ModuleNotFoundPlugin(paths.appPath),
new webpack.DefinePlugin(env.stringified),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
];
if (isEnvDevelopment) {
plugins.push(
new webpack.HotModuleReplacementPlugin(),
new CaseSensitivePathsPlugin(),
new WatchMissingNodeModulesPlugin(paths.appNodeModules)
);
}
if (isEnvProduction) {
plugins.push(
new MiniCssExtractPlugin({
//选项类似于webpackOptions.output中的相同选项
//两个选项都是可选的
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css'
})
);
if (shouldInlineRuntimeChunk) {
plugins.push(new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]));
}
}
return plugins;
};
5、编辑devServer.js:
const paths = require('./paths');
const ignoredFiles = require('react-dev-utils/ignoredFiles');
module.exports = {
disableHostCheck: process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
hot: true, // 热替换
contentBase: paths.appBuild, // server文件的根目录
contentBasePublicPath: paths.publicUrlOrPath,
watchContentBase: true,
compress: true, // 开启gzip
port: 8080, // 端口
host: '127.0.0.1',
//使WebpackDevServer自己的日志静音,因为它们通常没有用。
//此设置仍会显示编译警告和错误。
clientLogLevel: 'none',
publicPath: paths.publicUrlOrPath.slice(0, -1),
//默认情况下,WebpackDevServer嘈杂,因此我们发出自定义消息
//通过上面的`compiler.hooks [...]。tap`调用监听编译器事件。
quiet: true,
//据报道,这避免了某些系统上的CPU过载。
watchOptions: {
ignored: ignoredFiles(paths.appSrc)
},
proxy: {}
};
6、以上配置文件编辑完成后npm run dev就可以直接运行了