09-15补充

PC config-overrides.js 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
const path = require('path')
// node_modules/react-scripts/config/webpack.config.js
const {
override,
addDecoratorsLegacy,
addWebpackAlias,
addPostcssPlugins,
addWebpackPlugin,
addWebpackModuleRule,
adjustStyleLoaders,
setWebpackStats,
babelExclude,
setWebpackOptimizationSplitChunks
} = require('customize-cra')

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const ProgressBarWebpackPlugin = require('progress-bar-webpack-plugin')
// const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin') // 显示打包速度

console.log('ENV', process.env.NODE_ENV)

function resolve(dir) {
return path.join(__dirname, '.', dir)
}

module.exports = override(
// 装饰器
addDecoratorsLegacy(),
// Alias
addWebpackAlias({
'@': resolve('src'),
'@comps': resolve('src/pages/components'),
'@style': resolve('src/assets/style'),
'@images': resolve('src/assets/images'),
'@pages': resolve('src/pages'),
'@type': resolve('src/@types'),
'@api': resolve('src/utils/http'),
}),
// 控制台只输出报错 https://www.shouce.ren/api/view/a/13366
setWebpackStats('errors-only'),
// postcss
addPostcssPlugins([
require('postcss-autoreset')({
// 重置浏览器默认样式
reset: {
margin: 0,
padding: 0,
},
}),
]),
// 排除
babelExclude('/node_modules/'),
// analyzer
process.env.REACT_APP_REPORT == 'true' ? addWebpackPlugin(new BundleAnalyzerPlugin()) : '',
// 给打包加 进度条 webpackbar & progress-bar-webpack-plugin
addWebpackPlugin(new ProgressBarWebpackPlugin()),
setWebpackOptimizationSplitChunks({
maxInitialRequests: 20,// 最大初始请求数量
minSize: 50 * 1024, // 抽离体积大于50kb的chunk
minChunks: 2, // 抽离被多个入口引用次数大于等于2的chunk
// 缓存组
cacheGroups: {
antDesign: {
name: 'chunk-antDesign',
test: /[\/]node_modules[\/]_?@ant-design(.*)/,
priority: 10, // 缓存组打包的先后优先级,数值大的优先
enforce: true, //告诉 webpack 忽略 splitChunks.minSize, splitChunks.minChunks, splitChunks.maxAsyncRequests and splitChunks.maxInitialRequests,始终为此缓存组创建 chunk。
reuseExistingChunk: true, // 如果当前的 chunk 已被从 split 出来,那么将会直接复用这个 chunk 而不是重新创建一个
},
wangEditor: {
name: 'chunk-wangEditor',
test: /[\/]node_modules[\/]_?@wangeditor(.*)/,
priority: 8,
enforce: true, // 始终为此缓存组创建 chunk。
reuseExistingChunk: true, // 重复使用已经存在的块
},
antv: {
name: 'chunk-antv',
test: /[\/]node_modules[\/]_?@antv(.*)/,
priority: 7,
minSize: 0,
enforce: true, // 始终为此缓存组创建 chunk。
reuseExistingChunk: true, // 重复使用已经存在的块
},
nodesAsync: {
name: 'chunk-nodesAsync',
test: /[\/]node_modules[\/]/,
// 一个模块可以属于多个缓存组。优化将优先考虑具有更高 priority(优先级)的缓存组。
// 默认组的优先级为负,以允许自定义组获得更高的优先级(自定义组的默认值为 0)。
priority: 2,
minChunks: 2,
// chunks 代码块类型 必须三选一: “initial”(初始化) | “all”(默认就是 all) | “async”(动态加载)默认
chunks: 'async', // 仅打包异步引用的依赖
reuseExistingChunk: true, // 重复使用已经存在的块
},
nodesInitial: {
name: 'chunk-nodesInitial',
filename: 'static/js/[name].[contenthash:8].chunk.js', // sy chunks 为 initial时,需要设置filename,否则无法启动
test: /[\/]node_modules[\/]/,
priority: 1, // 优先级
minChunks: 1,
chunks: 'initial',
reuseExistingChunk: true, // 重复使用已经存在的块
},
},
}),
adjustStyleLoaders(rule => {
if (rule.test.toString().includes('scss')) {
rule.use.push({
loader: require.resolve('sass-resources-loader'),
options: {
resources: [resolve('./src/assets/styles/theme/_theme.scss')],
},
})
}
}),
addWebpackModuleRule({
test: /\.svg$/,
exclude: [/node_modules/],
use: [
{
loader: 'svg-sprite-loader',
options: {},
},
{
loader: 'svgo-loader',
options: {},
},
],
})
)

