@bundle-stats/utils 4.2.2 → 4.2.3-beta.1

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 (188) hide show
  1. package/README.md +15 -10
  2. package/lib/browsertime/metrics.js +22 -22
  3. package/lib/browsertime/metrics.js.map +1 -1
  4. package/lib/config/component-links.js +7 -1
  5. package/lib/config/component-links.js.map +1 -1
  6. package/lib/config/file-types.js +3 -3
  7. package/lib/config/file-types.js.map +1 -1
  8. package/lib/config/index.js +1 -2
  9. package/lib/config/index.js.map +1 -1
  10. package/lib/config/metric-types.js +28 -0
  11. package/lib/config/metric-types.js.map +1 -0
  12. package/lib/constants.js +23 -22
  13. package/lib/constants.js.map +1 -1
  14. package/lib/i18n.js +15 -10
  15. package/lib/i18n.js.map +1 -1
  16. package/lib/jobs/create-job.js +17 -7
  17. package/lib/jobs/create-job.js.map +1 -1
  18. package/lib/jobs/create-jobs.js.map +1 -1
  19. package/lib/lighthouse/metrics.js +13 -13
  20. package/lib/lighthouse/metrics.js.map +1 -1
  21. package/lib/report/add-row-metric-data.js +4 -4
  22. package/lib/report/add-row-metric-data.js.map +1 -1
  23. package/lib/report/compare-metrics.js +4 -1
  24. package/lib/report/compare-metrics.js.map +1 -1
  25. package/lib/report/create.js +17 -7
  26. package/lib/report/create.js.map +1 -1
  27. package/lib/report/get-metric-changed.js.map +1 -1
  28. package/lib/report/index.js +1 -0
  29. package/lib/report/index.js.map +1 -1
  30. package/lib/report/merge-metrics-by-key.js +1 -1
  31. package/lib/report/merge-metrics-by-key.js.map +1 -1
  32. package/lib/utils/component-links.js +191 -27
  33. package/lib/utils/component-links.js.map +1 -1
  34. package/lib/utils/date.js.map +1 -1
  35. package/lib/utils/delta.js +35 -30
  36. package/lib/utils/delta.js.map +1 -1
  37. package/lib/utils/format.js +5 -6
  38. package/lib/utils/format.js.map +1 -1
  39. package/lib/utils/insights.js.map +1 -1
  40. package/lib/utils/metrics.js +49 -32
  41. package/lib/utils/metrics.js.map +1 -1
  42. package/lib/webpack/compare.js +16 -12
  43. package/lib/webpack/compare.js.map +1 -1
  44. package/lib/webpack/extract/assets.js +7 -1
  45. package/lib/webpack/extract/assets.js.map +1 -1
  46. package/lib/webpack/extract/meta.js.map +1 -1
  47. package/lib/webpack/extract/modules-packages-duplicate.js +13 -8
  48. package/lib/webpack/extract/modules-packages-duplicate.js.map +1 -1
  49. package/lib/webpack/extract/modules-packages-new-insight.js.map +1 -1
  50. package/lib/webpack/extract/modules-packages.js +8 -1
  51. package/lib/webpack/extract/modules-packages.js.map +1 -1
  52. package/lib/webpack/extract/modules.js +45 -20
  53. package/lib/webpack/extract/modules.js.map +1 -1
  54. package/lib/webpack/metrics.js +35 -34
  55. package/lib/webpack/metrics.js.map +1 -1
  56. package/lib/webpack/selectors.js +40 -3
  57. package/lib/webpack/selectors.js.map +1 -1
  58. package/lib/webpack/types.js +1 -1
  59. package/lib/webpack/types.js.map +1 -1
  60. package/lib/webpack/utils.js +37 -25
  61. package/lib/webpack/utils.js.map +1 -1
  62. package/lib-esm/browsertime/metrics.js +22 -22
  63. package/lib-esm/browsertime/metrics.js.map +1 -1
  64. package/lib-esm/config/component-links.js +6 -0
  65. package/lib-esm/config/component-links.js.map +1 -1
  66. package/lib-esm/config/file-types.js +3 -3
  67. package/lib-esm/config/file-types.js.map +1 -1
  68. package/lib-esm/config/index.js +1 -2
  69. package/lib-esm/config/index.js.map +1 -1
  70. package/lib-esm/config/metric-types.js +25 -0
  71. package/lib-esm/config/metric-types.js.map +1 -0
  72. package/lib-esm/constants.js +19 -18
  73. package/lib-esm/constants.js.map +1 -1
  74. package/lib-esm/i18n.js +15 -10
  75. package/lib-esm/i18n.js.map +1 -1
  76. package/lib-esm/jobs/create-job.js.map +1 -1
  77. package/lib-esm/jobs/create-jobs.js.map +1 -1
  78. package/lib-esm/lighthouse/metrics.js +13 -13
  79. package/lib-esm/lighthouse/metrics.js.map +1 -1
  80. package/lib-esm/report/add-row-metric-data.js +4 -4
  81. package/lib-esm/report/add-row-metric-data.js.map +1 -1
  82. package/lib-esm/report/compare-metrics.js +4 -1
  83. package/lib-esm/report/compare-metrics.js.map +1 -1
  84. package/lib-esm/report/create.js.map +1 -1
  85. package/lib-esm/report/get-metric-changed.js.map +1 -1
  86. package/lib-esm/report/index.js +1 -0
  87. package/lib-esm/report/index.js.map +1 -1
  88. package/lib-esm/report/merge-metrics-by-key.js +1 -1
  89. package/lib-esm/report/merge-metrics-by-key.js.map +1 -1
  90. package/lib-esm/utils/component-links.js +185 -27
  91. package/lib-esm/utils/component-links.js.map +1 -1
  92. package/lib-esm/utils/date.js.map +1 -1
  93. package/lib-esm/utils/delta.js +32 -26
  94. package/lib-esm/utils/delta.js.map +1 -1
  95. package/lib-esm/utils/format.js.map +1 -1
  96. package/lib-esm/utils/insights.js.map +1 -1
  97. package/lib-esm/utils/metrics.js +44 -27
  98. package/lib-esm/utils/metrics.js.map +1 -1
  99. package/lib-esm/webpack/compare.js +7 -5
  100. package/lib-esm/webpack/compare.js.map +1 -1
  101. package/lib-esm/webpack/extract/assets.js +7 -1
  102. package/lib-esm/webpack/extract/assets.js.map +1 -1
  103. package/lib-esm/webpack/extract/meta.js.map +1 -1
  104. package/lib-esm/webpack/extract/modules-packages-duplicate.js +13 -8
  105. package/lib-esm/webpack/extract/modules-packages-duplicate.js.map +1 -1
  106. package/lib-esm/webpack/extract/modules-packages-new-insight.js.map +1 -1
  107. package/lib-esm/webpack/extract/modules-packages.js +8 -1
  108. package/lib-esm/webpack/extract/modules-packages.js.map +1 -1
  109. package/lib-esm/webpack/extract/modules.js +45 -20
  110. package/lib-esm/webpack/extract/modules.js.map +1 -1
  111. package/lib-esm/webpack/metrics.js +35 -34
  112. package/lib-esm/webpack/metrics.js.map +1 -1
  113. package/lib-esm/webpack/selectors.js +35 -1
  114. package/lib-esm/webpack/selectors.js.map +1 -1
  115. package/lib-esm/webpack/utils.js +37 -25
  116. package/lib-esm/webpack/utils.js.map +1 -1
  117. package/package.json +20 -22
  118. package/src/browsertime/metrics.js +22 -22
  119. package/src/config/component-links.js +7 -0
  120. package/src/config/file-types.js +3 -3
  121. package/src/config/index.js +1 -2
  122. package/src/config/metric-types.ts +25 -0
  123. package/src/constants.ts +94 -34
  124. package/src/i18n.js +15 -10
  125. package/src/lighthouse/metrics.js +13 -15
  126. package/src/report/add-row-metric-data.ts +42 -38
  127. package/src/report/compare-metrics.ts +7 -4
  128. package/src/report/create.ts +1 -1
  129. package/src/report/index.js +1 -0
  130. package/src/report/merge-metrics-by-key.ts +7 -5
  131. package/src/report/types.ts +17 -0
  132. package/src/utils/component-links.ts +276 -35
  133. package/src/utils/delta.ts +98 -0
  134. package/src/utils/metrics.ts +88 -0
  135. package/src/webpack/compare.ts +62 -0
  136. package/src/webpack/extract/assets.ts +13 -7
  137. package/src/webpack/extract/modules-packages-duplicate.ts +23 -15
  138. package/src/webpack/extract/modules-packages.ts +74 -61
  139. package/src/webpack/extract/modules.ts +66 -33
  140. package/src/webpack/{metrics.js → metrics.ts} +41 -39
  141. package/src/webpack/selectors.js +82 -9
  142. package/src/webpack/types.ts +13 -4
  143. package/src/webpack/utils.ts +173 -0
  144. package/types/browsertime/compare.d.ts +1 -1
  145. package/types/browsertime/metrics.d.ts +63 -43
  146. package/types/config/component-links.d.ts +26 -18
  147. package/types/config/file-types.d.ts +17 -17
  148. package/types/config/index.d.ts +1 -2
  149. package/types/config/metric-types.d.ts +2 -0
  150. package/types/constants.d.ts +52 -29
  151. package/types/i18n.d.ts +19 -14
  152. package/types/lighthouse/compare.d.ts +1 -1
  153. package/types/lighthouse/filter.d.ts +1 -1
  154. package/types/lighthouse/metrics.d.ts +36 -28
  155. package/types/report/add-row-metric-data.d.ts +1 -16
  156. package/types/report/compare-metrics.d.ts +1 -1
  157. package/types/report/index.d.ts +1 -0
  158. package/types/report/merge-metrics-by-key.d.ts +1 -1
  159. package/types/report/types.d.ts +12 -0
  160. package/types/utils/component-links.d.ts +24 -8
  161. package/types/utils/delta.d.ts +4 -6
  162. package/types/utils/metrics.d.ts +4 -3
  163. package/types/webpack/compare.d.ts +11 -14
  164. package/types/webpack/constants.d.ts +1 -1
  165. package/types/webpack/extract/assets-size-total-insight.d.ts +2 -2
  166. package/types/webpack/extract/modules-packages-duplicate.d.ts +3 -3
  167. package/types/webpack/extract/modules.d.ts +1 -1
  168. package/types/webpack/metrics.d.ts +7 -153
  169. package/types/webpack/selectors.d.ts +21 -7
  170. package/types/webpack/types.d.ts +4 -4
  171. package/types/webpack/utils.d.ts +8 -8
  172. package/CHANGELOG.md +0 -333
  173. package/lib/config/delta.js +0 -13
  174. package/lib/config/delta.js.map +0 -1
  175. package/lib/config/metrics.js +0 -32
  176. package/lib/config/metrics.js.map +0 -1
  177. package/lib-esm/config/delta.js +0 -10
  178. package/lib-esm/config/delta.js.map +0 -1
  179. package/lib-esm/config/metrics.js +0 -29
  180. package/lib-esm/config/metrics.js.map +0 -1
  181. package/src/config/delta.js +0 -11
  182. package/src/config/metrics.js +0 -35
  183. package/src/utils/delta.js +0 -94
  184. package/src/utils/metrics.js +0 -92
  185. package/src/webpack/compare.js +0 -81
  186. package/src/webpack/utils.js +0 -155
  187. package/types/config/delta.d.ts +0 -9
  188. package/types/config/metrics.d.ts +0 -35
