@tramvai/cli 5.53.80 → 5.53.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/README.md +1 -1
  2. package/lib/api/analyze/index.d.ts.map +1 -1
  3. package/lib/api/analyze/index.js +1 -13
  4. package/lib/api/analyze/index.js.map +1 -1
  5. package/lib/api/benchmark/build.d.ts +1 -6
  6. package/lib/api/benchmark/build.d.ts.map +1 -1
  7. package/lib/api/benchmark/build.js +59 -23
  8. package/lib/api/benchmark/build.js.map +1 -1
  9. package/lib/api/benchmark/const.d.ts +2 -0
  10. package/lib/api/benchmark/const.d.ts.map +1 -0
  11. package/lib/api/benchmark/const.js +5 -0
  12. package/lib/api/benchmark/const.js.map +1 -0
  13. package/lib/api/benchmark/index.d.ts +4 -6
  14. package/lib/api/benchmark/index.d.ts.map +1 -1
  15. package/lib/api/benchmark/index.js.map +1 -1
  16. package/lib/api/benchmark/start.d.ts +1 -7
  17. package/lib/api/benchmark/start.d.ts.map +1 -1
  18. package/lib/api/benchmark/start.js +56 -49
  19. package/lib/api/benchmark/start.js.map +1 -1
  20. package/lib/api/benchmark/types.d.ts +76 -12
  21. package/lib/api/benchmark/types.d.ts.map +1 -1
  22. package/lib/api/benchmark/utils/compilationUtils.d.ts +4 -0
  23. package/lib/api/benchmark/utils/compilationUtils.d.ts.map +1 -0
  24. package/lib/api/benchmark/utils/compilationUtils.js +157 -0
  25. package/lib/api/benchmark/utils/compilationUtils.js.map +1 -0
  26. package/lib/api/benchmark/utils/stats.d.ts +2 -7
  27. package/lib/api/benchmark/utils/stats.d.ts.map +1 -1
  28. package/lib/api/benchmark/utils/stats.js +36 -22
  29. package/lib/api/benchmark/utils/stats.js.map +1 -1
  30. package/lib/api/build/index.d.ts +1 -0
  31. package/lib/api/build/index.d.ts.map +1 -1
  32. package/lib/api/build/index.js.map +1 -1
  33. package/lib/api/index.js +1 -1
  34. package/lib/api/index.js.map +1 -1
  35. package/lib/api/start/index.d.ts +1 -0
  36. package/lib/api/start/index.d.ts.map +1 -1
  37. package/lib/api/start/index.js.map +1 -1
  38. package/lib/builder/webpack/analyzePlugins/rsdoctor.d.ts +5 -5
  39. package/lib/builder/webpack/analyzePlugins/rsdoctor.d.ts.map +1 -1
  40. package/lib/builder/webpack/analyzePlugins/rsdoctor.js +7 -9
  41. package/lib/builder/webpack/analyzePlugins/rsdoctor.js.map +1 -1
  42. package/lib/builder/webpack/index.d.ts.map +1 -1
  43. package/lib/builder/webpack/index.js +14 -12
  44. package/lib/builder/webpack/index.js.map +1 -1
  45. package/lib/builder/webpack/providers/build/client.d.ts.map +1 -1
  46. package/lib/builder/webpack/providers/build/client.js +3 -2
  47. package/lib/builder/webpack/providers/build/client.js.map +1 -1
  48. package/lib/builder/webpack/providers/build/server.d.ts.map +1 -1
  49. package/lib/builder/webpack/providers/build/server.js +3 -2
  50. package/lib/builder/webpack/providers/build/server.js.map +1 -1
  51. package/lib/builder/webpack/providers/shared.d.ts.map +1 -1
  52. package/lib/builder/webpack/providers/shared.js +39 -5
  53. package/lib/builder/webpack/providers/shared.js.map +1 -1
  54. package/lib/builder/webpack/providers/start/shared.d.ts.map +1 -1
  55. package/lib/builder/webpack/providers/start/shared.js +6 -2
  56. package/lib/builder/webpack/providers/start/shared.js.map +1 -1
  57. package/lib/builder/webpack/tokens.d.ts +8 -16
  58. package/lib/builder/webpack/tokens.d.ts.map +1 -1
  59. package/lib/cli/index.d.ts.map +1 -1
  60. package/lib/cli/index.js +12 -12
  61. package/lib/cli/index.js.map +1 -1
  62. package/lib/commands/analyze/command.d.ts +1 -1
  63. package/lib/commands/analyze/command.d.ts.map +1 -1
  64. package/lib/commands/benchmark/benchmark.d.ts +3 -1
  65. package/lib/commands/benchmark/benchmark.d.ts.map +1 -1
  66. package/lib/commands/benchmark/benchmark.js +130 -31
  67. package/lib/commands/benchmark/benchmark.js.map +1 -1
  68. package/lib/commands/benchmark/command.d.ts +9 -0
  69. package/lib/commands/benchmark/command.d.ts.map +1 -1
  70. package/lib/commands/benchmark/command.js +12 -0
  71. package/lib/commands/benchmark/command.js.map +1 -1
  72. package/lib/commands/build/command.d.ts.map +1 -1
  73. package/lib/commands/build/command.js +5 -0
  74. package/lib/commands/build/command.js.map +1 -1
  75. package/lib/commands/new/steps/installDependencies.js +1 -1
  76. package/lib/commands/new/steps/installDependencies.js.map +1 -1
  77. package/lib/commands/start/command.d.ts.map +1 -1
  78. package/lib/commands/start/command.js +5 -0
  79. package/lib/commands/start/command.js.map +1 -1
  80. package/lib/config/configManager.d.ts +2 -0
  81. package/lib/config/configManager.d.ts.map +1 -1
  82. package/lib/config/configManager.js +2 -0
  83. package/lib/config/configManager.js.map +1 -1
  84. package/lib/di/tokens/config.d.ts +4 -8
  85. package/lib/di/tokens/config.d.ts.map +1 -1
  86. package/lib/library/webpack/blocks/js.d.ts.map +1 -1
  87. package/lib/library/webpack/blocks/js.js +57 -11
  88. package/lib/library/webpack/blocks/js.js.map +1 -1
  89. package/lib/library/webpack/common/main.d.ts.map +1 -1
  90. package/lib/library/webpack/common/main.js +7 -0
  91. package/lib/library/webpack/common/main.js.map +1 -1
  92. package/lib/library/webpack/utils/rsdoctor.d.ts +2 -0
  93. package/lib/library/webpack/utils/rsdoctor.d.ts.map +1 -0
  94. package/lib/library/webpack/utils/rsdoctor.js +27 -0
  95. package/lib/library/webpack/utils/rsdoctor.js.map +1 -0
  96. package/lib/library/webpack/utils/threadLoader.d.ts.map +1 -1
  97. package/lib/library/webpack/utils/threadLoader.js +3 -3
  98. package/lib/library/webpack/utils/threadLoader.js.map +1 -1
  99. package/lib/schema/autogeneratedSchema.json +228 -120
  100. package/lib/typings/build/Builder.d.ts +6 -2
  101. package/lib/typings/build/Builder.d.ts.map +1 -1
  102. package/lib/typings/configEntry/cli.d.ts +5 -0
  103. package/lib/typings/configEntry/cli.d.ts.map +1 -1
  104. package/package.json +16 -17
  105. package/schema.json +228 -120
  106. package/src/api/analyze/index.ts +3 -16
  107. package/src/api/benchmark/__integration__/start.test.ts +10 -12
  108. package/src/api/benchmark/build.ts +75 -30
  109. package/src/api/benchmark/const.ts +1 -0
  110. package/src/api/benchmark/index.ts +4 -6
  111. package/src/api/benchmark/start.ts +69 -65
  112. package/src/api/benchmark/types.ts +82 -14
  113. package/src/api/benchmark/utils/compilationUtils.ts +213 -0
  114. package/src/api/benchmark/utils/stats.ts +45 -28
  115. package/src/api/build/index.ts +1 -0
  116. package/src/api/index.ts +1 -1
  117. package/src/api/start/index.ts +1 -0
  118. package/src/builder/webpack/analyzePlugins/rsdoctor.ts +11 -14
  119. package/src/builder/webpack/index.ts +16 -21
  120. package/src/builder/webpack/providers/build/client.ts +7 -2
  121. package/src/builder/webpack/providers/build/server.ts +7 -2
  122. package/src/builder/webpack/providers/shared.ts +53 -5
  123. package/src/builder/webpack/providers/start/shared.ts +7 -2
  124. package/src/cli/index.ts +3 -2
  125. package/src/commands/analyze/command.ts +1 -1
  126. package/src/commands/benchmark/benchmark.ts +168 -33
  127. package/src/commands/benchmark/command.ts +12 -0
  128. package/src/commands/build/command.ts +6 -0
  129. package/src/commands/new/steps/installDependencies.ts +1 -1
  130. package/src/commands/new/templates/shared/_prettierrc.js.hbs +1 -1
  131. package/src/commands/new/templates/shared/package.json.hbs +1 -1
  132. package/src/commands/start/command.ts +6 -0
  133. package/src/config/configManager.ts +4 -0
  134. package/src/library/webpack/blocks/js.ts +61 -12
  135. package/src/library/webpack/common/main.ts +8 -0
  136. package/src/library/webpack/utils/rsdoctor.ts +26 -0
  137. package/src/library/webpack/utils/threadLoader.ts +1 -0
  138. package/src/models/config.spec.ts +4 -0
  139. package/src/schema/autogeneratedSchema.json +228 -120
  140. package/src/schema/tramvai.spec.ts +2 -0
  141. package/src/typings/build/Builder.ts +7 -2
  142. package/src/typings/configEntry/cli.ts +6 -0
  143. package/lib/api/analyze/providers/shared.d.ts +0 -3
  144. package/lib/api/analyze/providers/shared.d.ts.map +0 -1
  145. package/lib/api/analyze/providers/shared.js +0 -23
  146. package/lib/api/analyze/providers/shared.js.map +0 -1
  147. package/lib/builder/webpack/providers/analyze/shared.d.ts +0 -3
  148. package/lib/builder/webpack/providers/analyze/shared.d.ts.map +0 -1
  149. package/lib/builder/webpack/providers/analyze/shared.js +0 -141
  150. package/lib/builder/webpack/providers/analyze/shared.js.map +0 -1
  151. package/src/api/analyze/providers/shared.ts +0 -26
  152. package/src/api/benchmark/utils/stats.spec.ts +0 -36
  153. package/src/builder/webpack/providers/analyze/shared.ts +0 -160