react-scripts包webpack配置源码修改记录

修改点 通过 // sy 查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
// @remove-on-eject-begin
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// @remove-on-eject-end
'use strict';

const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const resolve = require('resolve');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const ESLintPlugin = require('eslint-webpack-plugin');
const paths = require('./paths');
const modules = require('./modules');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin =
process.env.TSC_COMPILE_ON_ERROR === 'true'
? require('react-dev-utils/ForkTsCheckerWarningWebpackPlugin')
: require('react-dev-utils/ForkTsCheckerWebpackPlugin');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
// @remove-on-eject-begin
const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier');
// @remove-on-eject-end
const createEnvironmentHash = require('./webpack/persistentCache/createEnvironmentHash');

// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';

const reactRefreshRuntimeEntry = require.resolve('react-refresh/runtime');
const reactRefreshWebpackPluginRuntimeEntry = require.resolve(
'@pmmmwh/react-refresh-webpack-plugin'
);
const babelRuntimeEntry = require.resolve('babel-preset-react-app');
const babelRuntimeEntryHelpers = require.resolve(
'@babel/runtime/helpers/esm/assertThisInitialized',
{ paths: [babelRuntimeEntry] }
);
const babelRuntimeRegenerator = require.resolve('@babel/runtime/regenerator', {
paths: [babelRuntimeEntry],
});

// Some apps do not need the benefits of saving a web request, so not inlining the chunk
// makes for a smoother build process.
const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false';

const emitErrorsAsWarnings = process.env.ESLINT_NO_DEV_ERRORS === 'true';
const disableESLintPlugin = process.env.DISABLE_ESLINT_PLUGIN === 'true';

const imageInlineSizeLimit = parseInt(
process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
);

// Check if TypeScript is setup
const useTypeScript = fs.existsSync(paths.appTsConfig);

// Check if Tailwind config exists
const useTailwind = fs.existsSync(
path.join(paths.appPath, 'tailwind.config.js')
);

// Get the path to the uncompiled service worker (if it exists).
const swSrc = paths.swSrc;

// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;

const hasJsxRuntime = (() => {
if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') {
return false;
}

try {
require.resolve('react/jsx-runtime');
return true;
} catch (e) {
return false;
}
})();

// sy nodejs核心模块,直接使用
const os = require("os");
// cpu核数
const threads = os.cpus().length - 2 ;


// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
module.exports = function (webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production';

// Variable used for enabling profiling in Production
// passed into alias object. Uses a flag if passed into the build command
const isEnvProductionProfile =
isEnvProduction && process.argv.includes('--profile');

// We will provide `paths.publicUrlOrPath` to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
// Get environment variables to inject into our app.
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));

const shouldUseReactRefresh = env.raw.FAST_REFRESH;

// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
// sy 生产环境css提取 通过文件加载, 开发环境通过 style标签 注入css,以支持热加载
const loaders = [
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
loader: MiniCssExtractPlugin.loader,
// css is located in `static/css`, use '../../' to locate index.html folder
// in production `paths.publicUrlOrPath` can be a relative path
options: paths.publicUrlOrPath.startsWith('.')
? { publicPath: '../../' }
: {},
},
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
loader: require.resolve('postcss-loader'),
options: {
postcssOptions: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
config: false,
plugins: !useTailwind
? [
'postcss-flexbugs-fixes',
[
'postcss-preset-env',
{
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
},
],
// Adds PostCSS Normalize as the reset css with default options,
// so that it honors browserslist config in package.json
// which in turn let's users customize the target behavior as per their needs.
'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'],
// Webpack noise constrained to errors and warnings
stats: 'errors-warnings',
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
// Stop compilation early in production
bail: isEnvProduction,
devtool: isEnvProduction
? shouldUseSourceMap
? 'source-map'
: false
: isEnvDevelopment && 'cheap-module-source-map',
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
entry: paths.appIndexJs,
output: {
// The build folder.
path: paths.appBuild,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
filename: isEnvProduction
? 'static/js/[name].[contenthash:8].js'
: isEnvDevelopment && 'static/js/bundle.js',
// There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
assetModuleFilename: 'static/media/[name].[hash][ext]',
// webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
// We inferred the "public path" (such as / or /my-project) from homepage.
publicPath: paths.publicUrlOrPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
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),
cacheDirectory: paths.appWebpackCache,
store: 'pack',
buildDependencies: {
defaultWebpack: ['webpack/lib/'],
config: [__filename],
tsconfig: [paths.appTsConfig, paths.appJsConfig].filter(f =>
fs.existsSync(f)
),
},
},
// sy webpack基础 log信息
infrastructureLogging: {
level: 'none',
},
optimization: {
// sy named 对调试更友好的可读的 id。deterministic: 被哈希转化成的小位数值模块名。
// moduleIds: isEnvProduction ? 'deterministic': 'named',
// usedExports: true, // 标记不被使用的函数, 用于 webpack5 的 tree sharking
// runtimeChunk: true, // 运行时代码创建一个额外的 chunk,减少 entry chunk 体积,提高性能
minimize: isEnvProduction,
minimizer: [
// This is only used in production mode
// sy https://webpack.docschina.org/plugins/terser-webpack-plugin
new TerserPlugin({
// parallel: true, // sy 默认true 使用多进程并发运行以提高构建速度。 并发运行的默认数量: os.cpus().length - 1 。
// sy terserOptions 配置 https://github.com/terser/terser#minify-options
terserOptions: {
parse: {
// We want terser to parse ecma 8 code. However, we don't want it
// to apply any minification steps that turns valid ecma 5 code
// into invalid ecma 5 code. This is why the 'compress' and 'output'
// sections only apply transformations that are ecma 5 safe
// https://github.com/facebook/create-react-app/pull/4234
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
// Disabled because of an issue with Uglify breaking seemingly valid code:
// https://github.com/facebook/create-react-app/issues/2376
// Pending further investigation:
// https://github.com/mishoo/UglifyJS2/issues/2011
comparisons: false,
// Disabled because of an issue with Terser breaking valid code:
// https://github.com/facebook/create-react-app/issues/5250
// Pending further investigation:
// https://github.com/terser-js/terser/issues/120
inline: 2,
},
mangle: {
safari10: true,
},
// Added for profiling in devtools
keep_classnames: isEnvProductionProfile,
keep_fnames: isEnvProductionProfile,
output: {
ecma: 5,
comments: false,
// Turned on because emoji and regex is not minified properly using default
// https://github.com/facebook/create-react-app/issues/2488
ascii_only: true,
},
},
}),
// This is only used in production mode
new CssMinimizerPlugin(),
],
// sy 代码切割
splitChunks: {
maxInitialRequests: 20,// 最大初始请求数量
minSize: 50 * 1024, // 抽离体积大于50kb的chunk
minChunks: 2, // 抽离被多个入口引用次数大于等于2的chunk
cacheGroups: {
antDesign: {
name:"chunk-antDesign",
test: /[\/]node_modules[\/]_?@ant-design(.*)/,
priority: 30, // 缓存组打包的先后优先级,数值大的优先
enforce: true, //告诉 webpack 忽略 splitChunks.minSize, splitChunks.minChunks, splitChunks.maxAsyncRequests and splitChunks.maxInitialRequests,始终为此缓存组创建 chunk。
reuseExistingChunk:true // 如果当前的 chunk 已被从 split 出来,那么将会直接复用这个 chunk 而不是重新创建一个
},
wangEditor:{
name:"chunk-wangEditor",
test: /[\/]node_modules[\/]_?@wangeditor(.*)/,
priority: 20,
enforce: true, // 始终为此缓存组创建 chunk。
reuseExistingChunk:true // 重复使用已经存在的块
},
antv:{
name:"chunk-antv",
test: /[\/]node_modules[\/]_?@antv(.*)/,
priority: 10,
minSize:0,
enforce: true, // 始终为此缓存组创建 chunk。
reuseExistingChunk:true // 重复使用已经存在的块
},
nodesAsync: {
name:"chunk-nodesAsync",
test: /[\/]node_modules[\/]/,
// 一个模块可以属于多个缓存组。优化将优先考虑具有更高 priority(优先级)的缓存组。
// 默认组的优先级为负,以允许自定义组获得更高的优先级(自定义组的默认值为 0)。
priority: 8,
minChunks: 2,
// chunks 代码块类型 必须三选一: “initial”(初始化) | “all”(默认就是 all) | “async”(动态加载)默认
chunks: "async",// 仅打包异步引用的依赖
reuseExistingChunk: true // 重复使用已经存在的块
},
nodesInitial: {
name:"chunk-nodesInitial",
filename: 'static/js/[name].[contenthash:8].chunk.js', // sy chunks 为 initial时,需要设置filename,否则无法启动
test: /[\/]node_modules[\/]/,
priority: 1, // 优先级
minChunks: 1,
chunks: "initial",
reuseExistingChunk:true // 重复使用已经存在的块
},
},
},
},
resolve: {
// This allows you to set a fallback for where webpack should look for modules.
// We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebook/create-react-app/issues/253
modules: ['node_modules', paths.appNodeModules].concat(
modules.additionalModulePaths || []
),
// These are the reasonable defaults supported by the Node ecosystem.
// We also include JSX as a common component filename extension to support
// some tools, although we do not recommend using it, see:
// https://github.com/facebook/create-react-app/issues/290
// `web` extension prefixes have been added for better support
// for React Native Web.
extensions: paths.moduleFileExtensions
.map(ext => `.${ext}`)
.filter(ext => useTypeScript || !ext.includes('ts')),
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
// Allows for better profiling with ReactDevTools
...(isEnvProductionProfile && {
'react-dom$': 'react-dom/profiling',
'scheduler/tracing': 'scheduler/tracing-profiling',
}),
...(modules.webpackAliases || {}),
},
plugins: [
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [
paths.appPackageJson,
reactRefreshRuntimeEntry,
reactRefreshWebpackPluginRuntimeEntry,
babelRuntimeEntry,
babelRuntimeEntryHelpers,
babelRuntimeRegenerator,
]),
],
},
module: {
strictExportPresence: true,
rules: [
// Handle node_modules packages that contain sourcemaps
shouldUseSourceMap && {
enforce: 'pre',
exclude: /@babel(?:\/|\\{1,2})runtime/,
test: /\.(js|mjs|jsx|ts|tsx|css)$/,
loader: require.resolve('source-map-loader'),
},
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
// TODO: Merge this config once `image/avif` is in the mime-db
// https://github.com/jshttp/mime-db
{
test: [/\.avif$/],
type: 'asset',
mimetype: 'image/avif',
parser: {
dataUrlCondition: {
maxSize: imageInlineSizeLimit,
},
},
},
// "url" loader works like "file" loader except that it embeds assets
// smaller than specified limit in bytes as data URLs to avoid requests.
// A missing `test` is equivalent to a match.
// sy 处理图片
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: imageInlineSizeLimit,
},
},
},
{
test: /\.svg$/,
use: [
{
loader: require.resolve('@svgr/webpack'),
options: {
prettier: false,
svgo: false,
svgoConfig: {
plugins: [{ removeViewBox: false }],
},
titleProp: true,
ref: true,
},
},
{
loader: require.resolve('file-loader'),
options: {
name: 'static/media/[name].[hash].[ext]',
},
},
],
issuer: {
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
},
},
// Process application JS with Babel.
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
presets: [
[
require.resolve('babel-preset-react-app'),
{
runtime: hasJsxRuntime ? 'automatic' : 'classic',
},
],
],
// @remove-on-eject-begin
babelrc: false,
configFile: false,
// Make sure we have a unique cache identifier, erring on the
// side of caution.
// We remove this when the user ejects because the default
// is sane and uses Babel options. Instead of options, we use
// the react-scripts and babel-preset-react-app versions.
cacheIdentifier: getCacheIdentifier(
isEnvProduction
? 'production'
: isEnvDevelopment && 'development',
[
'babel-plugin-named-asset-import',
'babel-preset-react-app',
'react-dev-utils',
'react-scripts',
]
),
// @remove-on-eject-end
plugins: [
isEnvDevelopment &&
shouldUseReactRefresh &&
require.resolve('react-refresh/babel'),
].filter(Boolean),
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true, // sy 开启babel编译缓存, 加快编译速度
// See #6846 for context on why cacheCompression is disabled
cacheCompression: false, // 缓存文件不要压缩
compact: isEnvProduction,
},


},
// Process any JS outside of the app with Babel.
// Unlike the application JS, we only compile the standard ES features.
{
test: /\.(js|mjs)$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve('babel-loader'),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [
[
require.resolve('babel-preset-react-app/dependencies'),
{ helpers: true },
],
],
cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
// @remove-on-eject-begin
cacheIdentifier: getCacheIdentifier(
isEnvProduction
? 'production'
: isEnvDevelopment && 'development',
[
'babel-plugin-named-asset-import',
'babel-preset-react-app',
'react-dev-utils',
'react-scripts',
]
),
// @remove-on-eject-end
// Babel sourcemaps are needed for debugging into node_modules
// code. Without the options below, debuggers like VSCode
// show incorrect code and set breakpoints on the wrong lines.
sourceMaps: shouldUseSourceMap,
inputSourceMap: shouldUseSourceMap,
},
},
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
// In production, we use MiniCSSExtractPlugin to extract that CSS
// to a file, but in development "style" loader enables hot editing
// of CSS.
// By default we support CSS Modules with the extension .module.css
{
test: cssRegex,
exclude: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'icss',
},
}),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules (https://github.com/css-modules/css-modules)
// using the extension .module.css
{
test: cssModuleRegex,
use: getStyleLoaders({
importLoaders: 1,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
}),
},
// Opt-in support for SASS (using .scss or .sass extensions).
// By default we support SASS Modules with the
// extensions .module.scss or .module.sass
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'icss',
},
},
'sass-loader'
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
// Adds support for CSS Modules, but using SASS
// using the extension .module.scss or .module.sass
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
},
'sass-loader'
),
},
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise be processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
type: 'asset/resource',
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
].filter(Boolean),
},
plugins: [
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
template: paths.appHtml,
},
isEnvProduction
? {
// sy 压缩html
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),
// Inlines the webpack runtime script. This script is too small to warrant
// a network request.
// https://github.com/facebook/create-react-app/issues/5358
isEnvProduction &&
shouldInlineRuntimeChunk &&
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
// It will be an empty string unless you specify "homepage"
// in `package.json`, in which case it will be the pathname of that URL.
new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
// This gives some necessary context to module not found errors, such as
// the requesting resource.
new ModuleNotFoundPlugin(paths.appPath),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
// It is absolutely essential that NODE_ENV is set to production
// during a production build.
// Otherwise React will be compiled in the very slow development mode.
new webpack.DefinePlugin(env.stringified),
// Experimental hot reloading for React .
// https://github.com/facebook/react/tree/main/packages/react-refresh
isEnvDevelopment &&
shouldUseReactRefresh &&
new ReactRefreshWebpackPlugin({
overlay: false,
}),
// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebook/create-react-app/issues/240
isEnvDevelopment && new CaseSensitivePathsPlugin(),
// sy 生产环境css提取 通过文件加载
isEnvProduction &&
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
}),
// Generate an asset manifest file with the following content:
// - "files" key: Mapping of all asset filenames to their corresponding
// output file so that tools can pick it up without having to parse
// `index.html`
// - "entrypoints" key: Array of files which are included in `index.html`,
// can be used to reconstruct the HTML if necessary
new WebpackManifestPlugin({
fileName: 'asset-manifest.json',
publicPath: paths.publicUrlOrPath,
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path;
return manifest;
}, seed);
const entrypointFiles = entrypoints.main.filter(
fileName => !fileName.endsWith('.map')
);