package/src/i18n.js CHANGED
@@ -1,17 +1,22 @@
1
1
  export default {
2
2
  COMPONENT_LINK_TOTALS: 'View total size by type',
3
- COMPONENT_LINK_BUNDLE_ASSETS_INITIAL_JS: 'View all initial JS assets',
4
- COMPONENT_LINK_BUNDLE_ASSETS_INITIAL_CSS: 'View all initial CSS assets',
5
- COMPONENT_LINK_BUNDLE_ASSETS_CACHE_INVALIDATION: 'View all changed assets',
6
- COMPONENT_LINK_BUNDLE_ASSETS_COUNT: 'View all assets',
7
- COMPONENT_LINK_BUNDLE_ASSETS_CHUNK_COUNT: 'View all chunks',
3
+ COMPONENT_LINK_BUNDLE_ASSET_DETAILS: 'View asset details',
4
+ COMPONENT_LINK_BUNDLE_ASSETS_INITIAL_JS: 'View initial JS assets',
5
+ COMPONENT_LINK_BUNDLE_ASSETS_INITIAL_CSS: 'View initial CSS assets',
6
+ COMPONENT_LINK_BUNDLE_ASSETS_CACHE_INVALIDATION: 'View changed assets',
7
+ COMPONENT_LINK_BUNDLE_ASSETS_COUNT: 'View assets',
8
+ COMPONENT_LINK_BUNDLE_ASSETS_CHUNK_COUNT: 'View chunks',
9
+ COMPONENT_LINK_BUNDLE_ASSETS_CHUNKS: 'View chunk assets',
8
10
  COMPONENT_LINK_MODULES: 'View modules',
9
11
  COMPONENT_LINK_MODULES_DUPLICATE: 'View duplicate modules',
12
+ COMPONENT_LINK_MODULES_BY_FILE_TYPE: (fileType) => `View ${fileType} modules`,
13
+ COMPONENT_LINK_MODULES_BY_SOURCE: (source) => `View ${source} modules`,
10
14
  COMPONENT_LINK_CHUNK_MODULES: 'View chunk modules',
11
- COMPONENT_LINK_PACKAGES_COUNT: 'View all packages',
12
- COMPONENT_LINK_PACKAGES_CHANGED: 'View all changed packages',
13
- COMPONENT_LINK_PACKAGES_DUPLICATE: 'View all duplicate packages',
14
- COMPONENT_LINK_PACKAGES_DUPLICATE_CHANGED: 'View all changed duplicate packages',
15
+ COMPONENT_LINK_PACKAGES_COUNT: 'View packages',
16
+ COMPONENT_LINK_PACKAGES_CHANGED: 'View changed packages',
17
+ COMPONENT_LINK_PACKAGES_DUPLICATE: 'View duplicate packages',
18
+ COMPONENT_LINK_PACKAGES_DUPLICATE_CHANGED: 'View changed duplicate packages',
15
19
  COMPONENT_LINK_VIEW_PACKAGE: 'View package',
16
- COMPONENT_LINK_BUNDLE_ASSETS_BY_FILE_TYPE: ({ label }) => `View all ${label} assets`,
20
+ COMPONENT_LINK_BUNDLE_ASSETS_BY_FILE_TYPE: ({ label }) => `View ${label} assets`,
21
+ COMPONENT_LINK_BUNDLE_ASSETS_BY_ENTRY_TYPE: (assetType) => `View ${assetType} assets`,
17
22
  };