@@ -1,21 +1,19 @@
1
+ /* eslint-disable max-statements */
1
2
  import type { Container } from '@tinkoff/dippy';
2
3
  import type { Params as OriginalBuildParams, Result as OriginalBuildResult } from '../build/index';
3
4
  import { COMMAND_PARAMETERS_TOKEN, COMMAND_RUNNER_TOKEN } from '../../di/tokens';
4
5
  import type { Params, Result } from './index';
5
- import type { Samples, RunStats } from './types';
6
+ import type { Samples, RunStats, CompilationStats } from './types';
6
7
  import { clearCacheDirectory } from './utils/clearCache';
7
8
  import { getResultStats } from './utils/stats';
9
+ import { getClientCompilationTimings, getServerCompilationTimings } from './utils/compilationUtils';
10
+ import { DEFAULT_TIMES } from './const';
8
11
 
9
12
  export interface BuildParams extends Params {
10
13
  command: 'build';
11
14
  commandOptions: OriginalBuildParams;
12
15
  }
13
16
 
14
- export interface BuildResult extends Result {
15
- noCache?: RunStats;
16
- cache?: RunStats;
17
- }
18
-
19
17
  const runBuildCommand = async (
20
18
  di: Container,
21
19
  {
@@ -26,49 +24,96 @@ const runBuildCommand = async (
26
24
  shouldClearCache: boolean;
27
25
  }
28
26
  ): Promise<Samples> => {
29
- const clientSamples: number[] = Array(times);
30
- const serverSamples: number[] = Array(times);
31
- const maxMemoryRssSamples: number[] = Array(times);
27
+ const clientBuildTimeSamples: number[] = [];
28
+ const serverBuildTimeSamples: number[] = [];
29
+
30
+ const clientMaxMemoryRssSamples: number[] = [];
31
+ const serverMaxMemoryRssSamples: number[] = [];
32
+
33
+ const serverCompilationTimings: CompilationStats[] = [];
34
+ const clientCompilationTimings: CompilationStats[] = [];
35
+
36
+ const maxMemoryRssSamples: number[] = [];
32
37
 
33
38
  const { commandOptions } = di.get(COMMAND_PARAMETERS_TOKEN) as BuildParams;
39
+ commandOptions.benchmark = true;
40
+
41
+ const buildType = commandOptions.buildType ?? 'all';
42
+
43
+ const benchmarkStartTime = new Date().toISOString();
34
44
 
35
45
  for (let i = 0; i < times; i++) {
36
46
  if (shouldClearCache) {
37
47
  await clearCacheDirectory(di);
38
48
  }
39
49
 
40
- const { getBuildStats: getStats } = await (di
50
+ const attemptStartTime = Date.now();
51
+ const { getBuildStats } = await (di
41
52
  .get(COMMAND_RUNNER_TOKEN)
42
53
  .run('build', commandOptions) as OriginalBuildResult);
43
- const stats = getStats();
54
+ const stats = getBuildStats();
55
+
56
+ // at first attempt do not save metrics if cache is used
57
+ if (!shouldClearCache && i === 0) {
58
+ continue;
59
+ }
60
+
61
+ if (buildType === 'all' || buildType === 'server') {
62
+ serverCompilationTimings.push(
63
+ await getServerCompilationTimings(benchmarkStartTime, attemptStartTime, i)
64
+ );
65
+ serverBuildTimeSamples.push(stats.server.buildTime);
44
66
 
45
- clientSamples[i] = stats.clientBuildTime;
46
- serverSamples[i] = stats.serverBuildTime;
47
- maxMemoryRssSamples[i] = stats.maxMemoryRss;
67
+ if (stats.server.maxMemoryRss) {
68
+ clientMaxMemoryRssSamples.push(stats.server.maxMemoryRss);
69
+ }
70
+ }
71
+
72
+ if (buildType === 'all' || buildType === 'client') {
73
+ clientCompilationTimings.push(
74
+ await getClientCompilationTimings(benchmarkStartTime, attemptStartTime, i)
75
+ );
76
+ clientBuildTimeSamples.push(stats.client.buildTime);
77
+
78
+ if (stats.client.maxMemoryRss) {
79
+ clientMaxMemoryRssSamples.push(stats.client.maxMemoryRss);
80
+ }
81
+ }
82
+
83
+ if (stats.maxMemoryRss) {
84
+ maxMemoryRssSamples.push(stats.maxMemoryRss);
85
+ }
48
86
  }
49
87
 
50
88
  return {
51
- clientSamples,
52
- serverSamples,
89
+ serverCompilationTimings,
90
+ clientCompilationTimings,
91
+ clientBuildTimeSamples,
92
+ serverBuildTimeSamples,
53
93
  maxMemoryRssSamples,
94
+ clientMaxMemoryRssSamples,
95
+ serverMaxMemoryRssSamples,
54
96
  };
55
97
  };
56
98
 
57
- export const benchmarkBuild = async (di: Container): Promise<BuildResult> => {
58
- const { times = 5 } = di.get(COMMAND_PARAMETERS_TOKEN) as Params;
99
+ export const benchmarkBuild = async (di: Container): Promise<Result> => {
100
+ const { times = DEFAULT_TIMES, commandOptions } = di.get(COMMAND_PARAMETERS_TOKEN) as Params;
101
+ const noCache = !commandOptions.fileCache;
59
102
 
60
- const noCache = await runBuildCommand(di, {
61
- times: Math.max(Math.floor(times / 2), 2),
62
- shouldClearCache: true,
63
- });
103
+ let result;
64
104
 
65
- const cache = await runBuildCommand(di, {
66
- times,
67
- shouldClearCache: false,
68
- });
105
+ if (noCache) {
106
+ result = await runBuildCommand(di, {
107
+ times,
108
+ shouldClearCache: true,
109
+ });
110
+ } else {
111
+ result = await runBuildCommand(di, {
112
+ // additional first attempt for cache warmup
113
+ times: times + 1,
114
+ shouldClearCache: false,
115
+ });
116
+ }
69
117
 
70
- return {
71
- cache: getResultStats(cache),
72
- noCache: getResultStats(noCache),
73
- };
118
+ return getResultStats(result);
74
119
  };
@@ -0,0 +1 @@
1
+ export const DEFAULT_TIMES = 3;
@@ -1,9 +1,9 @@
1
1
  import type { Provider } from '@tinkoff/dippy';
2
2
  import { createCommand } from '../../commands/createCommand';
3
3
  import { COMMAND_PARAMETERS_TOKEN, WITH_BUILD_STATS_TOKEN } from '../../di/tokens';
4
- import type { BuildParams, BuildResult } from './build';
4
+ import type { BuildParams } from './build';
5
5
  import { benchmarkBuild } from './build';
6
- import type { StartParams, StartResult } from './start';
6
+ import type { StartParams } from './start';
7
7
  import { benchmarkStart } from './start';
8
8
  import type { RunStats } from './types';
9
9
 
@@ -13,14 +13,12 @@ export interface Params {
13
13
  times?: number;
14
14
  }
15
15
 
16
- export interface Result {
17
- [key: string]: RunStats | undefined;
18
- }
16
+ export type Result = RunStats;
19
17
 
20
18
  export type BenchmarkCommand = (
21
19
  params: StartParams | BuildParams,
22
20
  providers?: Provider[]
23
- ) => Promise<StartResult | BuildResult>;
21
+ ) => Promise<Result>;
24
22
 
25
23
  export default createCommand({
26
24
  name: 'benchmark',
@@ -1,24 +1,19 @@
1
+ /* eslint-disable max-statements */
1
2
  import type { Container } from '@tinkoff/dippy';
2
3
  import type { Params as OriginalStartParams, Result as OriginalStartResult } from '../start/index';
3
4
  import { COMMAND_PARAMETERS_TOKEN, COMMAND_RUNNER_TOKEN } from '../../di/tokens';
4
5
  import type { Params, Result } from './index';
5
- import type { Samples, RunStats } from './types';
6
+ import type { Samples, CompilationStats } from './types';
6
7
  import { clearCacheDirectory } from './utils/clearCache';
7
8
  import { getResultStats } from './utils/stats';
8
-
9
- const REBUILD_WARMUP_TIMES = 3;
9
+ import { getClientCompilationTimings, getServerCompilationTimings } from './utils/compilationUtils';
10
+ import { DEFAULT_TIMES } from './const';
10
11
 
11
12
  export interface StartParams extends Params {
12
13
  command: 'start';
13
14
  commandOptions: OriginalStartParams;
14
15
  }
15
16
 
16
- export interface StartResult extends Result {
17
- noCache?: RunStats;
18
- cache?: RunStats;
19
- rebuild?: RunStats;
20
- }
21
-
22
17
  const runStartCommand = async (
23
18
  di: Container,
24
19
  {
@@ -29,17 +24,29 @@ const runStartCommand = async (
29
24
  shouldClearCache: boolean;
30
25
  }
31
26
  ): Promise<Samples> => {
32
- const clientSamples: number[] = Array(times);
33
- const serverSamples: number[] = Array(times);
34
- const maxMemoryRssSamples: number[] = Array(times);
27
+ const clientBuildTimeSamples: number[] = [];
28
+ const serverBuildTimeSamples: number[] = [];
29
+ const clientMaxMemoryRssSamples: number[] = [];
30
+ const serverMaxMemoryRssSamples: number[] = [];
31
+
32
+ const serverCompilationTimings: CompilationStats[] = [];
33
+ const clientCompilationTimings: CompilationStats[] = [];
34
+
35
+ const maxMemoryRssSamples: number[] = [];
35
36
 
36
37
  const { commandOptions } = di.get(COMMAND_PARAMETERS_TOKEN) as StartParams;
38
+ commandOptions.benchmark = true;
39
+
40
+ const buildType = commandOptions.buildType ?? 'all';
41
+
42
+ const benchmarkStartTime = new Date().toISOString();
37
43
 
38
44
  for (let i = 0; i < times; i++) {
39
45
  if (shouldClearCache) {
40
46
  await clearCacheDirectory(di);
41
47
  }
42
48
 
49
+ const attemptStartTime = Date.now();
43
50
  const { close, getBuildStats } = await (di
44
51
  .get(COMMAND_RUNNER_TOKEN)
45
52
  .run('start', commandOptions) as OriginalStartResult);
@@ -47,70 +54,67 @@ const runStartCommand = async (
47
54
 
48
55
  await close();
49
56
 
50
- clientSamples[i] = stats.clientBuildTime;
51
- serverSamples[i] = stats.serverBuildTime;
52
- maxMemoryRssSamples[i] = stats.maxMemoryRss;
53
- }
54
-
55
- return {
56
- clientSamples,
57
- serverSamples,
58
- maxMemoryRssSamples,
59
- };
60
- };
61
-
62
- const runRebuild = async (di: Container, { times }: { times: number }): Promise<Samples> => {
63
- const clientSamples: number[] = Array(times);
64
- const serverSamples: number[] = Array(times);
65
- const maxMemoryRssSamples: number[] = Array(times);
66
- const { commandOptions } = di.get(COMMAND_PARAMETERS_TOKEN) as StartParams;
57
+ // at first attempt do not save metrics if cache is used
58
+ if (!shouldClearCache && i === 0) {
59
+ continue;
60
+ }
67
61
 
68
- const { close, invalidate, getBuildStats } = await (di
69
- .get(COMMAND_RUNNER_TOKEN)
70
- .run('start', commandOptions) as OriginalStartResult);
62
+ if (buildType === 'all' || buildType === 'server') {
63
+ serverCompilationTimings.push(
64
+ await getServerCompilationTimings(benchmarkStartTime, attemptStartTime, i)
65
+ );
66
+ serverBuildTimeSamples.push(stats.server.buildTime);
71
67
 
72
- // warmup rebuild as it usually pretty slow at first runs
73
- for (let i = 0; i < REBUILD_WARMUP_TIMES; i++) {
74
- await invalidate();
75
- }
68
+ if (stats.server.maxMemoryRss) {
69
+ serverMaxMemoryRssSamples.push(stats.server.maxMemoryRss);
70
+ }
71
+ }
76
72
 
77
- for (let i = 0; i < times; i++) {
78
- await invalidate();
73
+ if (buildType === 'all' || buildType === 'client') {
74
+ clientCompilationTimings.push(
75
+ await getClientCompilationTimings(benchmarkStartTime, attemptStartTime, i)
76
+ );
77
+ clientBuildTimeSamples.push(stats.client.buildTime);
79
78
 
80
- const stats = getBuildStats();
79
+ if (stats.client.maxMemoryRss) {
80
+ clientMaxMemoryRssSamples.push(stats.client.maxMemoryRss);
81
+ }
82
+ }
81
83
 
82
- clientSamples[i] = stats.clientBuildTime;
83
- serverSamples[i] = stats.serverBuildTime;
84
- maxMemoryRssSamples[i] = stats.maxMemoryRss;
84
+ if (stats.maxMemoryRss) {
85
+ maxMemoryRssSamples.push(stats.maxMemoryRss);
86
+ }
85
87
  }
86
88
 
87
- await close();
88
-
89
89
  return {
90
- clientSamples,
91
- serverSamples,
90
+ serverCompilationTimings,
91
+ clientCompilationTimings,
92
+ clientMaxMemoryRssSamples,
93
+ serverMaxMemoryRssSamples,
94
+ clientBuildTimeSamples,
95
+ serverBuildTimeSamples,
92
96
  maxMemoryRssSamples,
93
97
  };
94
98
  };
95
99
 
96
- export const benchmarkStart = async (di: Container): Promise<StartResult> => {
97
- const { times = 5 } = di.get(COMMAND_PARAMETERS_TOKEN) as Params;
98
-
99
- const noCache = await runStartCommand(di, {
100
- times: Math.max(Math.floor(times / 3), 2),
101
- shouldClearCache: true,
102
- });
103
-
104
- const cache = await runStartCommand(di, {
105
- times: Math.max(Math.floor(times / 2), 2),
106
- shouldClearCache: false,
107
- });
108
-
109
- const rebuild = await runRebuild(di, { times });
100
+ export const benchmarkStart = async (di: Container): Promise<Result> => {
101
+ const { times = DEFAULT_TIMES, commandOptions } = di.get(COMMAND_PARAMETERS_TOKEN) as Params;
102
+ const noCache = !commandOptions.fileCache;
103
+
104
+ let result;
105
+
106
+ if (noCache) {
107
+ result = await runStartCommand(di, {
108
+ times,
109
+ shouldClearCache: true,
110
+ });
111
+ } else {
112
+ result = await runStartCommand(di, {
113
+ // additional first attempt for cache warmup
114
+ times: times + 1,
115
+ shouldClearCache: false,
116
+ });
117
+ }
110
118
 
111
- return {
112
- cache: getResultStats(cache),
113
- noCache: getResultStats(noCache),
114
- rebuild: getResultStats(rebuild),
115
- };
119
+ return getResultStats(result);
116
120
  };
@@ -1,19 +1,87 @@
1
- export interface Stats {
2
- samples: number[];
3
- mean: number;
4
- std: number;
5
-
6
- variance: number;
7
- }
8
-
9
1
  export type Samples = {
10
- clientSamples: Stats['samples'];
11
- serverSamples: Stats['samples'];
12
- maxMemoryRssSamples: Stats['samples'];
2
+ serverCompilationTimings: CompilationStats[];
3
+ serverBuildTimeSamples: number[];
4
+ clientCompilationTimings: CompilationStats[];
5
+ clientBuildTimeSamples: number[];
6
+ maxMemoryRssSamples: number[];
7
+ clientMaxMemoryRssSamples: number[];
8
+ serverMaxMemoryRssSamples: number[];
9
+ };
10
+
11
+ export type CompilationStats = {
12
+ totalBuildCosts: Record<string, number>;
13
+ loaderBuildCosts: Record<string, number>;
14
+ pluginBuildCosts: Record<string, number>;
13
15
  };
14
16
 
15
17
  export type RunStats = {
16
- client: Stats;
17
- server: Stats;
18
- maxMemoryRss: Stats;
18
+ serverCompilationStats: CompilationStats | undefined;
19
+ clientCompilationStats: CompilationStats | undefined;
20
+ clientBuildTime: number | undefined;
21
+ serverBuildTime: number | undefined;
22
+ clientMaxMemoryRss: number | undefined;
23
+ serverMaxMemoryRss: number | undefined;
24
+ maxMemoryRss: number | undefined;
25
+ };
26
+
27
+ type PlainObject<T = any> = {
28
+ [key: string]: T;
19
29
  };
30
+
31
+ interface ProcessData {
32
+ /**
33
+ * process id
34
+ */
35
+ pid: number;
36
+ /**
37
+ * parent process id
38
+ */
39
+ ppid: number | null;
40
+ }
41
+
42
+ export interface LoaderTransformData extends ProcessData {
43
+ /** loader name */
44
+ loader: string;
45
+ /**
46
+ * loader index
47
+ */
48
+ loaderIndex: number;
49
+ /** loader path */
50
+ path: string;
51
+ input: string | null | undefined;
52
+ /**
53
+ * - isPitch: true: the result of loader.pitch()
54
+ * - isPitch: false: the code result of loader()
55
+ */
56
+ result: string | null | undefined;
57
+ /** Timestamp when called */
58
+ startAt: number;
59
+ endAt: number;
60
+ /** loader configuration */
61
+ options: PlainObject;
62
+ /** pitching loader */
63
+ isPitch: boolean;
64
+ /**
65
+ * is sync
66
+ */
67
+ sync: boolean;
68
+ /** Error during conversion */
69
+ // errors: DevToolErrorInstance[];
70
+ /** module layer */
71
+ layer?: string;
72
+ }
73
+
74
+ export type MinimalLoaderData = Pick<LoaderTransformData, 'startAt' | 'endAt' | 'pid' | 'loader'>;
75
+
76
+ export interface PluginData {
77
+ /** hook tap name */
78
+ tapName: string;
79
+ /** hook call time-consuming */
80
+ costs: number;
81
+ startAt: number;
82
+ endAt: number;
83
+ /** hook function type */
84
+ type: 'sync' | 'async' | 'promise';
85
+ /** hook function result */
86
+ result: any;
87
+ }
@@ -0,0 +1,213 @@
1
+ import fs from 'node:fs/promises';
2
+
3
+ import { CompilationStats, LoaderTransformData, MinimalLoaderData, PluginData } from '../types';
4
+
5
+ export async function getServerCompilationTimings(
6
+ benchmarkStartTime: string,
7
+ attemptStartTime: number,
8
+ attempt: number
9
+ ) {
10
+ return getCompilationTimings(benchmarkStartTime, attemptStartTime, attempt, 'server');
11
+ }
12
+
13
+ export async function getClientCompilationTimings(
14
+ benchmarkStartTime: string,
15
+ attemptStartTime: number,
16
+ attempt: number
17
+ ) {
18
+ return getCompilationTimings(benchmarkStartTime, attemptStartTime, attempt, 'client');
19
+ }
20
+
21
+ async function getCompilationTimings(
22
+ benchmarkStartTime: string,
23
+ attemptStartTime: number,
24
+ attempt: number,
25
+ type: 'client' | 'server'
26
+ ): Promise<CompilationStats> {
27
+ try {
28
+ const rsdoctorData = await getReportData(
29
+ await processReport(benchmarkStartTime, attempt, type)
30
+ );
31
+
32
+ const { summary, loader, plugin } = rsdoctorData;
33
+
34
+ const totalBuildCosts = calculateTotalCosts(summary, attemptStartTime);
35
+ const loaderBuildCosts = calculateLoadersCosts(loader);
36
+ const pluginBuildCosts = calculatePluginsCosts(plugin);
37
+
38
+ return { totalBuildCosts, loaderBuildCosts, pluginBuildCosts };
39
+ } catch (err) {
40
+ throw new Error(`Failed to get rsdoctor report!\n${err}`);
41
+ }
42
+ }
43
+
44
+ async function getReportData(reportPath: string) {
45
+ const rsdoctorRawStats = await fs.readFile(reportPath, 'utf-8');
46
+ const rsdoctorStats = JSON.parse(rsdoctorRawStats);
47
+
48
+ return rsdoctorStats.data;
49
+ }
50
+
51
+ async function processReport(
52
+ benchmarkStartTime: string,
53
+ attempt: number,
54
+ type: 'client' | 'server'
55
+ ) {
56
+ const reportBasePath = `./.rsdoctor/${benchmarkStartTime}`;
57
+ const reportPath = `${reportBasePath}/${type}-rsdoctor-data-${attempt + 1}.json`;
58
+
59
+ await fs.mkdir(reportBasePath, { recursive: true });
60
+ await fs.rename(`./.rsdoctor/${type}-rsdoctor-data.json`, reportPath);
61
+
62
+ return reportPath;
63
+ }
64
+
65
+ function mergeIntervals(intervals: [number, number][]) {
66
+ // Sort from small to large
67
+ intervals.sort((a, b) => a[0] - b[0]);
68
+ // The previous interval, the next interval, store the result
69
+ let previous;
70
+ let current;
71
+ const result: [number, number][] = [];
72
+
73
+ for (let i = 0; i < intervals.length; i++) {
74
+ current = intervals[i];
75
+ // If the first interval or the current interval does not overlap with the previous interval, add the current interval to the result
76
+ if (!previous || current[0] > previous[1]) {
77
+ // Assign the current interval to the previous interval
78
+ previous = current;
79
+ result.push(current);
80
+ } else {
81
+ // Otherwise, the two intervals overlap
82
+ // Update the end time of the previous interval
83
+ previous[1] = Math.max(previous[1], current[1]);
84
+ }
85
+ }
86
+
87
+ return result;
88
+ }
89
+
90
+ function getLoadersCosts(
91
+ filter: (loader: MinimalLoaderData) => boolean,
92
+ loaders: MinimalLoaderData[]
93
+ ) {
94
+ const match: { [pid: number | string]: [start: number, end: number][] } = {};
95
+ const others: { [pid: number | string]: [start: number, end: number][] } = {};
96
+
97
+ loaders.forEach((e) => {
98
+ if (filter(e)) {
99
+ if (!match[e.pid]) match[e.pid] = [];
100
+ match[e.pid].push([e.startAt, e.endAt]);
101
+ } else {
102
+ if (!others[e.pid]) others[e.pid] = [];
103
+ others[e.pid].push([e.startAt, e.endAt]);
104
+ }
105
+ });
106
+
107
+ let costs = 0;
108
+
109
+ const pids = Object.keys(match);
110
+
111
+ for (let i = 0; i < pids.length; i++) {
112
+ const pid = pids[i];
113
+ const _match = mergeIntervals(match[pid]);
114
+ // between in loader.startAt and loader.endAt
115
+ const _others = mergeIntervals(others[pid] || []).filter(([s, e]) =>
116
+ _match.some((el) => s >= el[0] && e <= el[1])
117
+ );
118
+
119
+ // eslint-disable-next-line no-param-reassign
120
+ const matchSum = _match.length ? _match.reduce((t, c) => (t += c[1] - c[0]), 0) : 0;
121
+ // eslint-disable-next-line no-param-reassign
122
+ const othersSum = _others.length ? _others.reduce((t, c) => (t += c[1] - c[0]), 0) : 0;
123
+
124
+ costs += matchSum - othersSum;
125
+ }
126
+
127
+ return costs;
128
+ }
129
+
130
+ function calculateTotalCosts(
131
+ summary: { costs: { name: string; cost: string }[] },
132
+ attemptStartTime: number
133
+ ) {
134
+ const { costs: buildCosts } = summary;
135
+ const buildCostsMap = buildCosts.reduce((acc, cost) => {
136
+ acc[cost.name] = cost;
137
+ return acc;
138
+ }, {});
139
+
140
+ const result: Record<string, number> = {};
141
+
142
+ for (const buildCostName in buildCostsMap) {
143
+ if (buildCostName === 'bootstrap->beforeCompile') {
144
+ // start of bootstrap is start of first build, so use attemptStarTime instead
145
+ result[buildCostName] =
146
+ buildCostsMap['beforeCompile->afterCompile'].startAt - attemptStartTime;
147
+ } else {
148
+ result[buildCostName] = buildCostsMap[buildCostName].costs;
149
+ }
150
+ }
151
+
152
+ return result;
153
+ }
154
+
155
+ function calculateLoadersCosts(loaders: { loaders: LoaderTransformData[] }[]) {
156
+ const filteredLoaders: MinimalLoaderData[] = [];
157
+ const uniqueLoaders = new Map<
158
+ string,
159
+ {
160
+ files: number;
161
+ path: string;
162
+ }
163
+ >();
164
+
165
+ loaders.forEach((data) => {
166
+ data.loaders.forEach((fl) => {
167
+ const uniqueLoader = uniqueLoaders.get(fl.loader);
168
+ if (uniqueLoader) {
169
+ uniqueLoaders.set(fl.loader, {
170
+ files: uniqueLoader.files + 1,
171
+ path: fl.path,
172
+ });
173
+ } else {
174
+ uniqueLoaders.set(fl.loader, { files: 1, path: fl.path });
175
+ }
176
+
177
+ return filteredLoaders.push({
178
+ loader: fl.loader,
179
+ startAt: fl.startAt,
180
+ endAt: fl.endAt,
181
+ pid: fl.pid,
182
+ });
183
+ });
184
+ });
185
+
186
+ const costs: Record<string, number> = {};
187
+ uniqueLoaders.forEach((_, loaderName: string) => {
188
+ costs[loaderName] = getLoadersCosts((item) => item.loader === loaderName, filteredLoaders);
189
+ });
190
+
191
+ return costs;
192
+ }
193
+
194
+ function calculatePluginsCosts(plugins: PluginData) {
195
+ const pluginCosts: Record<string, number> = {};
196
+
197
+ for (const hookName in plugins) {
198
+ const hookCalls = plugins[hookName];
199
+
200
+ hookCalls.forEach((plugin) => {
201
+ const pluginName = plugin.tapName;
202
+ const pluginCost = plugin.costs;
203
+
204
+ if (pluginCosts[pluginName]) {
205
+ pluginCosts[pluginName] += pluginCost;
206
+ } else {
207
+ pluginCosts[pluginName] = pluginCost;
208
+ }
209
+ });
210
+ }
211
+
212
+ return pluginCosts;
213
+ }