@bundle-stats/ui 3.4.0-alpha.1 → 3.4.0-alpha.5

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/CHANGELOG.md CHANGED
@@ -3,35 +3,6 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- # [3.4.0-alpha.0](https://github.com/relative-ci/bundle-stats/compare/v3.2.9...v3.4.0-alpha.0) (2022-03-12)
7
-
8
-
9
- ### Bug Fixes
10
-
11
- * **ui:** Alert - allow to override props ([c9f85cd](https://github.com/relative-ci/bundle-stats/commit/c9f85cde563291da7de47f7c5ddaa7f2caa1d674))
12
- * **ui:** Summary - pass budget data to SummaryItem ([9023a19](https://github.com/relative-ci/bundle-stats/commit/9023a19d164cf8e5c6b7c23d086b689c938c67c8))
13
- * **utils:** Budgets - take biggerIsBetter in consideration ([b95b956](https://github.com/relative-ci/bundle-stats/commit/b95b95637a47cc266d4e4c254cb49f1a4f19794c))
14
-
15
-
16
- ### Features
17
-
18
- * App - add BudgetInsights ([84a6698](https://github.com/relative-ci/bundle-stats/commit/84a669806f25a1f164055ab1cbca39dc1e19987a))
19
- * **ui:** App - pass budgets info to Summary ([d665805](https://github.com/relative-ci/bundle-stats/commit/d665805298e26d9771c017c0bddacc80dcc19f87))
20
- * **ui:** BudgetInsights component ([f097f38](https://github.com/relative-ci/bundle-stats/commit/f097f388147910b6abb69fc3398d04796102aad1))
21
- * **ui:** BundleAssetsTotalsTable - add budgets ([1a45ce8](https://github.com/relative-ci/bundle-stats/commit/1a45ce8a63f411b95a2a9092a13d45925e692062))
22
- * **ui:** Icon - add check-circle icon ([8ce4c40](https://github.com/relative-ci/bundle-stats/commit/8ce4c40d92759d1a898c2311026d52d311520451))
23
- * **ui:** Icon - alert-circle icon ([c571e57](https://github.com/relative-ci/bundle-stats/commit/c571e57405b4533cf55067f7ba71b944552579a5))
24
- * **ui:** SummaryItem - budgets ([105ed0e](https://github.com/relative-ci/bundle-stats/commit/105ed0ef693c0b26c44217189d2d3cb5c4547af5))
25
-
26
-
27
- ### Performance Improvements
28
-
29
- * **ui:** BundleAssetsTotalsTable - use memo / callback ([f027cbc](https://github.com/relative-ci/bundle-stats/commit/f027cbcb59ff3890b2bf6db6b95d23c7c898bc6b))
30
-
31
-
32
-
33
-
34
-
35
6
  ## [3.2.8](https://github.com/relative-ci/bundle-stats/compare/v3.2.7...v3.2.8) (2022-03-10)
36
7
 
37
8
  **Note:** Version bump only for package @bundle-stats/ui
package/lib/app/app.js CHANGED
@@ -109,6 +109,7 @@ var Layout = function Layout(_ref) {
109
109
 
110
110
  Layout.propTypes = {
111
111
  jobs: _propTypes["default"].arrayOf(_propTypes["default"].object),
112
+ // eslint-disable-line react/forbid-prop-types
112
113
  version: _propTypes["default"].string
113
114
  };
114
115
  Layout.defaultProps = {
@@ -135,7 +136,8 @@ var JobsProvider = function JobsProvider(_ref2) {
135
136
  };
136
137
 
137
138
  JobsProvider.propTypes = {
138
- jobs: _propTypes["default"].arrayOf(_propTypes["default"].object).isRequired
139
+ jobs: _propTypes["default"].arrayOf(_propTypes["default"].object).isRequired // eslint-disable-line react/forbid-prop-types
140
+
139
141
  };
140
142
 
141
143
  var OverviewContent = function OverviewContent() {
@@ -36,7 +36,8 @@ var Header = function Header(_ref) {
36
36
  exports.Header = Header;
37
37
  Header.propTypes = {
38
38
  className: _propTypes["default"].string,
39
- jobs: _propTypes["default"].arrayOf(_propTypes["default"].object)
39
+ jobs: _propTypes["default"].arrayOf(_propTypes["default"].object) // eslint-disable-line react/forbid-prop-types
40
+
40
41
  };
41
42
  Header.defaultProps = {
42
43
  className: '',
@@ -206,7 +206,8 @@ RowHeader.propTypes = {
206
206
  isChunk: _propTypes["default"].bool,
207
207
  isInitial: _propTypes["default"].bool,
208
208
  isEntry: _propTypes["default"].bool,
209
- runs: _propTypes["default"].arrayOf(_propTypes["default"].object)
209
+ runs: _propTypes["default"].arrayOf(_propTypes["default"].object) // eslint-disable-line react/forbid-prop-types
210
+
210
211
  }).isRequired,
211
212
  chunks: _propTypes["default"].arrayOf(_propTypes["default"].shape({
212
213
  id: _propTypes["default"].string,
@@ -39,6 +39,8 @@ var _hoverCard = require("../../ui/hover-card");
39
39
 
40
40
  var _sortDropdown = require("../../ui/sort-dropdown");
41
41
 
42
+ var _tag = require("../../ui/tag");
43
+
42
44
  var _toolbar = require("../../ui/toolbar");
43
45
 
44
46
  var _metricsTable = require("../metrics-table");
@@ -95,10 +97,17 @@ var RowHeader = function RowHeader(_ref) {
95
97
  return setHoverCard(true);
96
98
  }, [showHoverCard]);
97
99
  var content = (0, _react.useMemo)(function () {
98
- return /*#__PURE__*/_react["default"].createElement(_fileName.FileName, {
100
+ return /*#__PURE__*/_react["default"].createElement("span", {
101
+ className: _bundleModulesModule["default"].name
102
+ }, row.duplicated && /*#__PURE__*/_react["default"].createElement(_tag.Tag, {
103
+ className: _bundleModulesModule["default"].nameTagDuplicated,
104
+ size: "small",
105
+ kind: _tag.Tag.KINDS.DANGER
106
+ }), /*#__PURE__*/_react["default"].createElement(_fileName.FileName, {
107
+ className: _bundleModulesModule["default"].nameText,
99
108
  name: row.label
100
- });
101
- }, [row.label]);
109
+ }));
110
+ }, [row]);
102
111
 
103
112
  if (!showHoverCard) {
104
113
  return /*#__PURE__*/_react["default"].createElement("div", {
@@ -120,7 +129,8 @@ var RowHeader = function RowHeader(_ref) {
120
129
 
121
130
  RowHeader.propTypes = {
122
131
  row: _propTypes["default"].shape({
123
- label: _propTypes["default"].string
132
+ label: _propTypes["default"].string,
133
+ duplicated: _propTypes["default"].bool
124
134
  }).isRequired,
125
135
  chunks: _propTypes["default"].arrayOf(_propTypes["default"].shape({
126
136
  id: _propTypes["default"].string,
@@ -173,6 +183,10 @@ var BundleModules = function BundleModules(_ref2) {
173
183
  label: 'Changed',
174
184
  defaultValue: filters.changed,
175
185
  disabled: jobs.length <= 1
186
+ }), _defineProperty(_objectSpread2, _utils.MODULE_FILTERS.DUPLICATED, {
187
+ label: 'Duplicate',
188
+ defaultValue: filters[_utils.MODULE_FILTERS.DUPLICATED],
189
+ disabled: jobs.length <= 1
176
190
  }), _defineProperty(_objectSpread2, _utils.MODULE_SOURCE_TYPE, (_MODULE_SOURCE_TYPE = {
177
191
  label: 'Source'
178
192
  }, _defineProperty(_MODULE_SOURCE_TYPE, _utils.MODULE_FILTERS.FIRST_PARTY, {
@@ -12,6 +12,15 @@
12
12
  margin-right: -1px; /** collapse border */
13
13
  }
14
14
 
15
- .namePopover {
16
- min-width: 280px;
15
+ .nameTagDuplicated {
16
+ margin-right: var(--space-xxxsmall);
17
+ vertical-align: baseline;
18
+ }
19
+
20
+ .nameTagDuplicated::before {
21
+ content: 'D';
22
+ }
23
+
24
+ .nameText {
25
+ vertical-align: middle;
17
26
  }
@@ -41,18 +41,17 @@ function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToAr
41
41
 
42
42
  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
43
43
 
44
- function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
45
-
46
- function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
44
+ var addRowSourceFlag = function addRowSourceFlag(row) {
45
+ var key = row.key,
46
+ runs = row.runs; // @NOTE Assign instead destructuring for perf reasons
47
+ // eslint-disable-next-line no-param-reassign
47
48
 
48
- function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
49
+ row.thirdParty = Boolean(key.match(_webpack.MODULE_PATH_PACKAGES)); // eslint-disable-next-line no-param-reassign
49
50
 
50
- var addRowSourceFlag = function addRowSourceFlag(row) {
51
- var key = row.key;
52
- var thirdParty = Boolean(key.match(_webpack.MODULE_PATH_PACKAGES));
53
- return _objectSpread(_objectSpread({}, row), {}, {
54
- thirdParty: thirdParty
55
- });
51
+ row.duplicated = Boolean(runs.find(function (run) {
52
+ return (run === null || run === void 0 ? void 0 : run.duplicated) === true;
53
+ }));
54
+ return row;
56
55
  };
57
56
 
58
57
  exports.addRowSourceFlag = addRowSourceFlag;
@@ -82,6 +81,11 @@ var getRowFilter = function getRowFilter(filters) {
82
81
  // Skip not changed rows
83
82
  if (filters[_utils.MODULE_FILTERS.CHANGED] && !row.changed) {
84
83
  return false;
84
+ } // Skip not duplicated rows
85
+
86
+
87
+ if (filters[_utils.MODULE_FILTERS.DUPLICATED] && !row.duplicated) {
88
+ return false;
85
89
  } // Skip not matching source type
86
90
 
87
91
 
@@ -72,12 +72,12 @@ var BundleModules = function BundleModules(props) {
72
72
 
73
73
  var _useMemo = (0, _react.useMemo)(function () {
74
74
  return {
75
- defaultFilters: _objectSpread(_objectSpread(_objectSpread({
75
+ defaultFilters: _objectSpread(_objectSpread(_objectSpread(_objectSpread({
76
76
  changed: (jobs === null || jobs === void 0 ? void 0 : jobs.length) > 1
77
- }, (0, _utils.getModuleSourceTypeFilters)(true)), (0, _utils.getModuleChunkFilters)(chunkIds, true)), (0, _utils.getModuleFileTypeFilters)(true)),
78
- allEntriesFilters: _objectSpread(_objectSpread(_objectSpread({
77
+ }, (0, _utils.getModuleSourceTypeFilters)(true)), (0, _utils.getModuleChunkFilters)(chunkIds, true)), (0, _utils.getModuleFileTypeFilters)(true)), {}, _defineProperty({}, _utils.MODULE_FILTERS.DUPLICATED, false)),
78
+ allEntriesFilters: _objectSpread(_objectSpread(_objectSpread(_objectSpread({
79
79
  changed: false
80
- }, (0, _utils.getModuleSourceTypeFilters)(true)), (0, _utils.getModuleChunkFilters)(chunkIds, true)), (0, _utils.getModuleFileTypeFilters)(true))
80
+ }, (0, _utils.getModuleSourceTypeFilters)(true)), (0, _utils.getModuleChunkFilters)(chunkIds, true)), (0, _utils.getModuleFileTypeFilters)(true)), {}, _defineProperty({}, _utils.MODULE_FILTERS.DUPLICATED, false))
81
81
  };
82
82
  }, [jobs]),
83
83
  defaultFilters = _useMemo.defaultFilters,
package/lib/constants.js CHANGED
@@ -56,7 +56,7 @@ var METRICS_WEBPACK_GENERAL = ['totalSizeByTypeALL', 'totalInitialSizeJS', 'tota
56
56
  exports.METRICS_WEBPACK_GENERAL = METRICS_WEBPACK_GENERAL;
57
57
  var METRICS_WEBPACK_ASSETS = ['assetCount', 'chunkCount'];
58
58
  exports.METRICS_WEBPACK_ASSETS = METRICS_WEBPACK_ASSETS;
59
- var METRICS_WEBPACK_MODULES = ['moduleCount'];
59
+ var METRICS_WEBPACK_MODULES = ['moduleCount', 'duplicateModulesCount', 'duplicateCode'];
60
60
  exports.METRICS_WEBPACK_MODULES = METRICS_WEBPACK_MODULES;
61
61
  var METRICS_WEBPACK_PACKAGES = ['packageCount', 'duplicatePackagesCount'];
62
62
  exports.METRICS_WEBPACK_PACKAGES = METRICS_WEBPACK_PACKAGES;
@@ -54,6 +54,7 @@ const Layout = _ref => {
54
54
 
55
55
  Layout.propTypes = {
56
56
  jobs: PropTypes.arrayOf(PropTypes.object),
57
+ // eslint-disable-line react/forbid-prop-types
57
58
  version: PropTypes.string
58
59
  };
59
60
  Layout.defaultProps = {
@@ -78,7 +79,8 @@ const JobsProvider = _ref2 => {
78
79
  };
79
80
 
80
81
  JobsProvider.propTypes = {
81
- jobs: PropTypes.arrayOf(PropTypes.object).isRequired
82
+ jobs: PropTypes.arrayOf(PropTypes.object).isRequired // eslint-disable-line react/forbid-prop-types
83
+
82
84
  };
83
85
 
84
86
  const OverviewContent = () => {
@@ -21,7 +21,8 @@ export const Header = _ref => {
21
21
  };
22
22
  Header.propTypes = {
23
23
  className: PropTypes.string,
24
- jobs: PropTypes.arrayOf(PropTypes.object)
24
+ jobs: PropTypes.arrayOf(PropTypes.object) // eslint-disable-line react/forbid-prop-types
25
+
25
26
  };
26
27
  Header.defaultProps = {
27
28
  className: '',
@@ -145,7 +145,8 @@ RowHeader.propTypes = {
145
145
  isChunk: PropTypes.bool,
146
146
  isInitial: PropTypes.bool,
147
147
  isEntry: PropTypes.bool,
148
- runs: PropTypes.arrayOf(PropTypes.object)
148
+ runs: PropTypes.arrayOf(PropTypes.object) // eslint-disable-line react/forbid-prop-types
149
+
149
150
  }).isRequired,
150
151
  chunks: PropTypes.arrayOf(PropTypes.shape({
151
152
  id: PropTypes.string,
@@ -17,6 +17,7 @@ import { FileName } from '../../ui/file-name';
17
17
  import { Filters } from '../../ui/filters';
18
18
  import { HoverCard } from '../../ui/hover-card';
19
19
  import { SortDropdown } from '../../ui/sort-dropdown';
20
+ import { Tag } from '../../ui/tag';
20
21
  import { Toolbar } from '../../ui/toolbar';
21
22
  import { MetricsTable } from '../metrics-table';
22
23
  import { MetricsTableSearch } from '../metrics-table-search';
@@ -37,9 +38,16 @@ const RowHeader = _ref => {
37
38
 
38
39
  const [showHoverCard, setHoverCard] = useState(false);
39
40
  const handleOnMouseEnter = useCallback(() => setHoverCard(true), [showHoverCard]);
40
- const content = useMemo(() => /*#__PURE__*/React.createElement(FileName, {
41
+ const content = useMemo(() => /*#__PURE__*/React.createElement("span", {
42
+ className: css.name
43
+ }, row.duplicated && /*#__PURE__*/React.createElement(Tag, {
44
+ className: css.nameTagDuplicated,
45
+ size: "small",
46
+ kind: Tag.KINDS.DANGER
47
+ }), /*#__PURE__*/React.createElement(FileName, {
48
+ className: css.nameText,
41
49
  name: row.label
42
- }), [row.label]);
50
+ })), [row]);
43
51
 
44
52
  if (!showHoverCard) {
45
53
  return /*#__PURE__*/React.createElement("div", {
@@ -61,7 +69,8 @@ const RowHeader = _ref => {
61
69
 
62
70
  RowHeader.propTypes = {
63
71
  row: PropTypes.shape({
64
- label: PropTypes.string
72
+ label: PropTypes.string,
73
+ duplicated: PropTypes.bool
65
74
  }).isRequired,
66
75
  chunks: PropTypes.arrayOf(PropTypes.shape({
67
76
  id: PropTypes.string,
@@ -111,6 +120,11 @@ export const BundleModules = _ref2 => {
111
120
  defaultValue: filters.changed,
112
121
  disabled: jobs.length <= 1
113
122
  },
123
+ [MODULE_FILTERS.DUPLICATED]: {
124
+ label: 'Duplicate',
125
+ defaultValue: filters[MODULE_FILTERS.DUPLICATED],
126
+ disabled: jobs.length <= 1
127
+ },
114
128
  [MODULE_SOURCE_TYPE]: {
115
129
  label: 'Source',
116
130
  [MODULE_FILTERS.FIRST_PARTY]: {
@@ -12,6 +12,15 @@
12
12
  margin-right: -1px; /** collapse border */
13
13
  }
14
14
 
15
- .namePopover {
16
- min-width: 280px;
15
+ .nameTagDuplicated {
16
+ margin-right: var(--space-xxxsmall);
17
+ vertical-align: baseline;
18
+ }
19
+
20
+ .nameTagDuplicated::before {
21
+ content: 'D';
22
+ }
23
+
24
+ .nameText {
25
+ vertical-align: middle;
17
26
  }
@@ -7,12 +7,15 @@ import { MODULE_CHUNK, MODULE_FILTERS, MODULE_FILE_TYPE, MODULE_SOURCE_TYPE, get
7
7
  import { SORT_BY_NAME, SORT_BY_SIZE, SORT_BY_DELTA } from './bundle-modules.constants';
8
8
  export const addRowSourceFlag = row => {
9
9
  const {
10
- key
11
- } = row;
12
- const thirdParty = Boolean(key.match(MODULE_PATH_PACKAGES));
13
- return { ...row,
14
- thirdParty
15
- };
10
+ key,
11
+ runs
12
+ } = row; // @NOTE Assign instead destructuring for perf reasons
13
+ // eslint-disable-next-line no-param-reassign
14
+
15
+ row.thirdParty = Boolean(key.match(MODULE_PATH_PACKAGES)); // eslint-disable-next-line no-param-reassign
16
+
17
+ row.duplicated = Boolean(runs.find(run => (run == null ? void 0 : run.duplicated) === true));
18
+ return row;
16
19
  };
17
20
  export const getCustomSort = sortBy => item => {
18
21
  if (sortBy === SORT_BY_NAME) {
@@ -33,6 +36,11 @@ export const getRowFilter = filters => row => {
33
36
  // Skip not changed rows
34
37
  if (filters[MODULE_FILTERS.CHANGED] && !row.changed) {
35
38
  return false;
39
+ } // Skip not duplicated rows
40
+
41
+
42
+ if (filters[MODULE_FILTERS.DUPLICATED] && !row.duplicated) {
43
+ return false;
36
44
  } // Skip not matching source type
37
45
 
38
46
 
@@ -5,7 +5,7 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
5
5
 
6
6
  import React, { useMemo } from 'react';
7
7
  import PropTypes from 'prop-types';
8
- import { getModuleChunkFilters, getModuleSourceTypeFilters, getModuleFileTypeFilters } from '@bundle-stats/utils';
8
+ import { MODULE_FILTERS, getModuleChunkFilters, getModuleSourceTypeFilters, getModuleFileTypeFilters } from '@bundle-stats/utils';
9
9
  import * as webpack from '@bundle-stats/utils/lib-esm/webpack';
10
10
  import { useRowsFilter } from '../../hooks/rows-filter';
11
11
  import { useRowsSort } from '../../hooks/rows-sort';
@@ -45,13 +45,15 @@ export const BundleModules = props => {
45
45
  changed: (jobs == null ? void 0 : jobs.length) > 1,
46
46
  ...getModuleSourceTypeFilters(true),
47
47
  ...getModuleChunkFilters(chunkIds, true),
48
- ...getModuleFileTypeFilters(true)
48
+ ...getModuleFileTypeFilters(true),
49
+ [MODULE_FILTERS.DUPLICATED]: false
49
50
  },
50
51
  allEntriesFilters: {
51
52
  changed: false,
52
53
  ...getModuleSourceTypeFilters(true),
53
54
  ...getModuleChunkFilters(chunkIds, true),
54
- ...getModuleFileTypeFilters(true)
55
+ ...getModuleFileTypeFilters(true),
56
+ [MODULE_FILTERS.DUPLICATED]: false
55
57
  }
56
58
  }), [jobs]);
57
59
  const searchParams = useSearchParams({
@@ -27,5 +27,5 @@ export const ASSETS_SIZES_FILE_TYPE_MAP = FILE_TYPES.reduce((agg, fileType) => (
27
27
  }), {});
28
28
  export const METRICS_WEBPACK_GENERAL = ['totalSizeByTypeALL', 'totalInitialSizeJS', 'totalInitialSizeCSS', 'cacheInvalidation'];
29
29
  export const METRICS_WEBPACK_ASSETS = ['assetCount', 'chunkCount'];
30
- export const METRICS_WEBPACK_MODULES = ['moduleCount'];
30
+ export const METRICS_WEBPACK_MODULES = ['moduleCount', 'duplicateModulesCount', 'duplicateCode'];
31
31
  export const METRICS_WEBPACK_PACKAGES = ['packageCount', 'duplicatePackagesCount'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bundle-stats/ui",
3
- "version": "3.4.0-alpha.1",
3
+ "version": "3.4.0-alpha.5",
4
4
  "description": "UI components for BundleStats projects.",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib-esm/index.js",
@@ -37,7 +37,7 @@
37
37
  "homepage": "https://github.com/relative-ci/bundle-stats/blob/master/packages/ui#readme",
38
38
  "devDependencies": {
39
39
  "@babel/cli": "7.17.6",
40
- "@babel/core": "7.17.5",
40
+ "@babel/core": "7.17.7",
41
41
  "@babel/plugin-proposal-class-properties": "7.16.7",
42
42
  "@babel/preset-env": "7.16.11",
43
43
  "@babel/preset-react": "7.16.7",
@@ -54,7 +54,7 @@
54
54
  "babel-jest": "27.5.1",
55
55
  "babel-plugin-lodash": "3.3.4",
56
56
  "babel-plugin-require-context-hook": "1.0.0",
57
- "chromatic": "6.5.1",
57
+ "chromatic": "6.5.3",
58
58
  "classnames": "2.3.1",
59
59
  "colormap": "2.3.2",
60
60
  "core-js": "3.20.2",
@@ -67,7 +67,7 @@
67
67
  "jest": "27.5.1",
68
68
  "jest-date-mock": "1.0.8",
69
69
  "lodash": "4.17.21",
70
- "postcss": "8.4.7",
70
+ "postcss": "8.4.12",
71
71
  "prop-types": "15.8.1",
72
72
  "react": "16.14.0",
73
73
  "react-dom": "16.14.0",
@@ -76,21 +76,21 @@
76
76
  "rimraf": "3.0.2"
77
77
  },
78
78
  "peerDependencies": {
79
- "classnames": "2.2.6",
80
- "core-js": "3.20.2",
81
- "lodash": "4.17.21",
82
- "prop-types": "15.7.2",
83
- "react": "16.14.0",
84
- "react-dom": "16.14.0",
85
- "react-router-dom": "5.3.0"
79
+ "classnames": "^2.0.0",
80
+ "core-js": "^3.0.0",
81
+ "lodash": "^4.0.0",
82
+ "prop-types": "^15.0.0",
83
+ "react": "^16.0.0",
84
+ "react-dom": "^16.0.0",
85
+ "react-router-dom": "^5.0.0"
86
86
  },
87
87
  "dependencies": {
88
- "@bundle-stats/utils": "^3.4.0-alpha.1",
88
+ "@bundle-stats/utils": "^3.4.0-alpha.5",
89
89
  "ariakit": "2.0.0-alpha.19",
90
90
  "date-fns": "2.28.0",
91
91
  "modern-css-reset": "1.4.0",
92
92
  "query-string": "7.1.1",
93
93
  "use-query-params": "1.2.3"
94
94
  },
95
- "gitHead": "a7ba8010b047028c4e56e407719616fde7d8ea88"
95
+ "gitHead": "8cf4f784d15aed35e09a911a2fc9646a3fb9b06c"
96
96
  }
package/src/app/app.jsx CHANGED
@@ -48,7 +48,7 @@ const Layout = ({ jobs, version, ...props }) => (
48
48
  );
49
49
 
50
50
  Layout.propTypes = {
51
- jobs: PropTypes.arrayOf(PropTypes.object),
51
+ jobs: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types
52
52
  version: PropTypes.string,
53
53
  };
54
54
 
@@ -64,7 +64,7 @@ const JobsProvider = ({ jobs, ...restProps }) => {
64
64
  };
65
65
 
66
66
  JobsProvider.propTypes = {
67
- jobs: PropTypes.arrayOf(PropTypes.object).isRequired,
67
+ jobs: PropTypes.arrayOf(PropTypes.object).isRequired, // eslint-disable-line react/forbid-prop-types
68
68
  };
69
69
 
70
70
  const OverviewContent = () => {
@@ -16,7 +16,7 @@ export const Header = ({ className, jobs }) => (
16
16
 
17
17
  Header.propTypes = {
18
18
  className: PropTypes.string,
19
- jobs: PropTypes.arrayOf(PropTypes.object),
19
+ jobs: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types
20
20
  };
21
21
 
22
22
  Header.defaultProps = {
@@ -146,7 +146,7 @@ RowHeader.propTypes = {
146
146
  isChunk: PropTypes.bool,
147
147
  isInitial: PropTypes.bool,
148
148
  isEntry: PropTypes.bool,
149
- runs: PropTypes.arrayOf(PropTypes.object),
149
+ runs: PropTypes.arrayOf(PropTypes.object), // eslint-disable-line react/forbid-prop-types
150
150
  }).isRequired,
151
151
  chunks: PropTypes.arrayOf(
152
152
  PropTypes.shape({
@@ -20,6 +20,7 @@ import { FileName } from '../../ui/file-name';
20
20
  import { Filters } from '../../ui/filters';
21
21
  import { HoverCard } from '../../ui/hover-card';
22
22
  import { SortDropdown } from '../../ui/sort-dropdown';
23
+ import { Tag } from '../../ui/tag';
23
24
  import { Toolbar } from '../../ui/toolbar';
24
25
  import { MetricsTable } from '../metrics-table';
25
26
  import { MetricsTableSearch } from '../metrics-table-search';
@@ -33,7 +34,17 @@ const RowHeader = ({ row, chunks, labels, CustomComponentLink }) => {
33
34
 
34
35
  const [showHoverCard, setHoverCard] = useState(false);
35
36
  const handleOnMouseEnter = useCallback(() => setHoverCard(true), [showHoverCard]);
36
- const content = useMemo(() => <FileName name={row.label} />, [row.label]);
37
+ const content = useMemo(
38
+ () => (
39
+ <span className={css.name}>
40
+ {row.duplicated && (
41
+ <Tag className={css.nameTagDuplicated} size="small" kind={Tag.KINDS.DANGER} />
42
+ )}
43
+ <FileName className={css.nameText} name={row.label} />
44
+ </span>
45
+ ),
46
+ [row],
47
+ );
37
48
 
38
49
  if (!showHoverCard) {
39
50
  return <div onMouseEnter={handleOnMouseEnter}>{content}</div>;
@@ -56,6 +67,7 @@ const RowHeader = ({ row, chunks, labels, CustomComponentLink }) => {
56
67
  RowHeader.propTypes = {
57
68
  row: PropTypes.shape({
58
69
  label: PropTypes.string,
70
+ duplicated: PropTypes.bool,
59
71
  }).isRequired,
60
72
  chunks: PropTypes.arrayOf(
61
73
  PropTypes.shape({
@@ -118,6 +130,11 @@ export const BundleModules = ({
118
130
  defaultValue: filters.changed,
119
131
  disabled: jobs.length <= 1,
120
132
  },
133
+ [MODULE_FILTERS.DUPLICATED]: {
134
+ label: 'Duplicate',
135
+ defaultValue: filters[MODULE_FILTERS.DUPLICATED],
136
+ disabled: jobs.length <= 1,
137
+ },
121
138
  [MODULE_SOURCE_TYPE]: {
122
139
  label: 'Source',
123
140
  [MODULE_FILTERS.FIRST_PARTY]: {
@@ -12,6 +12,15 @@
12
12
  margin-right: -1px; /** collapse border */
13
13
  }
14
14
 
15
- .namePopover {
16
- min-width: 280px;
15
+ .nameTagDuplicated {
16
+ margin-right: var(--space-xxxsmall);
17
+ vertical-align: baseline;
18
+ }
19
+
20
+ .nameTagDuplicated::before {
21
+ content: 'D';
22
+ }
23
+
24
+ .nameText {
25
+ vertical-align: middle;
17
26
  }
@@ -12,9 +12,15 @@ import {
12
12
  import { SORT_BY_NAME, SORT_BY_SIZE, SORT_BY_DELTA } from './bundle-modules.constants';
13
13
 
14
14
  export const addRowSourceFlag = (row) => {
15
- const { key } = row;
16
- const thirdParty = Boolean(key.match(MODULE_PATH_PACKAGES));
17
- return { ...row, thirdParty };
15
+ const { key, runs } = row;
16
+
17
+ // @NOTE Assign instead destructuring for perf reasons
18
+ // eslint-disable-next-line no-param-reassign
19
+ row.thirdParty = Boolean(key.match(MODULE_PATH_PACKAGES));
20
+ // eslint-disable-next-line no-param-reassign
21
+ row.duplicated = Boolean(runs.find((run) => run?.duplicated === true));
22
+
23
+ return row;
18
24
  };
19
25
 
20
26
  export const getCustomSort = (sortBy) => (item) => {
@@ -39,10 +45,16 @@ export const getRowFilter = (filters) => (row) => {
39
45
  return false;
40
46
  }
41
47
 
48
+ // Skip not duplicated rows
49
+ if (filters[MODULE_FILTERS.DUPLICATED] && !row.duplicated) {
50
+ return false;
51
+ }
52
+
42
53
  // Skip not matching source type
43
54
  if (
44
55
  !(
45
- (filters[`${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.FIRST_PARTY}`] && row.thirdParty === false) ||
56
+ (filters[`${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.FIRST_PARTY}`] &&
57
+ row.thirdParty === false) ||
46
58
  (filters[`${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.THIRD_PARTY}`] && row.thirdParty === true)
47
59
  )
48
60
  ) {
@@ -2,6 +2,7 @@ import React, { useMemo } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { uniqBy, map } from 'lodash';
4
4
  import {
5
+ MODULE_FILTERS,
5
6
  getModuleChunkFilters,
6
7
  getModuleSourceTypeFilters,
7
8
  getModuleFileTypeFilters,
@@ -33,12 +34,14 @@ export const BundleModules = (props) => {
33
34
  ...getModuleSourceTypeFilters(true),
34
35
  ...getModuleChunkFilters(chunkIds, true),
35
36
  ...getModuleFileTypeFilters(true),
37
+ [MODULE_FILTERS.DUPLICATED]: false,
36
38
  },
37
39
  allEntriesFilters: {
38
40
  changed: false,
39
41
  ...getModuleSourceTypeFilters(true),
40
42
  ...getModuleChunkFilters(chunkIds, true),
41
43
  ...getModuleFileTypeFilters(true),
44
+ [MODULE_FILTERS.DUPLICATED]: false,
42
45
  },
43
46
  }),
44
47
  [jobs],
package/src/constants.js CHANGED
@@ -53,5 +53,5 @@ export const METRICS_WEBPACK_GENERAL = [
53
53
  'cacheInvalidation',
54
54
  ];
55
55
  export const METRICS_WEBPACK_ASSETS = ['assetCount', 'chunkCount'];
56
- export const METRICS_WEBPACK_MODULES = ['moduleCount'];
56
+ export const METRICS_WEBPACK_MODULES = ['moduleCount', 'duplicateModulesCount', 'duplicateCode'];
57
57
  export const METRICS_WEBPACK_PACKAGES = ['packageCount', 'duplicatePackagesCount'];