@@ -1,55 +1,53 @@
1
- import {
2
- METRIC_TYPE_SCORE, METRIC_TYPE_DURATION, METRIC_TYPE_FILE_SIZE, METRIC_TYPE_NUMERIC,
3
- } from '../config/metrics';
1
+ import { MetricTypes } from '../constants';
4
2
 
5
3
  export const metrics = {
6
4
  score: {
7
5
  label: 'Score',
8
- type: METRIC_TYPE_SCORE,
6
+ type: MetricTypes.Score,
9
7
  },
10
8
  performanceScore: {
11
9
  label: 'Performance',
12
- type: METRIC_TYPE_SCORE,
10
+ type: MetricTypes.Score,
13
11
  },
14
12
  accessibilityScore: {
15
13
  label: 'Accessibility',
16
- type: METRIC_TYPE_SCORE,
14
+ type: MetricTypes.Score,
17
15
  },
18
16
  bestPracticesScore: {
19
17
  label: 'Best Practices',
20
- type: METRIC_TYPE_SCORE,
18
+ type: MetricTypes.Score,
21
19
  },
22
20
  seoScore: {
23
21
  label: 'SEO',
24
- type: METRIC_TYPE_SCORE,
22
+ type: MetricTypes.Score,
25
23
  },
26
24
  pwaScore: {
27
25
  label: 'PWA',
28
- type: METRIC_TYPE_SCORE,
26
+ type: MetricTypes.Score,
29
27
  },
30
28
 
31
29
  speedIndex: {
32
30
  label: 'Perceptual Speed Index',
33
- type: METRIC_TYPE_DURATION,
31
+ type: MetricTypes.Duration,
34
32
  },
35
33
  firstMeaningfulPaint: {
36
34
  label: 'First Meaningful Paint',
37
- type: METRIC_TYPE_DURATION,
35
+ type: MetricTypes.Duration,
38
36
  },
39
37
  timeToFirstByte: {
40
38
  label: 'Time To First Byte',
41
- type: METRIC_TYPE_DURATION,
39
+ type: MetricTypes.Duration,
42
40
  },
43
41
  firstInteractive: {
44
42
  label: 'First Interactive',
45
- type: METRIC_TYPE_DURATION,
43
+ type: MetricTypes.Duration,
46
44
  },
47
45
  totalByteWeight: {
48
46
  label: 'Total Weight',
49
- type: METRIC_TYPE_FILE_SIZE,
47
+ type: MetricTypes.FileSize,
50
48
  },
51
49
  domSize: {
52
50
  label: 'DOM Size',
53
- type: METRIC_TYPE_NUMERIC,
51
+ type: MetricTypes.Numeric,
54
52
  },
55
53
  };
