diff --git a/packages/ui/src/components/asset-info/asset-info.tsx b/packages/ui/src/components/asset-info/asset-info.tsx index 5954f4120d..59c13d551f 100644 --- a/packages/ui/src/components/asset-info/asset-info.tsx +++ b/packages/ui/src/components/asset-info/asset-info.tsx @@ -126,14 +126,17 @@ export const AssetInfo = (props: AssetInfoProps & React.ComponentProps<'div'>) = return ( {item.fileType && ( - + { const { row, filters, search, moduleMetric, customComponentLink: CustomComponentLink } = props; + const moduleRow = row as ReportMetricModuleRow; + return ( - {row.duplicated && ( + {moduleRow.duplicated && ( )} - + ); }; @@ -162,8 +167,8 @@ interface BundleModulesProps extends React.ComponentProps<'div'> { hideEntryInfo: () => void; showEntryInfo: (entryId: string) => void; - moduleMetric: ModuleMetric; - setModuleMetric: (newValue: ModuleMetric) => void; + moduleMetric: ModuleSizeMetric; + setModuleMetric: (newValue: ModuleSizeMetric) => void; customComponentLink: React.ElementType; } @@ -215,7 +220,7 @@ export const BundleModules = (props: BundleModulesProps) => { ); const renderRowHeader = useCallback( - (row: ReportMetricModuleRow) => ( + (row: ReportMetricRow) => ( { @@ -347,6 +358,7 @@ export const BundleModules = (props: BundleModulesProps) => { chunks={chunks} chunkIds={chunks?.map(({ id }) => id)} labels={jobLabels} + metricLabel={ModuleSizeMetrics[moduleMetric].label} customComponentLink={CustomComponentLink} onClose={hideEntryInfo} /> diff --git a/packages/ui/src/components/bundle-modules/bundle-modules.types.d.ts b/packages/ui/src/components/bundle-modules/bundle-modules.types.d.ts deleted file mode 100644 index f3684ce29e..0000000000 --- a/packages/ui/src/components/bundle-modules/bundle-modules.types.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Module } from '@bundle-stats/utils/lib-esm/webpack/types'; -import type { ReportMetricRun } from '@bundle-stats/utils/types/report/types'; - -export type ReportMetricModuleRow = { - thirdParty: boolean; - duplicated: boolean; - fileType: string; -} & ReportMetricRun & - Module; diff --git a/packages/ui/src/components/bundle-modules/bundle-modules.utils.ts b/packages/ui/src/components/bundle-modules/bundle-modules.utils.ts index 221648c135..dff9821937 100644 --- a/packages/ui/src/components/bundle-modules/bundle-modules.utils.ts +++ b/packages/ui/src/components/bundle-modules/bundle-modules.utils.ts @@ -3,10 +3,13 @@ import get from 'lodash/get'; import isEmpty from 'lodash/isEmpty'; import union from 'lodash/union'; import orderBy from 'lodash/orderBy'; +import type { + WebpackChunk, + MetricRunInfo, + ReportMetricRow, + ReportMetricRun, +} from '@bundle-stats/utils'; import { - type WebpackChunk, - type MetricRunInfo, - type ReportMetricRow, FILE_TYPE_LABELS, MODULE_CHUNK, MODULE_FILTERS, @@ -18,26 +21,46 @@ import { getModuleSourceTypeFilters, getModuleFileTypeFilters, } from '@bundle-stats/utils'; -// @ts-ignore -import { MODULE_PATH_PACKAGES, type Module } from '@bundle-stats/utils/lib-esm/webpack'; -import type { ReportMetricRun } from '@bundle-stats/utils/types/report/types'; +import type { Module } from '@bundle-stats/utils/types/webpack'; +import { MODULE_PATH_PACKAGES } from '@bundle-stats/utils/lib-esm/webpack'; -import type { FilterFieldsData, FilterGroupFieldData } from '../../types'; -import type { ReportMetricModuleRow } from './bundle-modules.types'; +import type { FilterFieldsData, FilterGroupFieldData, ReportMetricModuleRow } from '../../types'; import * as I18N from './bundle-modules.i18n'; -export const addRowFlags = (row: Module & ReportMetricRow): ReportMetricModuleRow => { +export const addRowFlags = (row: ReportMetricRow): ReportMetricModuleRow => { const { key, runs } = row; // @NOTE Assign instead destructuring for perf reasons + const moduleRow = row as any as ReportMetricModuleRow; + + // eslint-disable-next-line no-param-reassign + moduleRow.thirdParty = Boolean(key.match(MODULE_PATH_PACKAGES)); + // Mark metric row as duplicated when at least one run is duplicated // eslint-disable-next-line no-param-reassign - row.thirdParty = Boolean(key.match(MODULE_PATH_PACKAGES)); + moduleRow.duplicated = Boolean( + runs.find((run) => (run as Module & ReportMetricRun)?.duplicated === true), + ); // eslint-disable-next-line no-param-reassign - row.duplicated = Boolean(runs.find((run: Module & ReportMetricRun) => run?.duplicated === true)); + moduleRow.fileType = getModuleSourceFileType(row.key); + // eslint-disable-next-line no-param-reassign - row.fileType = getModuleSourceFileType(row.key); + moduleRow.runs = row.runs.map((run) => { + if (!run) { + return run; + } + + const moduleRun = run as Module & ReportMetricRun; + const chunkCount = moduleRun.chunkIds?.length || 0; + + return { + ...moduleRun, + size: moduleRun.value, + sizeDuplicate: chunkCount > 1 ? (chunkCount - 1) * moduleRun.value : 0, + sizeTotal: chunkCount * moduleRun.value, + }; + }); - return row; + return moduleRow; }; export const getCustomSort = (item: ReportMetricRow) => [!item.changed, item.key]; @@ -88,7 +111,7 @@ export const generateGetRowFilter = // Filter if any of the chunkIds are checked if (hasChunkFilters) { const rowRunsChunkIds = - row?.runs?.map((run: Module & MetricRunInfo) => run?.chunkIds || []) || []; + row?.runs?.map((run) => (run as Module & MetricRunInfo)?.chunkIds || []) || []; const rowChunkIds = union(...rowRunsChunkIds); const matchedChunkIds = intersection(rowChunkIds, checkedChunkIds); return matchedChunkIds.length > 0; diff --git a/packages/ui/src/components/bundle-modules/index.tsx b/packages/ui/src/components/bundle-modules/index.tsx index fca5035f37..9ea97dfcd2 100644 --- a/packages/ui/src/components/bundle-modules/index.tsx +++ b/packages/ui/src/components/bundle-modules/index.tsx @@ -3,7 +3,8 @@ import type { Job } from '@bundle-stats/utils'; // @ts-ignore import * as webpack from '@bundle-stats/utils/lib-esm/webpack'; -import type { SortAction } from '../../types'; +import { ModuleSizeMetric } from '../../constants'; +import type { ReportMetricModuleRow, SortAction } from '../../types'; import { getJobsChunksData } from '../../utils/jobs'; import { useRowsFilter } from '../../hooks/rows-filter'; import { useRowsSort } from '../../hooks/rows-sort'; @@ -16,27 +17,27 @@ import { generateFilters, getCustomSort, } from './bundle-modules.utils'; -import { ModuleMetric } from './bundle-modules.constants'; -import * as types from './bundle-modules.types'; interface UseMetricParams { metric?: string; - setState: ({ metric }: { metric: ModuleMetric }) => void; + setState: ({ metric }: { metric: ModuleSizeMetric }) => void; } -function useModuleMetric(params: UseMetricParams): [ModuleMetric, (value: ModuleMetric) => void] { +function useModuleMetric( + params: UseMetricParams, +): [ModuleSizeMetric, (value: ModuleSizeMetric) => void] { const { metric, setState } = params; - const moduleMetric: ModuleMetric = useMemo(() => { - if (Object.values(ModuleMetric).includes(metric as ModuleMetric)) { - return metric as ModuleMetric; + const moduleMetric: ModuleSizeMetric = useMemo(() => { + if (Object.values(ModuleSizeMetric).includes(metric as ModuleSizeMetric)) { + return metric as ModuleSizeMetric; } - return ModuleMetric.TOTAL_SIZE; + return ModuleSizeMetric.TOTAL_SIZE; }, [metric]); const setModuleMetric = useCallback( - (newModuleMetric: ModuleMetric) => { + (newModuleMetric: ModuleSizeMetric) => { if (newModuleMetric !== metric) { setState({ metric: newModuleMetric }); } @@ -96,11 +97,11 @@ export const BundleModules = (props: BundleModulesProps) => { }); const { rows, totalRowCount } = useMemo(() => { - let result: Array = []; + let result: Array = []; - if (moduleMetric === ModuleMetric.SIZE) { + if (moduleMetric === ModuleSizeMetric.SIZE) { result = webpack.compareBySection.modules(jobs, [addRowFlags]); - } else if (moduleMetric === ModuleMetric.DUPLICATE_SIZE) { + } else if (moduleMetric === ModuleSizeMetric.DUPLICATE_SIZE) { result = webpack.compareModuleDuplicateSize(jobs, [addRowFlags]); } else { result = webpack.compareModuleTotalSize(jobs, [addRowFlags]); @@ -117,7 +118,7 @@ export const BundleModules = (props: BundleModulesProps) => { searchPattern: searchParams.searchPattern, filters: searchParams.filters, getRowFilter: generateGetRowFilter({ chunkIds }), - }); + }) as Array; const sortParams = useRowsSort({ rows: filteredRows, diff --git a/packages/ui/src/components/entry-info/entry-info.module.css b/packages/ui/src/components/entry-info/entry-info.module.css index d773c0aaa1..f4f5b4baf7 100644 --- a/packages/ui/src/components/entry-info/entry-info.module.css +++ b/packages/ui/src/components/entry-info/entry-info.module.css @@ -71,40 +71,66 @@ .runs .runsColSize { text-align: right; + white-space: nowrap; } .size { font-family: var(--font-family-fixed); } +/** + * Meta element + */ +.meta {} + .metaLabel { - padding: 2px 0; - display: inline-block; margin-right: var(--space-xxxsmall); - min-width: 6em; + min-width: 8em; + flex: 0 1 6em; color: var(--color-text-light); } +.metaLabelTooltip { + margin-left: var(--space-xxxsmall); + color: var(--color-text-ultra-light); +} + +.metaContent { + padding: 1px 0; + flex: 0 1 auto; +} + .metaLink { - padding: 2px var(--space-xxxsmall); + padding: 1px var(--space-xxxsmall); + display: inline-block; + line-height: var(--line-height-heading); border-radius: var(--radius-small); background: var(--color-highlight); transition: var(--transition-out); - overflow-wrap: break-word; text-decoration: none; text-transform: none; font-weight: normal; + max-width: 16em; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } -.metaLink + .metaLink { - margin-left: var(--space-xxsmall); +.metaLink { + margin-right: var(--space-xxxsmall); } .metaLink:hover, .metaLink:active, .metaLink:focus { - transition: var(--transition-in); background: var(--color-outline); color: var(--color-primary-dark); } + +@media (min-width: 640px) { + .meta { + display: flex; + align-items: flex-start; + } +} diff --git a/packages/ui/src/components/entry-info/entry-info.tsx b/packages/ui/src/components/entry-info/entry-info.tsx index 791fa2e052..2ff00c7b7e 100644 --- a/packages/ui/src/components/entry-info/entry-info.tsx +++ b/packages/ui/src/components/entry-info/entry-info.tsx @@ -1,7 +1,9 @@ +import type { ElementType, ReactNode } from 'react'; import React from 'react'; import cx from 'classnames'; import { Portal } from 'ariakit/portal'; -import { METRIC_TYPE_CONFIGS, MetricRunInfo, getMetricRunInfo } from '@bundle-stats/utils'; +import type { MetricRunInfo, ReportMetricRow } from '@bundle-stats/utils'; +import { METRIC_TYPE_CONFIGS, getMetricRunInfo } from '@bundle-stats/utils'; import { Box } from '../../layout/box'; import { Stack } from '../../layout/stack'; @@ -12,9 +14,10 @@ import { Table } from '../../ui/table'; import { RunInfo } from '../run-info'; import * as I18N from './entry-info.i18n'; import css from './entry-info.module.css'; +import { Tooltip } from '../../ui'; interface EntryInfoMetaLinkProps { - as?: React.ElementType; + as?: ElementType; } export const EntryInfoMetaLink = (props: EntryInfoMetaLinkProps & React.ComponentProps<'a'>) => { @@ -24,34 +27,60 @@ export const EntryInfoMetaLink = (props: EntryInfoMetaLinkProps & React.Componen interface EntryInfoMetaProps { label: string; + tooltip?: ReactNode; } const EntryInfoMeta = ({ className = '', label, + tooltip, children, }: EntryInfoMetaProps & React.ComponentProps<'p'>) => ( -

- {label} - {children} -

+
+ + {label} + {tooltip && ( + + + + )} + +
{children}
+
); -interface EntryRun { - name: string; +function defaultRenderRunInfo(item: ReportMetricRow) { + const baselineRun = item.runs.length > 1 ? item.runs?.[item.runs.length - 1] : null; + + // Get the metric run info to handle added/removed cases when there are more than 2 jobs + const metricRunInfo = getMetricRunInfo( + METRIC_TYPE_CONFIGS.METRIC_TYPE_FILE_SIZE, + item.runs?.[0]?.value || 0, + baselineRun?.value || 0, + ) as MetricRunInfo; + + return ( + + ); } interface EntryInfoProps { itemTitle?: React.ReactNode; - item: { - label: string; - runs: Array; - }; + item: ReportMetricRow; labels: Array; runNameSelector?: string; runNameLabel?: string; + runSizeLabel?: string; tags?: React.ReactNode; onClose: () => void; + renderRunInfo?: (item: ReportMetricRow) => React.ReactNode; } export const EntryInfo = (props: EntryInfoProps & React.ComponentProps<'div'>) => { @@ -62,38 +91,22 @@ export const EntryInfo = (props: EntryInfoProps & React.ComponentProps<'div'>) = labels, runNameSelector = 'name', runNameLabel = I18N.PATH, + runSizeLabel = I18N.SIZE, children, tags = null, onClose, + renderRunInfo = defaultRenderRunInfo, } = props; - const baselineRun = item.runs.length > 1 ? item.runs?.[item.runs.length - 1] : null; - - // Get the metric run info to handle added/removed cases - const metricRunInfo = getMetricRunInfo( - METRIC_TYPE_CONFIGS.METRIC_TYPE_FILE_SIZE, - item.runs?.[0]?.value, - baselineRun?.value || 0, - ) as MetricRunInfo; - return ( - {tags &&
{tags}
} -

- +
{renderRunInfo(item)}