@bundle-stats/utils 4.4.0-beta.0 → 4.4.0-beta.3
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.
- package/lib/config/component-links.js +5 -1
- package/lib/config/component-links.js.map +1 -1
- package/lib/config/file-types.js +3 -3
- package/lib/config/file-types.js.map +1 -1
- package/lib/config/index.js +0 -1
- package/lib/config/index.js.map +1 -1
- package/lib/config/metrics.js.map +1 -1
- package/lib/constants.js +11 -5
- package/lib/constants.js.map +1 -1
- package/lib/i18n.js +5 -0
- package/lib/i18n.js.map +1 -1
- package/lib/utils/component-links.js +85 -9
- package/lib/utils/component-links.js.map +1 -1
- package/lib/utils/index.js +1 -0
- package/lib/utils/index.js.map +1 -1
- package/lib/utils/insights.js +54 -0
- package/lib/utils/insights.js.map +1 -0
- package/lib/webpack/extract/assets-size-total-insight.js +2 -2
- package/lib/webpack/extract/assets-size-total-insight.js.map +1 -1
- package/lib/webpack/extract/index.js +2 -0
- package/lib/webpack/extract/index.js.map +1 -1
- package/lib/webpack/extract/modules-packages-duplicate.js +28 -16
- package/lib/webpack/extract/modules-packages-duplicate.js.map +1 -1
- package/lib/webpack/extract/modules-packages-new-insight.js +57 -0
- package/lib/webpack/extract/modules-packages-new-insight.js.map +1 -0
- package/lib/webpack/extract/modules.js +45 -22
- package/lib/webpack/extract/modules.js.map +1 -1
- package/lib/webpack/metrics.js +5 -5
- package/lib/webpack/metrics.js.map +1 -1
- package/lib/webpack/types.js +1 -1
- package/lib/webpack/types.js.map +1 -1
- package/lib-esm/config/component-links.js +4 -0
- package/lib-esm/config/component-links.js.map +1 -1
- package/lib-esm/config/file-types.js +3 -3
- package/lib-esm/config/file-types.js.map +1 -1
- package/lib-esm/config/index.js +0 -1
- package/lib-esm/config/index.js.map +1 -1
- package/lib-esm/config/metrics.js +1 -1
- package/lib-esm/config/metrics.js.map +1 -1
- package/lib-esm/constants.js +6 -0
- package/lib-esm/constants.js.map +1 -1
- package/lib-esm/i18n.js +5 -0
- package/lib-esm/i18n.js.map +1 -1
- package/lib-esm/utils/component-links.js +79 -7
- package/lib-esm/utils/component-links.js.map +1 -1
- package/lib-esm/utils/index.js +1 -0
- package/lib-esm/utils/index.js.map +1 -1
- package/lib-esm/utils/insights.js +47 -0
- package/lib-esm/utils/insights.js.map +1 -0
- package/lib-esm/webpack/extract/assets-size-total-insight.js +1 -1
- package/lib-esm/webpack/extract/assets-size-total-insight.js.map +1 -1
- package/lib-esm/webpack/extract/index.js +2 -0
- package/lib-esm/webpack/extract/index.js.map +1 -1
- package/lib-esm/webpack/extract/modules-packages-duplicate.js +22 -10
- package/lib-esm/webpack/extract/modules-packages-duplicate.js.map +1 -1
- package/lib-esm/webpack/extract/modules-packages-new-insight.js +54 -0
- package/lib-esm/webpack/extract/modules-packages-new-insight.js.map +1 -0
- package/lib-esm/webpack/extract/modules.js +45 -23
- package/lib-esm/webpack/extract/modules.js.map +1 -1
- package/lib-esm/webpack/metrics.js +5 -5
- package/lib-esm/webpack/metrics.js.map +1 -1
- package/package.json +13 -13
- package/src/config/component-links.js +5 -0
- package/src/config/file-types.js +3 -3
- package/src/config/index.js +0 -1
- package/src/config/metrics.js +2 -6
- package/src/constants.ts +22 -10
- package/src/i18n.js +5 -0
- package/src/utils/component-links.ts +96 -10
- package/src/utils/index.js +1 -0
- package/src/utils/insights.ts +82 -0
- package/src/webpack/extract/assets-size-total-insight.js +1 -1
- package/src/webpack/extract/index.js +2 -0
- package/src/webpack/extract/modules-packages-duplicate.ts +32 -17
- package/src/webpack/extract/modules-packages-new-insight.ts +80 -0
- package/src/webpack/extract/modules.ts +60 -33
- package/src/webpack/metrics.js +5 -5
- package/src/webpack/types.ts +1 -2
- package/types/browsertime/metrics.d.ts +22 -22
- package/types/config/component-links.d.ts +24 -18
- package/types/config/file-types.d.ts +17 -17
- package/types/config/index.d.ts +0 -1
- package/types/config/metrics.d.ts +1 -30
- package/types/constants.d.ts +21 -9
- package/types/i18n.d.ts +17 -12
- package/types/lighthouse/metrics.d.ts +16 -16
- package/types/utils/component-links.d.ts +8 -1
- package/types/utils/index.d.ts +1 -0
- package/types/utils/insights.d.ts +9 -0
- package/types/webpack/constants.d.ts +1 -1
- package/types/webpack/extract/assets-size-total-insight.d.ts +2 -2
- package/types/webpack/extract/modules-packages-duplicate.d.ts +3 -3
- package/types/webpack/extract/modules-packages-new-insight.d.ts +8 -0
- package/types/webpack/metrics.d.ts +51 -51
- package/types/webpack/types.d.ts +1 -2
- package/lib/config/insights.js +0 -10
- package/lib/config/insights.js.map +0 -1
- package/lib-esm/config/insights.js +0 -7
- package/lib-esm/config/insights.js.map +0 -1
- package/src/config/insights.ts +0 -5
- package/tsconfig.lib.json +0 -7
- package/types/config/insights.d.ts +0 -5
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
MODULE_FILE_TYPE,
|
|
17
17
|
MODULE_FILTERS,
|
|
18
18
|
MODULE_SOURCE_TYPE,
|
|
19
|
+
MODULE_SOURCE_TYPE_LABELS,
|
|
19
20
|
PACKAGE_FILTERS,
|
|
20
21
|
SECTIONS,
|
|
21
22
|
} from '../config/component-links';
|
|
@@ -25,6 +26,7 @@ export type ComponentLinkFilters = Record<string, boolean>;
|
|
|
25
26
|
|
|
26
27
|
export interface ComponentLinkParams {
|
|
27
28
|
search?: string;
|
|
29
|
+
entryId?: string;
|
|
28
30
|
filters: ComponentLinkFilters;
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -159,6 +161,51 @@ export const BUNDLE_ASSETS_CHUNK_COUNT: ComponentLink = {
|
|
|
159
161
|
},
|
|
160
162
|
};
|
|
161
163
|
|
|
164
|
+
export const getBundleAsset = (entryId: string): ComponentLink => ({
|
|
165
|
+
section: SECTIONS.ASSETS,
|
|
166
|
+
title: 'View asset details' as any,
|
|
167
|
+
params: {
|
|
168
|
+
[COMPONENT.BUNDLE_ASSETS]: {
|
|
169
|
+
entryId,
|
|
170
|
+
filters: {},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
export const getBundleAssetsByEntryType = (
|
|
176
|
+
assetType: 'entry' | 'initial' | 'chunk',
|
|
177
|
+
): ComponentLink => ({
|
|
178
|
+
section: SECTIONS.ASSETS,
|
|
179
|
+
title: I18N.COMPONENT_LINK_BUNDLE_ASSETS_BY_ENTRY_TYPE(assetType),
|
|
180
|
+
params: {
|
|
181
|
+
[COMPONENT.BUNDLE_ASSETS]: {
|
|
182
|
+
filters: {
|
|
183
|
+
[ASSET_FILTERS.CHANGED]: false,
|
|
184
|
+
...getAssetEntryTypeFilters(false),
|
|
185
|
+
[`${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.ENTRY}`]: assetType === 'entry',
|
|
186
|
+
[`${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.INITIAL}`]: assetType === 'initial',
|
|
187
|
+
[`${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.CHUNK}`]: assetType === 'chunk',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
export const getBundleAssetsFileTypeComponentLink = (
|
|
194
|
+
fileType: string,
|
|
195
|
+
label: string = fileType,
|
|
196
|
+
): ComponentLink => ({
|
|
197
|
+
section: SECTIONS.ASSETS,
|
|
198
|
+
title: I18N.COMPONENT_LINK_BUNDLE_ASSETS_BY_FILE_TYPE({ label }),
|
|
199
|
+
params: {
|
|
200
|
+
[COMPONENT.BUNDLE_ASSETS]: {
|
|
201
|
+
filters: {
|
|
202
|
+
...getAssetFileTypeFilters(false),
|
|
203
|
+
[`${ASSET_FILE_TYPE}.${fileType}`]: true,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
162
209
|
export const BUNDLE_MODULES: ComponentLink = {
|
|
163
210
|
section: SECTIONS.MODULES,
|
|
164
211
|
title: I18N.COMPONENT_LINK_MODULES,
|
|
@@ -221,6 +268,35 @@ export const getBundleModulesByChunk = (
|
|
|
221
268
|
},
|
|
222
269
|
});
|
|
223
270
|
|
|
271
|
+
export const getBundleModulesByFileTpe = (
|
|
272
|
+
fileType: string,
|
|
273
|
+
fileTypeLabel?: string,
|
|
274
|
+
): ComponentLink => ({
|
|
275
|
+
section: SECTIONS.MODULES,
|
|
276
|
+
title: I18N.COMPONENT_LINK_MODULES_BY_FILE_TYPE(fileTypeLabel || fileType),
|
|
277
|
+
params: {
|
|
278
|
+
[COMPONENT.BUNDLE_MODULES]: {
|
|
279
|
+
filters: {
|
|
280
|
+
...getModuleFileTypeFilters(false),
|
|
281
|
+
[`${MODULE_FILE_TYPE}.${fileType}`]: true,
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
export const getBundleModulesBySource = (thirdParty: boolean, label = ''): ComponentLink => ({
|
|
288
|
+
section: SECTIONS.MODULES,
|
|
289
|
+
title: I18N.COMPONENT_LINK_MODULES_BY_SOURCE(label),
|
|
290
|
+
params: {
|
|
291
|
+
[COMPONENT.BUNDLE_MODULES]: {
|
|
292
|
+
filters: {
|
|
293
|
+
[`${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.THIRD_PARTY}`]: thirdParty,
|
|
294
|
+
[`${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.FIRST_PARTY}`]: !thirdParty,
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
224
300
|
export const BUNDLE_PACKAGES_COUNT: ComponentLink = {
|
|
225
301
|
section: SECTIONS.PACKAGES,
|
|
226
302
|
title: I18N.COMPONENT_LINK_PACKAGES_COUNT,
|
|
@@ -234,6 +310,19 @@ export const BUNDLE_PACKAGES_COUNT: ComponentLink = {
|
|
|
234
310
|
},
|
|
235
311
|
};
|
|
236
312
|
|
|
313
|
+
export const BUNDLE_PACKAGES_CHANGED: ComponentLink = {
|
|
314
|
+
section: SECTIONS.PACKAGES,
|
|
315
|
+
title: I18N.COMPONENT_LINK_PACKAGES_CHANGED,
|
|
316
|
+
params: {
|
|
317
|
+
[COMPONENT.BUNDLE_PACKAGES]: {
|
|
318
|
+
filters: {
|
|
319
|
+
// Keep a filter to allow the merge and skip default
|
|
320
|
+
[PACKAGE_FILTERS.CHANGED]: true,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
|
|
237
326
|
export const BUNDLE_PACKAGES_DUPLICATE: ComponentLink = {
|
|
238
327
|
section: SECTIONS.PACKAGES,
|
|
239
328
|
title: I18N.COMPONENT_LINK_PACKAGES_DUPLICATE,
|
|
@@ -246,21 +335,18 @@ export const BUNDLE_PACKAGES_DUPLICATE: ComponentLink = {
|
|
|
246
335
|
},
|
|
247
336
|
};
|
|
248
337
|
|
|
249
|
-
export const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
): ComponentLink => ({
|
|
253
|
-
section: SECTIONS.ASSETS,
|
|
254
|
-
title: I18N.COMPONENT_LINK_BUNDLE_ASSETS_BY_FILE_TYPE({ label }),
|
|
338
|
+
export const BUNDLE_PACKAGES_DUPLICATE_CHANGED: ComponentLink = {
|
|
339
|
+
section: SECTIONS.PACKAGES,
|
|
340
|
+
title: I18N.COMPONENT_LINK_PACKAGES_DUPLICATE_CHANGED,
|
|
255
341
|
params: {
|
|
256
|
-
[COMPONENT.
|
|
342
|
+
[COMPONENT.BUNDLE_PACKAGES]: {
|
|
257
343
|
filters: {
|
|
258
|
-
|
|
259
|
-
[
|
|
344
|
+
[PACKAGE_FILTERS.CHANGED]: true,
|
|
345
|
+
[PACKAGE_FILTERS.DUPLICATE]: true,
|
|
260
346
|
},
|
|
261
347
|
},
|
|
262
348
|
},
|
|
263
|
-
}
|
|
349
|
+
};
|
|
264
350
|
|
|
265
351
|
export const getBundlePackagesByNameComponentLink = (search: string): ComponentLink => ({
|
|
266
352
|
section: SECTIONS.PACKAGES,
|
package/src/utils/index.js
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import isEmpty from 'lodash/isEmpty';
|
|
2
|
+
|
|
3
|
+
import { InsightType, JobInsight, JobInsightsInfo } from '../constants';
|
|
4
|
+
import {
|
|
5
|
+
BUNDLE_PACKAGES_DUPLICATE,
|
|
6
|
+
BUNDLE_PACKAGES_CHANGED,
|
|
7
|
+
BUNDLE_PACKAGES_DUPLICATE_CHANGED,
|
|
8
|
+
} from './component-links';
|
|
9
|
+
|
|
10
|
+
interface InsightListItem {
|
|
11
|
+
/**
|
|
12
|
+
* Inight name
|
|
13
|
+
*/
|
|
14
|
+
name: string;
|
|
15
|
+
/**
|
|
16
|
+
* Insight data
|
|
17
|
+
*/
|
|
18
|
+
insight: JobInsight;
|
|
19
|
+
/**
|
|
20
|
+
* Custom link props
|
|
21
|
+
*/
|
|
22
|
+
link: any;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the insigh list ordered by level type
|
|
27
|
+
*/
|
|
28
|
+
export const getInsightList = (insights: JobInsightsInfo): Array<InsightListItem> => {
|
|
29
|
+
const { duplicatePackages, newPackages } = insights;
|
|
30
|
+
|
|
31
|
+
const insightsByLevel: Record<InsightType, Array<InsightListItem>> = {
|
|
32
|
+
[InsightType.ERROR]: [],
|
|
33
|
+
[InsightType.WARNING]: [],
|
|
34
|
+
[InsightType.INFO]: [],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
if (duplicatePackages) {
|
|
38
|
+
insightsByLevel[duplicatePackages.type].push({
|
|
39
|
+
name: 'duplicatePackages',
|
|
40
|
+
insight: duplicatePackages,
|
|
41
|
+
link:
|
|
42
|
+
duplicatePackages.type === InsightType.WARNING
|
|
43
|
+
? BUNDLE_PACKAGES_DUPLICATE
|
|
44
|
+
: BUNDLE_PACKAGES_DUPLICATE_CHANGED,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (newPackages) {
|
|
49
|
+
insightsByLevel[newPackages.type].push({
|
|
50
|
+
name: 'newPackages',
|
|
51
|
+
insight: newPackages,
|
|
52
|
+
link: BUNDLE_PACKAGES_CHANGED,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return [
|
|
57
|
+
...(insightsByLevel[InsightType.ERROR] || []),
|
|
58
|
+
...(insightsByLevel[InsightType.WARNING] || []),
|
|
59
|
+
...(insightsByLevel[InsightType.INFO] || []),
|
|
60
|
+
];
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Show only the insights that are introduced by a change
|
|
65
|
+
*/
|
|
66
|
+
export const getChangedInsights = (normalizedInsights: JobInsightsInfo): JobInsightsInfo | null => {
|
|
67
|
+
const res: JobInsightsInfo = {};
|
|
68
|
+
|
|
69
|
+
if (normalizedInsights.duplicatePackages?.type === InsightType.ERROR) {
|
|
70
|
+
res.duplicatePackages = normalizedInsights.duplicatePackages;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (normalizedInsights.newPackages) {
|
|
74
|
+
res.newPackages = normalizedInsights.newPackages;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isEmpty(res)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return res;
|
|
82
|
+
};
|
|
@@ -11,6 +11,7 @@ import { extractModules } from './modules';
|
|
|
11
11
|
import { extractModulesPackages } from './modules-packages';
|
|
12
12
|
import { extractModulesPackagesCount } from './modules-packages-count';
|
|
13
13
|
import { extractModulesPackagesDuplicate } from './modules-packages-duplicate';
|
|
14
|
+
import { extractModulesPackagesNewInsight } from './modules-packages-new-insight';
|
|
14
15
|
|
|
15
16
|
const extractFns = [
|
|
16
17
|
extractAssets,
|
|
@@ -24,6 +25,7 @@ const extractFns = [
|
|
|
24
25
|
extractModulesPackages,
|
|
25
26
|
extractModulesPackagesCount,
|
|
26
27
|
extractModulesPackagesDuplicate,
|
|
28
|
+
extractModulesPackagesNewInsight,
|
|
27
29
|
];
|
|
28
30
|
|
|
29
31
|
export const extract = (webpackStats, baseline) =>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import isEmpty from 'lodash/isEmpty';
|
|
1
2
|
import map from 'lodash/map';
|
|
2
3
|
import orderBy from 'lodash/orderBy';
|
|
3
4
|
import sum from 'lodash/sum';
|
|
4
5
|
|
|
5
|
-
import { InsightType } from '../../config';
|
|
6
6
|
import {
|
|
7
|
+
InsightType,
|
|
7
8
|
Job,
|
|
8
9
|
JobInsight,
|
|
9
10
|
JobInsightDuplicatePackagesData,
|
|
@@ -25,9 +26,9 @@ interface DuplicatePackageGroup {
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
interface ModulesPackagesDuplicateData {
|
|
28
|
-
insights
|
|
29
|
+
insights?: {
|
|
29
30
|
duplicatePackages?: JobInsights['webpack']['duplicatePackages'];
|
|
30
|
-
duplicatePackagesV3
|
|
31
|
+
duplicatePackagesV3?: JobInsights['webpack']['duplicatePackagesV3'];
|
|
31
32
|
};
|
|
32
33
|
metrics: {
|
|
33
34
|
duplicatePackagesCount: MetricRun;
|
|
@@ -37,7 +38,7 @@ interface ModulesPackagesDuplicateData {
|
|
|
37
38
|
export const getDuplicatePackagesInsight = (
|
|
38
39
|
duplicatePackagesMap: Record<string, Array<string>>,
|
|
39
40
|
baselineDuplicatePackagesMap?: Record<string, Array<string>>,
|
|
40
|
-
): JobInsight<JobInsightDuplicatePackagesV3Data> => {
|
|
41
|
+
): JobInsight<JobInsightDuplicatePackagesV3Data> | null => {
|
|
41
42
|
const duplicateInstances: Array<string> = [];
|
|
42
43
|
const baselineDuplicateInstances: Array<string> = [];
|
|
43
44
|
const newDuplicateInstances: Array<string> = [];
|
|
@@ -81,8 +82,8 @@ export const getDuplicatePackagesInsight = (
|
|
|
81
82
|
const newDuplicateInstancesCount = newDuplicateInstances.length;
|
|
82
83
|
const removedDuplicateInstancesCount = removedDuplicateInstances.length;
|
|
83
84
|
|
|
84
|
-
let text
|
|
85
|
-
let insightType: InsightType;
|
|
85
|
+
let text = '';
|
|
86
|
+
let insightType: InsightType | null = null;
|
|
86
87
|
|
|
87
88
|
if (newDuplicateInstancesCount > 0 && removedDuplicateInstancesCount > 0) {
|
|
88
89
|
// New duplicate packages and removed duplicates
|
|
@@ -90,7 +91,7 @@ export const getDuplicatePackagesInsight = (
|
|
|
90
91
|
newDuplicateInstancesCount > 1 || removedDuplicateInstancesCount > 1 ? 'packages' : 'package';
|
|
91
92
|
text = `Bundle introduced ${newDuplicateInstancesCount} and removed ${removedDuplicateInstancesCount} duplicate ${item}`;
|
|
92
93
|
insightType = InsightType.ERROR;
|
|
93
|
-
} else if (newDuplicateInstancesCount > 0 && baselineDuplicatePackagesMap) {
|
|
94
|
+
} else if (newDuplicateInstancesCount > 0 && !isEmpty(baselineDuplicatePackagesMap)) {
|
|
94
95
|
// New duplicate packages and baseline comparison
|
|
95
96
|
const item = newDuplicateInstancesCount > 1 ? 'packages' : 'package';
|
|
96
97
|
text = `Bundle introduced ${newDuplicateInstancesCount} duplicate ${item}`;
|
|
@@ -100,6 +101,12 @@ export const getDuplicatePackagesInsight = (
|
|
|
100
101
|
const item = newDuplicateInstancesCount > 1 ? 'packages' : 'package';
|
|
101
102
|
text = `Bundle contains ${newDuplicateInstancesCount} duplicate ${item}`;
|
|
102
103
|
insightType = InsightType.WARNING;
|
|
104
|
+
} else if (removedDuplicateInstancesCount > 0 && duplicateInstancesCount > 0) {
|
|
105
|
+
// Removed duplicate packages, but there are duplicate packages remaining
|
|
106
|
+
const itemRemoved = removedDuplicateInstancesCount > 1 ? 'packages' : 'package';
|
|
107
|
+
const itemRemaining = duplicateInstancesCount > 1 ? 'packages' : 'package';
|
|
108
|
+
text = `Bundle removed ${removedDuplicateInstancesCount} duplicate ${itemRemoved}, ${duplicateInstancesCount} duplicate ${itemRemaining} remaining`;
|
|
109
|
+
insightType = InsightType.WARNING;
|
|
103
110
|
} else if (removedDuplicateInstancesCount > 0) {
|
|
104
111
|
// Removed duplicate packages
|
|
105
112
|
const item = removedDuplicateInstancesCount > 1 ? 'packages' : 'package';
|
|
@@ -115,9 +122,11 @@ export const getDuplicatePackagesInsight = (
|
|
|
115
122
|
text = `Bundle contains ${duplicateInstancesCount} duplicate ${item}`;
|
|
116
123
|
insightType = InsightType.WARNING;
|
|
117
124
|
} else {
|
|
118
|
-
// No change
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
// No change - noop
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!text || !insightType) {
|
|
129
|
+
return null;
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
return {
|
|
@@ -180,11 +189,15 @@ export const extractModulesPackagesDuplicate = (
|
|
|
180
189
|
baselineJob?.insights?.webpack?.duplicatePackages?.data ||
|
|
181
190
|
{};
|
|
182
191
|
|
|
192
|
+
const noDuplicatesDuplicatePackagesV3 = getDuplicatePackagesInsight({}, baselineDuplicatePackages);
|
|
193
|
+
|
|
183
194
|
if (!count) {
|
|
184
195
|
return {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
196
|
+
...(noDuplicatesDuplicatePackagesV3 && {
|
|
197
|
+
insights: {
|
|
198
|
+
duplicatePackagesV3: noDuplicatesDuplicatePackagesV3,
|
|
199
|
+
},
|
|
200
|
+
}),
|
|
188
201
|
metrics: {
|
|
189
202
|
duplicatePackagesCount: {
|
|
190
203
|
value: count,
|
|
@@ -221,16 +234,18 @@ export const extractModulesPackagesDuplicate = (
|
|
|
221
234
|
duplicatePackagesData[packageName] = packageData.children.map(({ id }) => id);
|
|
222
235
|
});
|
|
223
236
|
|
|
237
|
+
const duplicatePackagesV3 = getDuplicatePackagesInsight(
|
|
238
|
+
duplicatePackagesData,
|
|
239
|
+
baselineDuplicatePackages,
|
|
240
|
+
);
|
|
241
|
+
|
|
224
242
|
return {
|
|
225
243
|
insights: {
|
|
226
244
|
duplicatePackages: {
|
|
227
245
|
type: InsightType.WARNING,
|
|
228
246
|
data: duplicatePackagesData,
|
|
229
247
|
},
|
|
230
|
-
duplicatePackagesV3
|
|
231
|
-
duplicatePackagesData,
|
|
232
|
-
baselineDuplicatePackages,
|
|
233
|
-
),
|
|
248
|
+
...(duplicatePackagesV3 && { duplicatePackagesV3 }),
|
|
234
249
|
},
|
|
235
250
|
metrics: {
|
|
236
251
|
duplicatePackagesCount: {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { InsightType, Job, JobInsights } from '../../constants';
|
|
2
|
+
import { Packages } from '../types';
|
|
3
|
+
|
|
4
|
+
interface ModulesPackagesNewData {
|
|
5
|
+
insights?: {
|
|
6
|
+
newPackages: JobInsights['webpack']['newPackages'];
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const getPackageNames = (packages: Packages): Set<string> => {
|
|
11
|
+
const result: Set<string> = new Set();
|
|
12
|
+
|
|
13
|
+
Object.values(packages).forEach((metricPackage) => {
|
|
14
|
+
result.add(metricPackage.name);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const getText = (newPackages: Array<string>) => {
|
|
21
|
+
if (newPackages.length === 1) {
|
|
22
|
+
return `Bundle introduced one new package: ${newPackages[0]}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const firstNewPackages = newPackages.slice(0, 3);
|
|
26
|
+
const otherPackages = newPackages.slice(3);
|
|
27
|
+
|
|
28
|
+
const text = `Bundle introduced ${newPackages.length} new packages: ${firstNewPackages.join(', ')}`;
|
|
29
|
+
|
|
30
|
+
if (otherPackages.length === 0) {
|
|
31
|
+
return text;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (otherPackages.length === 1) {
|
|
35
|
+
return `${text} and one more`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return `${text} and ${otherPackages.length} more`;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const extractModulesPackagesNewInsight = (
|
|
42
|
+
_: any,
|
|
43
|
+
currentExtractedData: any,
|
|
44
|
+
baselineJob?: Job,
|
|
45
|
+
): ModulesPackagesNewData | null => {
|
|
46
|
+
const currentMetricPackages = currentExtractedData?.metrics?.packages as Packages;
|
|
47
|
+
const baselineMetricPackages = baselineJob?.metrics?.webpack?.packages as Packages;
|
|
48
|
+
|
|
49
|
+
// Skip insight when the baseline data is missing
|
|
50
|
+
if (!baselineMetricPackages) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const currentPackages = getPackageNames(currentMetricPackages);
|
|
55
|
+
const baselinePackages = getPackageNames(baselineMetricPackages);
|
|
56
|
+
|
|
57
|
+
const newPackages: Array<string> = [];
|
|
58
|
+
|
|
59
|
+
currentPackages.forEach((packageName) => {
|
|
60
|
+
if (!baselinePackages.has(packageName)) {
|
|
61
|
+
newPackages.push(packageName);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (newPackages.length === 0) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
insights: {
|
|
71
|
+
newPackages: {
|
|
72
|
+
type: InsightType.WARNING,
|
|
73
|
+
data: {
|
|
74
|
+
text: getText(newPackages),
|
|
75
|
+
packages: newPackages,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import round from 'lodash/round';
|
|
2
|
-
import
|
|
2
|
+
import union from 'lodash/union';
|
|
3
|
+
import { WebpackStatsFiltered, WebpackStatsFilteredModule } from '@bundle-stats/plugin-webpack-filter';
|
|
3
4
|
|
|
4
5
|
import { Module, MetricsModules } from '../types';
|
|
5
6
|
import { getModuleName, normalizeChunkId } from '../utils';
|
|
6
7
|
|
|
7
8
|
/*
|
|
8
|
-
* Extract webpack modules
|
|
9
|
+
* Extract webpack modules metrics and stats
|
|
9
10
|
*/
|
|
10
11
|
export const extractModules = (webpackStats?: WebpackStatsFiltered): MetricsModules => {
|
|
11
12
|
const modulesSource = webpackStats?.modules;
|
|
@@ -22,60 +23,86 @@ export const extractModules = (webpackStats?: WebpackStatsFiltered): MetricsModu
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
// Flatten concatenated modules
|
|
25
|
-
const
|
|
26
|
-
if (!moduleEntry.modules) {
|
|
27
|
-
agg.push(moduleEntry);
|
|
26
|
+
const modulesByName: Map<string, WebpackStatsFilteredModule> = new Map();
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
modulesSource.forEach((rootModule) => {
|
|
29
|
+
// skip modules that do not belong to any chunk
|
|
30
|
+
if (!rootModule.chunks || rootModule.chunks.length === 0) {
|
|
31
|
+
return;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
// flatten concatenated modules
|
|
35
|
+
if (rootModule.modules) {
|
|
36
|
+
rootModule.modules.forEach((concatenatedModule) => {
|
|
37
|
+
const concatenatedModuleName = getModuleName(concatenatedModule.name);
|
|
38
|
+
const existingModule = modulesByName.get(concatenatedModuleName);
|
|
39
|
+
|
|
40
|
+
// Merge if the module is already available
|
|
41
|
+
// webpack adds a module entry for every concatenated module
|
|
42
|
+
if (existingModule) {
|
|
43
|
+
modulesByName.set(concatenatedModuleName, {
|
|
44
|
+
...existingModule,
|
|
45
|
+
chunks: union(existingModule.chunks, rootModule.chunks),
|
|
46
|
+
});
|
|
47
|
+
} else {
|
|
48
|
+
modulesByName.set(concatenatedModuleName, {
|
|
49
|
+
...concatenatedModule,
|
|
50
|
+
chunks: rootModule.chunks,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const normalizedName = getModuleName(rootModule.name);
|
|
59
|
+
const savedRootModule = modulesByName.get(normalizedName);
|
|
60
|
+
|
|
61
|
+
// Merge module data
|
|
62
|
+
// @NOTE some webpack plugins (mini-css-extract-plugin) might add a root module entry
|
|
63
|
+
// even if thee module was already concantenated in another module
|
|
64
|
+
if (savedRootModule) {
|
|
65
|
+
modulesByName.set(normalizedName, {
|
|
66
|
+
...savedRootModule,
|
|
67
|
+
chunks: union(savedRootModule.chunks, rootModule.chunks),
|
|
68
|
+
size: rootModule.size,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
40
73
|
|
|
41
|
-
|
|
42
|
-
}
|
|
74
|
+
modulesByName.set(normalizedName, rootModule);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Normalize module entries and extract metrics
|
|
78
|
+
const modules: Record<string, Module> = {};
|
|
43
79
|
|
|
44
80
|
let moduleCount = 0;
|
|
45
81
|
let totalCodeSize = 0;
|
|
46
82
|
let duplicateCodeSize = 0;
|
|
47
83
|
let duplicateModulesCount = 0;
|
|
48
84
|
|
|
49
|
-
|
|
50
|
-
const modules = allModules.reduce((agg, moduleEntry) => {
|
|
85
|
+
modulesByName.forEach((moduleEntry, normalizedName) => {
|
|
51
86
|
const { name, size = 0, chunks } = moduleEntry;
|
|
52
|
-
const normalizedName = getModuleName(name);
|
|
53
|
-
|
|
54
|
-
// skip modules that are orphane(do not belong to any chunk)
|
|
55
|
-
if (!chunks || chunks?.length === 0) {
|
|
56
|
-
return agg;
|
|
57
|
-
}
|
|
58
87
|
|
|
59
88
|
const instances = chunks.length;
|
|
60
89
|
const duplicateInstances = instances - 1;
|
|
90
|
+
const duplicated = duplicateInstances > 0;
|
|
61
91
|
|
|
62
92
|
moduleCount += instances;
|
|
63
|
-
duplicateModulesCount += duplicateInstances;
|
|
64
|
-
duplicateCodeSize += duplicateInstances * size;
|
|
65
93
|
totalCodeSize += instances * size;
|
|
66
94
|
|
|
67
|
-
|
|
95
|
+
if (duplicated) {
|
|
96
|
+
duplicateModulesCount += duplicateInstances;
|
|
97
|
+
duplicateCodeSize += duplicateInstances * size;
|
|
98
|
+
}
|
|
68
99
|
|
|
69
|
-
|
|
70
|
-
agg[normalizedName] = {
|
|
100
|
+
modules[normalizedName] = {
|
|
71
101
|
name,
|
|
72
102
|
value: size,
|
|
73
103
|
chunkIds: chunks.map(normalizeChunkId),
|
|
74
|
-
duplicated
|
|
75
|
-
...(reasons && { reasons }),
|
|
104
|
+
...(duplicated && { duplicated }),
|
|
76
105
|
};
|
|
77
|
-
|
|
78
|
-
return agg;
|
|
79
106
|
}, {} as Record<string, Module>);
|
|
80
107
|
|
|
81
108
|
const duplicateCode = totalCodeSize ? round((duplicateCodeSize / totalCodeSize) * 100, 2) : 0;
|
package/src/webpack/metrics.js
CHANGED
|
@@ -9,7 +9,7 @@ import { Metric } from './types';
|
|
|
9
9
|
export const metrics = {
|
|
10
10
|
[Metric.BUNDLE_SIZE]: {
|
|
11
11
|
label: 'Bundle Size',
|
|
12
|
-
description: 'The total size of the assets
|
|
12
|
+
description: 'The total size of the assets processed by the bundler',
|
|
13
13
|
url: 'https://relative-ci.com/documentation/metrics-and-data#assets',
|
|
14
14
|
type: METRIC_TYPE_FILE_SIZE,
|
|
15
15
|
},
|
|
@@ -48,14 +48,14 @@ export const metrics = {
|
|
|
48
48
|
},
|
|
49
49
|
[Metric.MODULE_COUNT]: {
|
|
50
50
|
label: 'Modules',
|
|
51
|
-
description: 'The total number of
|
|
51
|
+
description: 'The total number of bundled modules',
|
|
52
52
|
url: 'https://relative-ci.com/documentation/metrics-and-data#modules-1',
|
|
53
53
|
type: METRIC_TYPE_NUMERIC,
|
|
54
54
|
biggerIsBetter: false,
|
|
55
55
|
},
|
|
56
56
|
[Metric.DUPLICATE_MODULES_COUNT]: {
|
|
57
57
|
label: 'Duplicate Modules',
|
|
58
|
-
description: 'The total number of
|
|
58
|
+
description: 'The total number of module instances bundled into multiple chunks',
|
|
59
59
|
url: 'https://relative-ci.com/documentation/metrics-and-data#modules-1',
|
|
60
60
|
type: METRIC_TYPE_NUMERIC,
|
|
61
61
|
biggerIsBetter: false,
|
|
@@ -69,14 +69,14 @@ export const metrics = {
|
|
|
69
69
|
},
|
|
70
70
|
[Metric.PACKAGE_COUNT]: {
|
|
71
71
|
label: 'Packages',
|
|
72
|
-
description: 'The total number of
|
|
72
|
+
description: 'The total number of bundled packages',
|
|
73
73
|
url: 'https://relative-ci.com/documentation/metrics-and-data#packages',
|
|
74
74
|
type: METRIC_TYPE_NUMERIC,
|
|
75
75
|
biggerIsBetter: false,
|
|
76
76
|
},
|
|
77
77
|
[Metric.DUPLICATE_PACKAGES_COUNT]: {
|
|
78
78
|
label: 'Duplicate Packages',
|
|
79
|
-
description: 'The total number of duplicate
|
|
79
|
+
description: 'The total number of bundled duplicate package instances',
|
|
80
80
|
url: 'https://relative-ci.com/documentation/metrics-and-data#packages',
|
|
81
81
|
type: METRIC_TYPE_NUMERIC,
|
|
82
82
|
biggerIsBetter: false,
|
package/src/webpack/types.ts
CHANGED
|
@@ -51,8 +51,7 @@ export interface MetricsAssets {
|
|
|
51
51
|
export interface Module extends MetricRun {
|
|
52
52
|
name: string;
|
|
53
53
|
chunkIds: Array<string>;
|
|
54
|
-
duplicated
|
|
55
|
-
reasons?: Array<string>;
|
|
54
|
+
duplicated?: boolean;
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
export type Modules = Record<string, Module>;
|