@@ -1,53 +1,57 @@
1
1
  import get from 'lodash/get';
2
2
 
3
3
  import { getGlobalMetricType, getMetricRunInfo } from '../utils/metrics';
4
+ import { MetricTypes } from '../constants';
4
5
  import { getMetricChanged } from './get-metric-changed';
5
6
  import * as types from './types';
6
7
 
8
+ /* eslint-disable prettier/prettier */
7
9
  /**
8
- * Add row metric diff data
10
+ * Add metric item data
9
11
  */
10
- export const getAddRowMetricData = (metricType?: string) => (row: types.ReportRow) => {
11
- const { key, runs } = row;
12
-
13
- // Resolve row metric
14
- // - if the key is a predefined metric, use it
15
- // - if the key is not matching an existing metric, use the default metricType
16
- const metric = getGlobalMetricType(
17
- key,
18
- typeof metricType === 'string' && metricType, // explicit, avoid passing of map cb params
19
- );
20
- const { biggerIsBetter, label } = metric;
21
-
22
- const processedRuns = runs.map((run, index) => {
23
- if (!run || typeof run.value === 'undefined' || run.value === null) {
24
- return null;
25
- }
26
-
27
- const metricRunInfo = getMetricRunInfo(
28
- metric,
29
- run.value,
30
- // Get baseline value only if not the latest run
31
- (index + 1 < runs.length) ? get(runs, [index + 1, 'value'], 0) : undefined,
12
+ export const getAddRowMetricData =
13
+ (metricType?: string) => (row: types.ReportRow): types.ReportMetricRow => {
14
+ const { key, runs } = row;
15
+
16
+ // Resolve row metric
17
+ // - if the key is a predefined metric, use it
18
+ // - if the key is not matching an existing metric, use the default metricType
19
+ const metric = getGlobalMetricType(
20
+ key,
21
+ typeof metricType === 'string' ? (metricType as MetricTypes) : undefined, // explicit, avoid passing of map cb params
32
22
  );
23
+ const { biggerIsBetter, label } = metric;
24
+
25
+ const processedRuns = runs.map((run, index) => {
26
+ if (!run || typeof run.value === 'undefined' || run.value === null) {
27
+ return null;
28
+ }
29
+
30
+ const metricRunInfo = getMetricRunInfo(
31
+ metric,
32
+ run.value,
33
+ // Get baseline value only if not the latest run
34
+ index + 1 < runs.length ? get(runs, [index + 1, 'value'], 0) : undefined,
35
+ );
36
+
37
+ return {
38
+ ...run,
39
+ ...metricRunInfo,
40
+ };
41
+ });
33
42
 
34
43
  return {
35
- ...run,
36
- ...metricRunInfo,
37
- };
38
- });
39
-
40
- return {
41
- ...row,
44
+ key: row.key,
42
45
 
43
- // Metric props
44
- biggerIsBetter,
45
- label,
46
+ // Metric props
47
+ label,
48
+ biggerIsBetter,
46
49
 
47
- // Row props
48
- changed: getMetricChanged(runs),
50
+ // Row props
51
+ changed: getMetricChanged(runs),
49
52
 
50
- // Runs
51
- runs: processedRuns,
53
+ // Runs
54
+ runs: processedRuns,
55
+ };
52
56
  };
53
- };
57
+ /* eslint-enable prettier/prettier */
@@ -7,9 +7,12 @@ import * as types from './types';
7
7
  export const compareMetrics = (
8
8
  jobs: Array<any>,
9
9
  selectMetrics: (job: any) => any,
10
- metricType: string,
11
- rowTransformers?: Array<(row: types.ReportRow) => types.ReportRow>,
12
- ) => {
10
+ metricType?: string,
11
+ rowTransformers?: Array<types.MetricReportRowTransformFn>,
12
+ ): Array<types.ReportMetricRow> => {
13
13
  const data = map(jobs, selectMetrics);
14
- return mergeMetricsByKey(data, [getAddRowMetricData(metricType), ...(rowTransformers || [])]);
14
+ return mergeMetricsByKey(data, [
15
+ getAddRowMetricData(metricType),
16
+ ...(rowTransformers || []),
17
+ ]) as Array<types.ReportMetricRow>;
15
18
  };
@@ -44,7 +44,7 @@ export const createReport = (jobs: Array<Job>): Report => {
44
44
  return Object.assign(data, { label: metric.label });
45
45
  });