return {
files: manifestFiles,
entrypoints: entrypointFiles,
};
},
}),
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
}),
// Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the webpack build.
isEnvProduction &&
fs.existsSync(swSrc) &&
new WorkboxWebpackPlugin.InjectManifest({
swSrc,
dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./,
exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/],
// Bump up the default maximum size (2mb) that's precached,
// to make lazy-loading failure scenarios less likely.
// See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
}),
// TypeScript type checking
useTypeScript &&
new ForkTsCheckerWebpackPlugin({
async: isEnvDevelopment,
typescript: {
typescriptPath: resolve.sync('typescript', {
basedir: paths.appNodeModules,
}),
configOverwrite: {
compilerOptions: {
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
skipLibCheck: true,
inlineSourceMap: false,
declarationMap: false,
noEmit: true,
incremental: true,
tsBuildInfoFile: paths.appTsBuildInfoFile,
},
},
context: paths.appPath,
diagnosticOptions: {
syntactic: true,
},
mode: 'write-references',
// profile: true,
},
issue: {
// This one is specifically to match during CI tests,
// as micromatch doesn't match
// '../cra-template-typescript/template/src/App.tsx'
// otherwise.
include: [
{ file: '../**/src/**/*.{ts,tsx}' },
{ file: '**/src/**/*.{ts,tsx}' },
],
exclude: [
{ file: '**/src/**/__tests__/**' },
{ file: '**/src/**/?(*.){spec|test}.*' },
{ file: '**/src/setupProxy.*' },
{ file: '**/src/setupTests.*' },
],
},
logger: {
infrastructure: 'silent',
},
}),
!disableESLintPlugin &&
new ESLintPlugin({
threads,// sy 开启多进程
// Plugin options
extensions: ['js', 'mjs', 'jsx', 'ts', 'tsx'],
formatter: require.resolve('react-dev-utils/eslintFormatter'),
eslintPath: require.resolve('eslint'),
failOnError: !(isEnvDevelopment && emitErrorsAsWarnings),
context: paths.appSrc,
cache: true, // sy 开启缓存
cacheLocation: path.resolve(
paths.appNodeModules,
'.cache/.eslintcache'
),
// ESLint class options
cwd: paths.appPath,
resolvePluginsRelativeTo: __dirname,
baseConfig: {
extends: [require.resolve('eslint-config-react-app/base')],
rules: {
...(!hasJsxRuntime && {
'react/react-in-jsx-scope': 'error',
}),
},
},
}),
].filter(Boolean),
// Turn off performance processing because we utilize
// our own hints via the FileSizeReporter
// sy false 关闭浏览器的性能分析
performance: false,
};
};


