diff --git a/packages/ui/src/components/bundle-modules/bundle-modules.jsx b/packages/ui/src/components/bundle-modules/bundle-modules.jsx
index 25c028b6f8..44a7f8c8fd 100644
--- a/packages/ui/src/components/bundle-modules/bundle-modules.jsx
+++ b/packages/ui/src/components/bundle-modules/bundle-modules.jsx
@@ -10,8 +10,7 @@ import {
MODULE_CHUNK,
MODULE_FILTERS,
MODULE_FILE_TYPE,
- SECTIONS,
- COMPONENT,
+ getBundleModulesEntry,
} from '@bundle-stats/utils';
import config from '../../config.json';
@@ -31,87 +30,60 @@ import { ModuleInfo } from '../module-info';
import css from './bundle-modules.module.css';
const getFilters = ({ filters, compareMode, chunks }) => ({
- [MODULE_FILTERS.CHANGED]: {
- label: 'Changed',
- defaultValue: filters.changed,
- disabled: !compareMode,
- },
- [MODULE_FILTERS.DUPLICATED]: {
- label: 'Duplicate',
- defaultValue: filters[MODULE_FILTERS.DUPLICATED],
- },
-
- // When chunks data available, list available chunks as filters
- ...(!isEmpty(chunks) && {
- [MODULE_CHUNK]: {
- label: 'Chunk',
- ...chunks.reduce(
- (chunkFilters, { id, name }) => ({
- ...chunkFilters,
- [id]: {
- label: name,
- defaultValue: get(filters, `${MODULE_CHUNK}.${id}`, true),
- },
- }),
- {},
- ),
- },
- }),
-
- [MODULE_SOURCE_TYPE]: {
- label: 'Source',
- [MODULE_FILTERS.FIRST_PARTY]: {
- label: 'First party',
- defaultValue: get(filters, `${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.FIRST_PARTY}`, true),
- },
- [MODULE_FILTERS.THIRD_PARTY]: {
- label: 'Third party',
- defaultValue: get(filters, `${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.THIRD_PARTY}`, true),
- },
- },
+ [MODULE_FILTERS.CHANGED]: {
+ label: 'Changed',
+ defaultValue: filters.changed,
+ disabled: !compareMode,
+ },
+ [MODULE_FILTERS.DUPLICATED]: {
+ label: 'Duplicate',
+ defaultValue: filters[MODULE_FILTERS.DUPLICATED],
+ },
- // Module source types
- [MODULE_FILE_TYPE]: {
- label: 'File type',
- ...MODULE_SOURCE_FILE_TYPES.reduce(
- (agg, fileType) => ({
- ...agg,
- [fileType]: {
- label: FILE_TYPE_LABELS[fileType],
- defaultValue: get(filters, `${MODULE_FILE_TYPE}.${fileType}`, true),
+ // When chunks data available, list available chunks as filters
+ ...(!isEmpty(chunks) && {
+ [MODULE_CHUNK]: {
+ label: 'Chunk',
+ ...chunks.reduce(
+ (chunkFilters, { id, name }) => ({
+ ...chunkFilters,
+ [id]: {
+ label: name,
+ defaultValue: get(filters, `${MODULE_CHUNK}.${id}`, true),
},
}),
{},
),
},
-})
+ }),
-const RowHeader = ({ row, filters, search, customComponentLink: CustomComponentLink }) => (
-
- {row.duplicated && (
-
- )}
-
-
-);
-
-RowHeader.propTypes = {
- row: PropTypes.shape({
- label: PropTypes.string,
- duplicated: PropTypes.bool,
- }).isRequired,
- search: PropTypes.string,
- filters: PropTypes.object,
- customComponentLink: PropTypes.elementType.isRequired,
-};
+ [MODULE_SOURCE_TYPE]: {
+ label: 'Source',
+ [MODULE_FILTERS.FIRST_PARTY]: {
+ label: 'First party',
+ defaultValue: get(filters, `${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.FIRST_PARTY}`, true),
+ },
+ [MODULE_FILTERS.THIRD_PARTY]: {
+ label: 'Third party',
+ defaultValue: get(filters, `${MODULE_SOURCE_TYPE}.${MODULE_FILTERS.THIRD_PARTY}`, true),
+ },
+ },
-RowHeader.defaultProps = {
- chunks: [],
-};
+ // Module source types
+ [MODULE_FILE_TYPE]: {
+ label: 'File type',
+ ...MODULE_SOURCE_FILE_TYPES.reduce(
+ (agg, fileType) => ({
+ ...agg,
+ [fileType]: {
+ label: FILE_TYPE_LABELS[fileType],
+ defaultValue: get(filters, `${MODULE_FILE_TYPE}.${fileType}`, true),
+ },
+ }),
+ {},
+ ),
+ },
+});
export const BundleModules = ({
className,
@@ -139,45 +111,56 @@ export const BundleModules = ({
const dropdownFilters = useMemo(
() => getFilters({ filters, chunks, compareMode: jobs.length > 1 }),
- [jobs, filters, chunks]
+ [jobs, filters, chunks],
+ );
+
+ const metricsTableTitle = useMemo(
+ () => (
+
+ ),
+ [items, totalRowCount],
);
- const metricsTableTitle = useMemo(() => (
-
- ), [items, totalRowCount]);
+ const getEntryComponentLinkProps = useCallback(
+ (moduleEntryId) => getBundleModulesEntry(moduleEntryId, search, filters),
+ [filters, search],
+ );
const renderRowHeader = useCallback(
(row) => (
-
+
+ {row.duplicated && (
+
+ )}
+
+
),
- [jobs, chunks, CustomComponentLink, filters, search],
+ [jobs, chunks, CustomComponentLink, getEntryComponentLinkProps],
);
- const emptyMessage = useMemo(() => (
-
- ), [totalRowCount, resetFilters, resetAllFilters]);
+ const emptyMessage = useMemo(
+ () => (
+
+ ),
+ [totalRowCount, resetFilters, resetAllFilters],
+ );
const entryItem = useMemo(() => {
if (!entryId) {
return null;
}
- return allItems.find(({ key }) => key === entryId)
+ return allItems.find(({ key }) => key === entryId);
}, [allItems, entryId]);
return (
@@ -230,6 +213,7 @@ export const BundleModules = ({
chunkIds={chunks?.map(({ id }) => id)}
labels={jobLabels}
customComponentLink={CustomComponentLink}
+ getEntryComponentLinkProps={getEntryComponentLinkProps}
onClose={hideEntryInfo}
/>
)}
@@ -240,8 +224,10 @@ export const BundleModules = ({
BundleModules.defaultProps = {
className: '',
items: [],
+ allItems: [],
jobs: [],
totalRowCount: 0,
+ entryId: '',
hasActiveFilters: false,
customComponentLink: ComponentLink,
};
@@ -253,6 +239,9 @@ BundleModules.propTypes = {
/** Rows data */
items: PropTypes.array, // eslint-disable-line react/forbid-prop-types
+ /** All rows data */
+ allItems: PropTypes.array, // eslint-disable-line react/forbid-prop-types
+
/** Jobs data */
jobs: PropTypes.array, // eslint-disable-line react/forbid-prop-types
@@ -278,6 +267,7 @@ BundleModules.propTypes = {
filters: PropTypes.shape({
changed: PropTypes.bool,
}).isRequired,
+ entryId: PropTypes.string,
hasActiveFilters: PropTypes.bool,
diff --git a/packages/ui/src/components/module-info/module-info.module.css b/packages/ui/src/components/module-info/module-info.module.css
index e370226bfe..71a16c3396 100644
--- a/packages/ui/src/components/module-info/module-info.module.css
+++ b/packages/ui/src/components/module-info/module-info.module.css
@@ -1,3 +1,10 @@
.chunksItems {
display: inline;
}
+
+.reasons {
+ display: inline-block;
+ padding: 0;
+ margin: 0;
+ list-style-position: inside;
+}
diff --git a/packages/ui/src/components/module-info/module-info.tsx b/packages/ui/src/components/module-info/module-info.tsx
index 2f914cd313..382a0dd9bd 100644
--- a/packages/ui/src/components/module-info/module-info.tsx
+++ b/packages/ui/src/components/module-info/module-info.tsx
@@ -1,7 +1,6 @@
import React, { useMemo } from 'react';
import cx from 'classnames';
import isEmpty from 'lodash/isEmpty';
-import noop from 'lodash/noop';
import {
BUNDLE_MODULES_DUPLICATE,
FILE_TYPE_LABELS,
@@ -14,6 +13,7 @@ import {
import { Module, MetaChunk } from '@bundle-stats/utils/types/webpack';
import { Stack } from '../../layout/stack';
+import { FileName } from '../../ui/file-name';
import { Tag } from '../../ui/tag';
import { ComponentLink } from '../component-link';
import { EntryInfo, EntryInfoMetaLink } from '../entry-info';
@@ -32,6 +32,7 @@ interface ModuleInfoProps {
chunkIds?: Array;
labels: Array;
customComponentLink?: React.ElementType;
+ getEntryComponentLinkProps: (entryId: string) => Record;
onClose: () => void;
}
@@ -43,7 +44,7 @@ export const ModuleInfo = (props: ModuleInfoProps & React.ComponentProps<'div'>)
chunks = [],
chunkIds = [],
customComponentLink: CustomComponentLink = ComponentLink,
- onClick = noop,
+ getEntryComponentLinkProps,
onClose,
} = props;
@@ -110,11 +111,28 @@ export const ModuleInfo = (props: ModuleInfoProps & React.ComponentProps<'div'>)
{sourceTypeLabel}
+
+ {item?.runs?.[0].reasons && (
+
+
+ {item.runs[0].reasons.map((reason) => (
+ -
+
+
+
+
+ ))}
+
+
+ )}
);
diff --git a/packages/utils/src/i18n.js b/packages/utils/src/i18n.js
index b6aa1002eb..c75bc2f8dd 100644
--- a/packages/utils/src/i18n.js
+++ b/packages/utils/src/i18n.js
@@ -6,6 +6,7 @@ export default {
COMPONENT_LINK_BUNDLE_ASSETS_COUNT: 'View all assets',
COMPONENT_LINK_BUNDLE_ASSETS_CHUNK_COUNT: 'View all chunks',
COMPONENT_LINK_MODULES: 'View modules',
+ COMPONENT_LINK_MODULE: 'View module information',
COMPONENT_LINK_MODULES_DUPLICATE: 'View duplicate modules',
COMPONENT_LINK_MODULES_BY_FILE_TYPE: (fileType) => `View all ${fileType} modules`,
COMPONENT_LINK_MODULES_BY_SOURCE: (source) => `View all ${source} modules`,
diff --git a/packages/utils/src/utils/component-links.ts b/packages/utils/src/utils/component-links.ts
index 6a9cd4ac48..396ccad9b8 100644
--- a/packages/utils/src/utils/component-links.ts
+++ b/packages/utils/src/utils/component-links.ts
@@ -246,6 +246,22 @@ export const getBundleModulesBySearch = (search: string): ComponentLink => ({
},
});
+export const getBundleModulesEntry = (
+ entryId: string,
+ search = '',
+ filters: ComponentLinkFilters = {},
+): ComponentLink => ({
+ section: SECTIONS.MODULES,
+ title: I18N.COMPONENT_LINK_MODULE,
+ params: {
+ [COMPONENT.BUNDLE_MODULES]: {
+ search,
+ filters,
+ entryId,
+ },
+ },
+});
+
export const getBundleModulesByChunk = (
chunkIds: Array,
chunkId: string,
diff --git a/packages/utils/src/webpack/extract/modules.ts b/packages/utils/src/webpack/extract/modules.ts
index 22f71a3f4d..424f0dd297 100644
--- a/packages/utils/src/webpack/extract/modules.ts
+++ b/packages/utils/src/webpack/extract/modules.ts
@@ -84,11 +84,10 @@ export const extractModules = (webpackStats?: WebpackStatsFiltered): MetricsModu
modulesByName.forEach((moduleEntry, normalizedName) => {
const { name, size = 0, chunks } = moduleEntry;
- const normalizedName = getModuleName(name);
// skip modules that are orphane(do not belong to any chunk)
if (!chunks || chunks?.length === 0) {
- return agg;
+ return;
}
const instances = chunks.length;
@@ -98,12 +97,13 @@ export const extractModules = (webpackStats?: WebpackStatsFiltered): MetricsModu
moduleCount += instances;
totalCodeSize += instances * size;
- const reasons = moduleEntry.reasons?.map((reason) => getModuleName(reason.module));
if (duplicated) {
duplicateModulesCount += duplicateInstances;
duplicateCodeSize += duplicateInstances * size;
}
+ const reasons = moduleEntry.reasons?.map((reason) => getModuleName(reason.module));
+
modules[normalizedName] = {
name,
value: size,