46
46
 
47
- summary[sourceId] = summaryEntries;
47
+ summary[sourceId] = summaryEntries as any;
48
48
  }, {});
49
49
 
50
50
  const sectionsData = webpack.compare(jobs);
@@ -3,3 +3,4 @@ export * from './compare-metrics';
3
3
  export * from './create';
4
4
  export * from './get-metric-changed';
5
5
  export * from './merge-metrics-by-key';
6
+ export * from './types';
@@ -4,18 +4,20 @@ import * as types from './types';
4
4
 
5
5
  type RunMetrics = Record<string, types.MetricValue>;
6
6
 
7
- const mergeWithRuns = (runIndex: number, runsCount: number) =>
8
- (aggregated: any, currentRun: any): Array<types.MetricValue | null> => {
9
- // if there are no runs, just create an array and fill it with `null`
10
- const runs = aggregated || Array(runsCount).fill(null);
7
+ /* eslint-disable prettier/prettier */
8
+ const mergeWithRuns =
9
+ (runIndex: number, runsCount: number) => (aggregated: any, currentRun: any): Array<types.MetricValue | null> => {
10
+ // if there are no runs, just create an array with empty values
11
+ const runs = aggregated || Array(runsCount).fill({ value: 0 });
11
12
  runs[runIndex] = currentRun;
12
13
 
13
14
  return runs;
14
15
  };
16
+ /* eslint-enable prettier/prettier */
15
17
 
16
18
  export const mergeMetricsByKey = (
17
19
  runs: Array<RunMetrics>,
18
- rowTransformers?: Array<(row: types.ReportRow) => types.ReportRow>,
20
+ rowTransformers?: Array<types.MetricReportRowTransformFn>,
19
21
  ): Array<types.ReportRow> => {
20
22
  const runsCount = runs.length;
21
23
 
@@ -1,3 +1,5 @@
1
+ import type { MetricRunInfo, MetricRunInfoBaseline, MetricTypeConfig } from '../constants';
2
+
1
3
  export interface MetricValue {
2
4
  value: number;
3
5
  name?: string;
@@ -7,3 +9,18 @@ export interface ReportRow {
7
9
  key: string;
8
10
  runs: Array<MetricValue | null>;
9
11
  }
12
+
13
+ export type ReportMetricRun = { name?: MetricValue['name'] } & (
14
+ | MetricRunInfo
15
+ | MetricRunInfoBaseline
16
+ );
17
+
18
+ export interface ReportMetricRow {
19
+ key: string;
20
+ label: string;
21
+ biggerIsBetter: MetricTypeConfig['biggerIsBetter'];
22
+ changed: boolean;
23
+ runs: Array<ReportMetricRun | null>;
24
+ }
25
+
26
+ export type MetricReportRowTransformFn = (row: ReportRow) => ReportMetricRow;
@@ -1,5 +1,13 @@
1
- import { stringify } from 'query-string';
2
- import { JsonParam, encodeQueryParams } from 'serialize-query-params';
1
+ import {
2
+ BooleanParam,
3
+ JsonParam,
4
+ ObjectParam,
5
+ QueryParamConfig,
6
+ StringParam,
7
+ decodeQueryParams,
8
+ encodeQueryParams,
9
+ } from 'serialize-query-params';
10
+ import isEmpty from 'lodash/isEmpty';
3
11
 
4
12
  import {
5
13
  FILE_TYPES,
@@ -8,6 +16,7 @@ import {
8
16
  MODULE_SOURCE_FILE_TYPES,
9
17
  } from '../config/file-types';
10
18
  import {
19
+ ASSET_CHUNK,
11
20
  ASSET_ENTRY_TYPE,
12
21
  ASSET_FILE_TYPE,
13
22
  ASSET_FILTERS,
@@ -21,11 +30,15 @@ import {
21
30
  } from '../config/component-links';
22
31
  import I18N from '../i18n';
23
32
 
24
- export type ComponentLinkFilters = Record<string, boolean>;
33
+ export type ComponentLinkFilters = Record<string, boolean | string>;
25
34
 
26
35
  export interface ComponentLinkParams {
27
36
  search?: string;
28
- filters: ComponentLinkFilters;
37
+ entryId?: string;
38
+ metric?: string;
39
+ filters?: ComponentLinkFilters;
40
+ sortBy?: string;
41
+ direction?: string;
29
42
  }
30
43
 
31
44
  export interface ComponentLink {
@@ -159,6 +172,68 @@ export const BUNDLE_ASSETS_CHUNK_COUNT: ComponentLink = {
159
172
  },
160
173
  };
161
174
 
175
+ export const getBundleAsset = (entryId: string): ComponentLink => ({
176
+ section: SECTIONS.ASSETS,
177
+ title: I18N.COMPONENT_LINK_BUNDLE_ASSET_DETAILS,
178
+ params: {
179
+ [COMPONENT.BUNDLE_ASSETS]: {
180
+ entryId,
181
+ filters: {},
182
+ },
183
+ },
184
+ });
185
+
186
+ export const getBundleAssetsByEntryType = (
187
+ assetType: 'entry' | 'initial' | 'chunk',
188
+ ): ComponentLink => ({
189
+ section: SECTIONS.ASSETS,
190
+ title: I18N.COMPONENT_LINK_BUNDLE_ASSETS_BY_ENTRY_TYPE(assetType),
191
+ params: {
192
+ [COMPONENT.BUNDLE_ASSETS]: {
193
+ filters: {
194
+ [ASSET_FILTERS.CHANGED]: false,
195
+ ...getAssetEntryTypeFilters(false),
196
+ [`${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.ENTRY}`]: assetType === 'entry',
197
+ [`${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.INITIAL}`]: assetType === 'initial',
198
+ [`${ASSET_ENTRY_TYPE}.${ASSET_FILTERS.CHUNK}`]: assetType === 'chunk',
199
+ },
200
+ },
201
+ },
202
+ });
203
+
204
+ export const getBundleAssetsFileTypeComponentLink = (
205
+ fileType: string,
206
+ label: string = fileType,
207
+ ): ComponentLink => ({
208
+ section: SECTIONS.ASSETS,
209
+ title: I18N.COMPONENT_LINK_BUNDLE_ASSETS_BY_FILE_TYPE({ label }),
210
+ params: {
211
+ [COMPONENT.BUNDLE_ASSETS]: {
212
+ filters: {
213
+ ...getAssetFileTypeFilters(false),
214
+ [`${ASSET_FILE_TYPE}.${fileType}`]: true,
215
+ },
216
+ },
217
+ },
218
+ });
219
+
220
+ export const getBundleAssetsByChunk = (
221
+ chunkIds: Array<string>,
222
+ chunkId: string,
223
+ ): ComponentLink => ({
224
+ section: SECTIONS.ASSETS,
225
+ title: I18N.COMPONENT_LINK_BUNDLE_ASSETS_CHUNKS,
226
+ params: {
227
+ [COMPONENT.BUNDLE_ASSETS]: {
228
+ filters: {
229
+ [ASSET_FILTERS.CHUNK]: false,
230
+ ...getModuleChunkFilters(chunkIds, false),
231
+ [`${ASSET_CHUNK}.${chunkId}`]: true,
232
+ },
233
+ },
234
+ },
235
+ });
236
+
162
237
  export const BUNDLE_MODULES: ComponentLink = {
163
238
  section: SECTIONS.MODULES,
164
239
  title: I18N.COMPONENT_LINK_MODULES,
@@ -168,6 +243,7 @@ export const BUNDLE_MODULES: ComponentLink = {
168
243
  // Keep a filter to allow the merge and skip default
169
244
  [MODULE_FILTERS.CHANGED]: false,
170
245
  },
246
+ [MODULE_FILTERS.METRIC]: 'totalSize',
171
247
  },
172
248
  },
173
249
  };
@@ -181,6 +257,7 @@ export const BUNDLE_MODULES_DUPLICATE: ComponentLink = {
181
257
  [MODULE_FILTERS.CHANGED]: false,
182
258
  [MODULE_FILTERS.DUPLICATED]: true,
183
259
  },
260
+ [MODULE_FILTERS.METRIC]: 'duplicateSize',
184
261
  },
185
262
  },
186
263
  };