一. 修改 webpack 配置

  1. 需要用到 react-app-rewired customize-cra
1
yarn add react-app-rewired customize-cra -D
  1. 修改package.json
1
2
3
4
5
6
7
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},

  1. 更目录下创建config-overrides.js,在该文件下修改 webpack 的配置;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
const path = require('path')

const {
override,
addDecoratorsLegacy,
addWebpackAlias,
addPostcssPlugins,
addWebpackModuleRule,
fixBabelImports,
adjustStyleLoaders,
} = require('customize-cra')

function resolve(dir) {
return path.join(__dirname, '.', dir)
}

module.exports = override(
// 装饰器
addDecoratorsLegacy(),
// Alias
addWebpackAlias({
'@': resolve('src'),
'@comps': resolve('src/components'),
'@style': resolve('src/assets/style'),
'@images': resolve('src/assets/images'),
'@pages': resolve('src/pages'),
'@api': resolve('src/assets/http'),
'@utils': resolve('src/utils'),
'@hooks': resolve('src/utils/hooks'),
}),
// postcss
addPostcssPlugins([
// require('postcss-px2rem')({ remUnit: 75 }),
require('autoprefixer'), // css 自动加前缀
require('postcss-autoreset')({
// 重置浏览器默认样式
reset: {
margin: 0,
padding: 0,
borderRadius: 0,
},
}),
// 适配
require('postcss-px-to-viewport')({
unitToConvert: 'px', //需要转换的单位,默认为"px"
viewportWidth: 750, // 视窗的宽度,对应的是我们设计稿的宽度
viewportHeight: 1624, //视窗的高度,根据375设备的宽度来指定,一般指定667,也可以不配置
unitPrecision: 3, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)
propList: ['*'], // 能转化为vw的属性列表
viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw
fontViewportUnit: 'vw', //字体使用的视口单位
selectorBlackList: ['.ignore-', '.hairlines', 'am-', 'px-'], //指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
mediaQuery: false, // 允许在媒体查询中转换`px`
replace: true, //是否直接更换属性值,而不添加备用属性
exclude: [/\/Stores\/.*.scss/, /global.css/, /node_modules/], //忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
landscape: false, //是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
landscapeUnit: 'vw', //横屏时使用的单位
landscapeWidth: 1134, //横屏时使用的视口宽度
}),
]),
// antd 按需加载
fixBabelImports('import', {
libraryName: 'antd-mobile',
style: 'css',
}),
// antd 样式加载不出来问题
addWebpackModuleRule({
test: /\.css$/,
exclude: /src/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
importLoaders: 1,
},
},
],
}),
// sass公共样式 引入
adjustStyleLoaders((rule) => {
if (rule.test.toString().includes('scss')) {
rule.use.push({
loader: require.resolve('sass-resources-loader'),
options: {
resources: [resolve('./src/assets/style/common.scss')],
},
})
}
}),
// svg处理
addWebpackModuleRule({
test: /\.svg$/,
exclude: [/node_modules/],
use: [
{
loader: 'svg-sprite-loader',
options: {},
},
{
loader: 'svgo-loader',
options: {},
},
],
})
)

二. 配置环境区分

  1. 安装 dotenv
1
yarn add dotenv dotenv-cli -D
  1. 修改package.json
1
2
3
4
5
6
7
8
9
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"build:test": "dotenv -e .env.test react-app-rewired build",
"build:uat": "dotenv -e .env.uat react-app-rewired build",
"build:prod": "dotenv -e .env.prod react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
  1. 在根目录下新建 .env.env.test.env.uat.env.prod,根据不同环境,在不同的环境文件下配置相应的 baseUrl 等数据;
1
2
3
4
5
6

REACT_APP_BASE_URL='xxxxxx/api'
REACT_APP_REDIRECT_URL='xxxxxxx'

...

三. PUBLIC_URL

.env.[xxx]文件里配置

1
PUBLIC_URL=/service-plat/

四. 修改打包文件夹名字

.env.[xxx]文件里配置

1
BUILD_PATH=service-plat

五. 生产环境去掉map文件

.env.prod文件里配置

1
GENERATE_SOURCEMAP=false 

六. History模式项目 tomcat 部署配置

新建一个 WEB-INF文件夹,web.xml文件

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<display-name>/</display-name>
<description>/</description>
<error-page>
<error-code>404</error-code>
<location>/index.html</location>
</error-page>
</web-app>

七. husky + lint-staged