@@ -221,6 +298,35 @@ export const getBundleModulesByChunk = (
221
298
  },
222
299
  });
223
300
 
301
+ export const getBundleModulesByFileTpe = (
302
+ fileType: string,
303
+ fileTypeLabel?: string,
304
+ ): ComponentLink => ({
305
+ section: SECTIONS.MODULES,
306
+ title: I18N.COMPONENT_LINK_MODULES_BY_FILE_TYPE(fileTypeLabel || fileType),
307
+ params: {
308
+ [COMPONENT.BUNDLE_MODULES]: {
309
+ filters: {
310
+ ...getModuleFileTypeFilters(false),
311
+ [`${MODULE_FILE_TYPE}.${fileType}`]: true,
312
+ },
313
+ },
314
+ },
315
+ });
316
+
317
+ export const getBundleModulesBySource = (thirdParty: boolean, label = ''): ComponentLink => ({
318
+ section: SECTIONS.MODULES,
319
+ title: I18N.COMPONENT_LINK_MODULES_BY_SOURCE(label),
320
+ params: {
321
+ [COMPONENT.BUNDLE_MODULES]: {
322
+ filters: {
323
+ [`${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.THIRD_PARTY}`]: thirdParty,
324
+ [`${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.FIRST_PARTY}`]: !thirdParty,
325
+ },
326
+ },
327
+ },
328
+ });
329
+
224
330
  export const BUNDLE_PACKAGES_COUNT: ComponentLink = {
225
331
  section: SECTIONS.PACKAGES,
226
332
  title: I18N.COMPONENT_LINK_PACKAGES_COUNT,
@@ -272,22 +378,6 @@ export const BUNDLE_PACKAGES_DUPLICATE_CHANGED: ComponentLink = {
272
378
  },
273
379
  };
274
380
 
275
- export const getBundleAssetsFileTypeComponentLink = (
276
- fileType: string,
277
- label: string,
278
- ): ComponentLink => ({
279
- section: SECTIONS.ASSETS,
280
- title: I18N.COMPONENT_LINK_BUNDLE_ASSETS_BY_FILE_TYPE({ label }),
281
- params: {
282
- [COMPONENT.BUNDLE_ASSETS]: {
283
- filters: {
284
- ...getAssetFileTypeFilters(false),
285
- [`${ASSET_FILE_TYPE}.${fileType}`]: true,
286
- },
287
- },
288
- },
289
- });
290
-
291
381
  export const getBundlePackagesByNameComponentLink = (search: string): ComponentLink => ({
292
382
  section: SECTIONS.PACKAGES,
293
383
  title: I18N.COMPONENT_LINK_VIEW_PACKAGE,
@@ -301,29 +391,180 @@ export const getBundlePackagesByNameComponentLink = (search: string): ComponentL
301
391
  },
302
392
  });
303
393
 
304
- export const getComponentStateQueryString = (params = {}) => {
305
- const meta = Object.keys(params).reduce(
306
- (agg, componentName) => ({
307
- ...agg,
308
- [componentName]: JsonParam,
309
- }),
310
- {},
394
+ /**
395
+ * Custom URI ecoding/decoding that encodes the params used by ObjectParam as separators: `_`, `-`
396
+ */
397
+ const CUSTOM_ENCODING_URI_CHARS_PATTERN = /(_|-)/g;
398
+ const CUSTOM_ENCODING_URI_CODE_PATTERN = /(%(?:2D|5F))/g;
399
+
400
+ function customEncodeURIComponent(value: string): string {
401
+ return encodeURIComponent(value).replace(
402
+ CUSTOM_ENCODING_URI_CHARS_PATTERN,
403
+ (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`,
404
+ );
405
+ }
406
+
407
+ function customDecodeURIComponent(value: string): string {
408
+ const encodedURI = value.replace(CUSTOM_ENCODING_URI_CODE_PATTERN, (code) =>
409
+ String.fromCharCode(parseInt(code.replace(/^%/, ''), 16)),
311
410
  );
312
411
 
313
- return stringify(encodeQueryParams(meta, params));
412
+ return decodeURIComponent(encodedURI);
413
+ }
414
+
415
+ /**
416
+ * Custom section params encoder/decoder
417
+ *
418
+ * @NOTE To keep the length of the query string small when using a lot of filters (ex: chunks)
419
+ * we use different encoding/decoding algorithms depending on the property type:
420
+ *
421
+ * - section: JsonParam
422
+ * - search: StringParam
423
+ * - entryId: StringParam
424
+ * - metric: StringParam
425
+ * - sortBy: StringParam
426
+ * - direction: StringParam
427
+ * - filters: ObjectParam
428
+ * - value: BooleanParam
429
+ */
430
+ export const ComponentStateParam: QueryParamConfig<any, any> = {
431
+ encode: (sectionState) => {
432
+ if (!sectionState) {
433
+ return undefined;
434
+ }
435
+
436
+ const search = StringParam.encode(sectionState.search);
437
+ const entryId = StringParam.encode(sectionState.entryId);
438
+ const metric = StringParam.encode(sectionState.metric);
439
+ const sortBy = StringParam.encode(sectionState.sortBy);
440
+ const direction = StringParam.encode(sectionState.direction);
441
+
442
+ const filtersEncodedValues: Record<string, string> = {};
443
+ Object.entries(sectionState.filters || {}).forEach(([key, val]) => {
444
+ filtersEncodedValues[customEncodeURIComponent(key)] = BooleanParam.encode(!!val) as string;
445
+ });
446
+ const filters = ObjectParam.encode(filtersEncodedValues);
447
+
448
+ const data = {} as any;
449
+
450
+ if (search) {
451
+ data.search = search;
452
+ }
453
+
454
+ if (entryId) {
455
+ data.entryId = entryId;
456
+ }
457
+
458
+ if (metric) {
459
+ data.metric = metric;
460
+ }
461
+
462
+ if (sortBy) {
463
+ data.sortBy = sortBy;
464
+ }
465
+
466
+ if (direction) {
467
+ data.direction = direction;
468
+ }
469
+
470
+ if (!isEmpty(filters)) {
471
+ data.filters = filters;
472
+ }
473
+
474
+ return JsonParam.encode(data);
475
+ },
476
+ decode: (queryString) => {
477
+ const params = JsonParam.decode(queryString);
478
+
479
+ if (isEmpty(params)) {
480
+ return undefined;
481
+ }
482
+
483
+ const search = StringParam.decode(params.search);
484
+ const entryId = StringParam.decode(params.entryId);
485
+ const metric = StringParam.decode(params.metric);
486
+ const sortBy = StringParam.decode(params.sortBy);
487
+ const direction = StringParam.decode(params.direction);
488
+
489
+ let filters: Record<string, boolean> = {};
490
+
491
+ // Decode filters if typeof is string (mixed encoding)
492
+ // .1 ObjectParam
493
+ // .2 BooleanParam
494
+ if (typeof params.filters === 'string') {
495
+ const decodedFilters = ObjectParam.decode(params.filters);
496
+
497
+ Object.entries(decodedFilters || {}).forEach(([key, value]) => {
498
+ const filterDecodedValue = BooleanParam.decode(value);
499
+
500
+ if (typeof filterDecodedValue !== 'undefined' && filterDecodedValue !== null) {
501
+ filters[customDecodeURIComponent(key)] = filterDecodedValue;
502
+ }
503
+ });
504
+ } else if (typeof params.filters === 'object') {
505
+ // Use directly if already decoded (JSON encoding - obsolete)
506
+ filters = params.filters;
507
+ }
508
+
509
+ // Include only the truthy values
510
+ const result: Record<string, any> = {};
511
+
512
+ if (search) {
513
+ result.search = search;
514
+ }
515
+
516
+ if (entryId) {
517
+ result.entryId = entryId;
518
+ }
519
+
520
+ if (metric) {
521
+ result.metric = metric;
522
+ }
523
+
524
+ if (sortBy) {
525
+ result.sortBy = sortBy;
526
+ }
527
+
528
+ if (direction) {
529
+ result.direction = direction;
530
+ }
531
+
532
+ if (!isEmpty(filters)) {
533
+ result.filters = filters;
534
+ }
535
+
536
+ if (isEmpty(result)) {
537
+ return undefined;
538
+ }
539
+
540
+ return result;
541
+ },
542
+ };
543
+
544
+ export const COMPONENT_STATE_META = {
545
+ [COMPONENT.BUNDLE_ASSETS]: ComponentStateParam,
546
+ [COMPONENT.BUNDLE_MODULES]: ComponentStateParam,
547
+ [COMPONENT.BUNDLE_PACKAGES]: ComponentStateParam,
548
+ } as const;
549
+
550
+ type ComponentStateQueryStringParams = Record<string, ComponentLinkParams>;
551
+
552
+ export const getComponentStateQueryString = (params: ComponentStateQueryStringParams = {}) => {
553
+ const data = encodeQueryParams(COMPONENT_STATE_META, params);
554
+ const searchParams = new URLSearchParams(data as Record<string, string>);
555
+ return searchParams.toString();
556
+ };
557
+
558
+ export const getComponentStateData = (queryString: string): ComponentStateQueryStringParams => {
559
+ const params = new URLSearchParams(queryString);
560
+ return decodeQueryParams(COMPONENT_STATE_META, Object.fromEntries(params.entries()));
314
561
  };
315
562
 
316
563
  export const METRIC_COMPONENT_LINKS = new Map([
317
564
  ['webpack.totalSizeByTypeALL', { link: TOTALS }],
318
565
  ['webpack.totalInitialSizeJS', { link: BUNDLE_ASSETS_INITIAL_JS }],
319
566
  ['webpack.totalInitialSizeCSS', { link: BUNDLE_ASSETS_INITIAL_CSS }],
320
- [
321
- 'webpack.cacheInvalidation',
322
- {
323
- link: BUNDLE_ASSETS_CACHE_INVALIDATION,
324
- showDelta: false,
325
- },
326
- ],
567
+ ['webpack.cacheInvalidation', { link: BUNDLE_ASSETS_CACHE_INVALIDATION }],
327
568
  ['webpack.assetCount', { link: BUNDLE_ASSETS_COUNT }],
328
569
  ['webpack.chunkCount', { link: BUNDLE_ASSETS_CHUNK_COUNT }],
329
570
  ['webpack.moduleCount', { link: BUNDLE_MODULES }],