规范git提交message,统一代码风格
注意:husky需要注意版本,5.x以上的版本配置有很大的更改,这里安装4.x的包
配置如下:

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{

"devDependencies": {
"@commitlint/cli": "^15.0.0",
"husky": "4.3.8",
"lint-staged": "^12.1.2",
"prettier": "^2.5.1"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"src/**/*.{jsx,js,tsx,ts}": [
"prettier --write",
"eslint --fix"
]
},

}

.commitlintrc.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
parserPreset: {
parserOpts: {
headerPattern: /^(\w*)(?:\((.*)\))?:\s(.*)$/,
headerCorrespondence: ['type', 'scope', 'subject']
}
},
rules: {
'type-empty': [2, 'never'],
'type-case': [2, 'always', 'lower-case'],
'subject-empty': [2, 'never'],
'type-enum': [
2,
'always',
['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'revert']
]
}
}

git message

  • feat: 新增feature
  • fix: 修复bug
  • docs: 仅仅修改了文档,如README.md
  • style: 仅仅是对格式进行修改,如逗号、缩进、空格等,不改变代码逻辑
  • refactor: 代码重构,没有新增功能或修复bug
  • perf: 优化相关,如提升性能、用户体验等
  • test: 测试用例,包括单元测试、集成测试
  • chore: 改变构建流程、或者增加依赖库、工具等
  • revert: 版本回滚

八. svg组件封装

安装svg-sprite-loadersvgo-loader, 配置详情见 “一. 修改 webpack 配置”;

  1. svg文件夹index.js中统一引入.svg文件,并导出Icon组件供全局使用;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React from 'react'

const importAll = (requireContext: __WebpackModuleApi.RequireContext) =>
requireContext.keys().forEach(requireContext)

try {
// 引入当前文件夹下所有后缀为 .svg
importAll(require.context('./', true, /\.svg$/))
} catch (error) {
console.log(error)
}

interface IIconProps {
name: string
}

const Icon: React.FC<IIconProps> = props => {
return (
<svg {...props}>
<use xlinkHref={'#' + props.name} />
</svg>
)
}

export default Icon

  1. 使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import React from 'react'
import { observer } from 'mobx-react-lite'
import { useStore } from '@/store'
import { IIconItemType } from '@/store/hmbc/plat.store'

import Icon from '@/assets/svg' // 引入 Icon 组件
import styles from './index.module.scss'

interface IIconItemCompsProps {
item: IIconItemType
}

const IconItemComps: React.FC<IIconItemCompsProps> = ({ item }): React.ReactElement => {
const { commonStore } = useStore()

return (
<div
className={`xy-center ${styles.mainNavItem}`}
onClick={() => commonStore.pushService(item.path)}
>
<Icon className={styles.mainIcon} name={item.icon} />
<p>{item.name}</p>
</div>
)
}

export default observer(IconItemComps)