From d2759a1c7480a7ca1a103ec4f8894b394c039b78 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Wed, 24 Jul 2024 23:07:15 +0200 Subject: [PATCH 1/9] feat(ui): ModuleInfo - show duplicate instances and show chunks differences --- .../entry-info/entry-info.module.css | 27 ++- .../src/components/entry-info/entry-info.tsx | 9 +- .../module-info/module-info.module.css | 34 ++++ .../module-info/module-info.stories.tsx | 60 ++++++- .../components/module-info/module-info.tsx | 163 +++++++++++++++--- .../components/run-info/run-info.module.css | 2 +- 6 files changed, 253 insertions(+), 42 deletions(-) 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..a2a4cd13e3 100644 --- a/packages/ui/src/components/entry-info/entry-info.module.css +++ b/packages/ui/src/components/entry-info/entry-info.module.css @@ -77,34 +77,47 @@ font-family: var(--font-family-fixed); } +.meta { + display: flex; + align-items: flex-start; +} + .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); } +.metaContent { + padding: 1px 0; + flex: 0 1 auto; +} + .metaLink { - padding: 2px var(--space-xxxsmall); + padding: 1px var(--space-xxxsmall); + display: inline-block; 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); } diff --git a/packages/ui/src/components/entry-info/entry-info.tsx b/packages/ui/src/components/entry-info/entry-info.tsx index 791fa2e052..416d4f0503 100644 --- a/packages/ui/src/components/entry-info/entry-info.tsx +++ b/packages/ui/src/components/entry-info/entry-info.tsx @@ -1,3 +1,4 @@ +import type { ElementType } from 'react'; import React from 'react'; import cx from 'classnames'; import { Portal } from 'ariakit/portal'; @@ -14,7 +15,7 @@ import * as I18N from './entry-info.i18n'; import css from './entry-info.module.css'; interface EntryInfoMetaLinkProps { - as?: React.ElementType; + as?: ElementType; } export const EntryInfoMetaLink = (props: EntryInfoMetaLinkProps & React.ComponentProps<'a'>) => { @@ -31,10 +32,10 @@ const EntryInfoMeta = ({ label, children, }: EntryInfoMetaProps & React.ComponentProps<'p'>) => ( -

+

{label} - {children} -

+
{children}
+
); interface EntryRun { 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..dc1d0edcef 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,37 @@ .chunksItems { display: inline; } + +.chunksItem-info { + color: var(--color-info); +} + +.chunksItem-info:hover, +.chunksItem-info:active, +.chunksItem-info:focus { + color: var(--color-info-dark); +} + +.chunksItem-danger { + color: var(--color-danger); +} + +.chunksItem-danger:hover, +.chunksItem-danger:active, +.chunksItem-danger:focus { + color: var(--color-danger-dark); +} + +.chunksItem-default { + color: var(--color-text-light); +} + +.chunksItem-default:hover, +.chunksItem-default:active, +.chunksItem-default:focus { + color: var(--color-text); +} + +.duplicateInstances { + padding: 4px 0; /* compensate for the Entry Label padding */ +} diff --git a/packages/ui/src/components/module-info/module-info.stories.tsx b/packages/ui/src/components/module-info/module-info.stories.tsx index 367ec3f1b2..9e89e84e21 100644 --- a/packages/ui/src/components/module-info/module-info.stories.tsx +++ b/packages/ui/src/components/module-info/module-info.stories.tsx @@ -1,4 +1,6 @@ import React from 'react'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { action } from '@storybook/addon-actions'; import { getWrapperDecorator } from '../../stories'; import { ModuleInfo } from '.'; @@ -17,12 +19,12 @@ export const Default = () => ( runs: [ { name: './node_modules/lodash/fp/merge.js', - chunkIds: ['1', '2'], + chunkIds: ['1'], value: 1024 * 4, }, { name: './node_modules/lodash/fp/merge.js', - chunkIds: ['1', '2'], + chunkIds: ['1'], value: 1024 * 3, displayValue: '3KiB', }, @@ -37,8 +39,60 @@ export const Default = () => ( id: '2', name: 'chunk-2', }, + { + id: '3', + name: 'chunk-3', + }, + ]} + chunkIds={['1', '2', '3']} + labels={['Job #2', 'Job #1']} + onClose={action('CLOSE')} + /> +); + +export const Duplicates = () => ( + ); diff --git a/packages/ui/src/components/module-info/module-info.tsx b/packages/ui/src/components/module-info/module-info.tsx index 5ea381f12b..43c1f2bdd3 100644 --- a/packages/ui/src/components/module-info/module-info.tsx +++ b/packages/ui/src/components/module-info/module-info.tsx @@ -1,24 +1,135 @@ +import type { ElementType } from 'react'; import React, { useMemo } from 'react'; import cx from 'classnames'; -import isEmpty from 'lodash/isEmpty'; +import orderBy from 'lodash/orderBy'; import noop from 'lodash/noop'; import { BUNDLE_MODULES_DUPLICATE, FILE_TYPE_LABELS, MODULE_SOURCE_TYPE_LABELS, MetricRunInfo, + formatNumber, getBundleModulesByChunk, getBundleModulesByFileTpe, getBundleModulesBySource, + getMetricRunInfo, } from '@bundle-stats/utils'; import { Module, MetaChunk } from '@bundle-stats/utils/types/webpack'; import { Stack } from '../../layout/stack'; import { Tag } from '../../ui/tag'; import { ComponentLink } from '../component-link'; +import { RunInfo } from '../run-info'; import { EntryInfo, EntryInfoMetaLink } from '../entry-info'; import css from './module-info.module.css'; +interface DuplicateInstancesProps { + current: number; + baseline: number; +} + +const DuplicateInstances = ({ current, baseline }: DuplicateInstancesProps) => { + const runInfo = getMetricRunInfo( + { biggerIsBetter: null, formatter: formatNumber }, + current, + baseline, + ) as MetricRunInfo; + + return ( + + ); +}; + +enum ChunkDataState { + ADDED = 2, + REMOVED = 1, + NOT_CHANGED = 0, +} + +const ChunkDataStateKind = { + [ChunkDataState.ADDED]: 'info', + [ChunkDataState.REMOVED]: 'danger', + [ChunkDataState.NOT_CHANGED]: 'default', +}; + +interface ChunkData { + id: string; + state: ChunkDataState; +} + +function compareChunksData( + currentChunkIds: Array, + baselineChunkIds: Array, +): Array { + const result: Array = []; + + currentChunkIds.forEach((chunkId) => { + if (!baselineChunkIds.includes(chunkId)) { + result.push({ id: chunkId, state: ChunkDataState.ADDED }); + } else { + result.push({ id: chunkId, state: ChunkDataState.NOT_CHANGED }); + } + }); + + baselineChunkIds.forEach((chunk) => { + if (!currentChunkIds.includes(chunk)) { + result.push({ id: chunk, state: ChunkDataState.REMOVED }); + } + }); + + return orderBy(result, 'state', 'desc'); +} + +interface ChunksDeltaProps { + customComponentLink: ElementType; + runs: Array; + chunks: Array; + chunkIds: Array; +} + +const ChunksDelta = (props: ChunksDeltaProps) => { + const { customComponentLink: CustomComponentLink, runs, chunks, chunkIds } = props; + + const currentChunkIds = runs[0]?.chunkIds || []; + const baselineChunkIds = runs[runs.length - 1]?.chunkIds || []; + + const chunksData = compareChunksData(currentChunkIds, baselineChunkIds); + + return ( + +
+ {chunksData.map((chunkData) => { + const chunk = chunks?.find(({ id }) => id === chunkData.id); + + if (!chunk) { + return null; + } + + return ( + + {chunk.name} + + ); + })} +
+
+ ); +}; + interface ModuleInfoProps { item: { label: string; @@ -69,35 +180,15 @@ export const ModuleInfo = (props: ModuleInfoProps & React.ComponentProps<'div'>) ? MODULE_SOURCE_TYPE_LABELS.THIRD_PARTY : MODULE_SOURCE_TYPE_LABELS.FIRST_PARTY; + const currentChunkCount = (item.runs[0]?.chunkIds || []).length; + const baselineChunkCount = (item.runs[item.runs.length - 1]?.chunkIds || []).length; + const currentDuplicateInstances = currentChunkCount > 0 ? currentChunkCount - 1 : 0; + const baselineDuplicateInstances = baselineChunkCount > 0 ? baselineChunkCount - 1 : 0; + const hasDuplicates = currentDuplicateInstances !== 0 || baselineDuplicateInstances !== 0; + return ( - {item.runs?.map( - (currentRun, index) => - !isEmpty(currentRun?.chunkIds) && ( - - {labels[index]}: - {currentRun.chunkIds.map((chunkId) => { - const chunk = chunks?.find(({ id }) => id === chunkId); - - if (!chunk) { - return null; - } - - return ( - - {chunk.name} - - ); - })} - - ), - )} - {item?.fileType && ( ) {sourceTypeLabel} + + {hasDuplicates && ( + + + + )} + + + + ); diff --git a/packages/ui/src/components/run-info/run-info.module.css b/packages/ui/src/components/run-info/run-info.module.css index 553d496995..916351a4d6 100644 --- a/packages/ui/src/components/run-info/run-info.module.css +++ b/packages/ui/src/components/run-info/run-info.module.css @@ -19,7 +19,7 @@ margin-bottom: -3px !important; } -.info { +.title + .info { margin-top: var(--space-xxsmall); } From e8c633c989aaea1ec7281ce802b389be55738f47 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Fri, 26 Jul 2024 00:34:53 +0200 Subject: [PATCH 2/9] feat(ui): EntryInfo - add help tooltips to metas --- .../src/components/asset-info/asset-info.tsx | 2 +- .../entry-info/entry-info.module.css | 22 ++++++++++++++----- .../src/components/entry-info/entry-info.tsx | 14 ++++++++++-- .../components/module-info/module-info.tsx | 8 +++---- .../components/package-info/package-info.tsx | 2 +- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/packages/ui/src/components/asset-info/asset-info.tsx b/packages/ui/src/components/asset-info/asset-info.tsx index 5954f4120d..eded734cac 100644 --- a/packages/ui/src/components/asset-info/asset-info.tsx +++ b/packages/ui/src/components/asset-info/asset-info.tsx @@ -133,7 +133,7 @@ export const AssetInfo = (props: AssetInfoProps & React.ComponentProps<'div'>) = className={cx(css.root, className)} > {item.fileType && ( - + ) => (
- {label} + + {label} + {tooltip && ( + + + + )} +
{children}
); diff --git a/packages/ui/src/components/module-info/module-info.tsx b/packages/ui/src/components/module-info/module-info.tsx index 43c1f2bdd3..29cd601af5 100644 --- a/packages/ui/src/components/module-info/module-info.tsx +++ b/packages/ui/src/components/module-info/module-info.tsx @@ -190,7 +190,7 @@ export const ModuleInfo = (props: ModuleInfoProps & React.ComponentProps<'div'>) {item?.fileType && ( - + ) )} - + ) {hasDuplicates && ( - + ) )} - + - + From 789e296f6d33cc1bde14528488838e11b68baf76 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sun, 4 Aug 2024 11:17:12 +0200 Subject: [PATCH 3/9] fix(ui): BundleModules - improve type definition --- .../src/components/asset-info/asset-info.tsx | 7 ++-- .../bundle-modules/bundle-modules.tsx | 26 +++++++++------ .../bundle-modules/bundle-modules.types.d.ts | 9 ------ .../bundle-modules/bundle-modules.utils.ts | 32 +++++++++++-------- .../src/components/bundle-modules/index.tsx | 7 ++-- .../src/components/entry-info/entry-info.tsx | 14 +++----- .../components/module-info/module-info.tsx | 21 ++++++------ .../components/package-info/package-info.tsx | 3 +- packages/ui/src/constants.ts | 2 +- packages/ui/src/hooks/rows-sort.ts | 27 ++++++++++------ packages/ui/src/types.d.ts | 11 +++++++ packages/utils/src/constants.ts | 3 ++ packages/utils/src/webpack/extract/modules.ts | 11 ++++--- packages/utils/src/webpack/types.ts | 9 ++++++ 14 files changed, 108 insertions(+), 74 deletions(-) delete mode 100644 packages/ui/src/components/bundle-modules/bundle-modules.types.d.ts diff --git a/packages/ui/src/components/asset-info/asset-info.tsx b/packages/ui/src/components/asset-info/asset-info.tsx index eded734cac..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 && ( )} - + ); }; @@ -215,7 +221,7 @@ export const BundleModules = (props: BundleModulesProps) => { ); const renderRowHeader = useCallback( - (row: ReportMetricModuleRow) => ( + (row: ReportMetricRow) => ( { type="button" onClick={() => setModuleMetric(ModuleMetric.TOTAL_SIZE)} > - Module total size + + Module total size + @@ -355,7 +358,7 @@ export const BundleModules = (props: BundleModulesProps) => { chunks={chunks} chunkIds={chunks?.map(({ id }) => id)} labels={jobLabels} - metricLabel={ModuleMetrics[moduleMetric].label} + metricLabel={ModuleSizeMetrics[moduleMetric].label} customComponentLink={CustomComponentLink} onClose={hideEntryInfo} /> diff --git a/packages/ui/src/components/bundle-modules/index.tsx b/packages/ui/src/components/bundle-modules/index.tsx index 515b0e36d1..9ea97dfcd2 100644 --- a/packages/ui/src/components/bundle-modules/index.tsx +++ b/packages/ui/src/components/bundle-modules/index.tsx @@ -3,6 +3,7 @@ import type { Job } from '@bundle-stats/utils'; // @ts-ignore import * as webpack from '@bundle-stats/utils/lib-esm/webpack'; +import { ModuleSizeMetric } from '../../constants'; import type { ReportMetricModuleRow, SortAction } from '../../types'; import { getJobsChunksData } from '../../utils/jobs'; import { useRowsFilter } from '../../hooks/rows-filter'; @@ -16,26 +17,27 @@ import { generateFilters, getCustomSort, } from './bundle-modules.utils'; -import { ModuleMetric } from './bundle-modules.constants'; 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 }); } @@ -97,9 +99,9 @@ export const BundleModules = (props: BundleModulesProps) => { const { rows, totalRowCount } = useMemo(() => { 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]); diff --git a/packages/ui/src/components/module-info/module-info.tsx b/packages/ui/src/components/module-info/module-info.tsx index ae6452d492..6f17be1d54 100644 --- a/packages/ui/src/components/module-info/module-info.tsx +++ b/packages/ui/src/components/module-info/module-info.tsx @@ -25,6 +25,7 @@ import { RunInfo } from '../run-info'; import { EntryInfo, EntryInfoMetaLink } from '../entry-info'; import css from './module-info.module.css'; import { FlexStack } from '../../layout'; +import { ModuleSizeMetric, ModuleSizeMetrics } from '../../constants'; interface DuplicateInstancesProps { current: number; @@ -136,18 +137,20 @@ const ChunksDelta = (props: ChunksDeltaProps) => { interface ModuleSizeRunInfoProps { metric: MetricTypeConfig; title: string; + titleTooltip?: string; current?: number; baseline?: number; } const ModuleSizeRunInfo = (props: ModuleSizeRunInfoProps) => { - const { metric, title, current, baseline } = props; + const { metric, title, titleTooltip, current, baseline } = props; const runInfo = getMetricRunInfo(metric, current || 0, baseline || 0) as MetricRunInfo; return ( { <> @@ -185,7 +190,8 @@ const renderRunInfo = (item: ReportMetricRow) => { )} diff --git a/packages/ui/src/constants.ts b/packages/ui/src/constants.ts index acc67f143f..06c6af63b3 100644 --- a/packages/ui/src/constants.ts +++ b/packages/ui/src/constants.ts @@ -45,3 +45,24 @@ export enum MetricsDisplayType { TABLE = 'table', TREEMAP = 'treemap', } + +export enum ModuleSizeMetric { + SIZE = 'value', + DUPLICATE_SIZE = 'duplicateSize', + TOTAL_SIZE = 'totalSize', +} + +export const ModuleSizeMetrics = { + [ModuleSizeMetric.SIZE]: { + label: 'Size', + tooltip: 'Module size (excluding duplicate modules)', + }, + [ModuleSizeMetric.TOTAL_SIZE]: { + label: 'Total size', + tooltip: 'Module total size (including duplicate modules)', + }, + [ModuleSizeMetric.DUPLICATE_SIZE]: { + label: 'Duplicate size', + tooltip: 'Module duplicate size', + }, +}; From 8a90741c6bd75804b744694ea254e6a899f467b7 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sun, 4 Aug 2024 16:47:24 +0200 Subject: [PATCH 8/9] enhance(ui): EntryInfo - render tags under header --- packages/ui/src/components/entry-info/entry-info.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/entry-info/entry-info.tsx b/packages/ui/src/components/entry-info/entry-info.tsx index 40d5c2e3ba..2ff00c7b7e 100644 --- a/packages/ui/src/components/entry-info/entry-info.tsx +++ b/packages/ui/src/components/entry-info/entry-info.tsx @@ -102,8 +102,6 @@ export const EntryInfo = (props: EntryInfoProps & React.ComponentProps<'div'>) = - {tags &&
{tags}
} -

@@ -122,6 +120,8 @@ export const EntryInfo = (props: EntryInfoProps & React.ComponentProps<'div'>) =
+ {tags &&
{tags}
} + {children} From 321230a97ebc271210bf99edabf9f9999716ffd4 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Sun, 4 Aug 2024 16:48:26 +0200 Subject: [PATCH 9/9] fix(ui): ModuleInfo - align duplicate instances --- packages/ui/src/components/module-info/module-info.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bc7fb7ea17..9e23e52bae 100644 --- a/packages/ui/src/components/module-info/module-info.module.css +++ b/packages/ui/src/components/module-info/module-info.module.css @@ -37,5 +37,5 @@ } .duplicateInstances { - padding: 4px 0; /* compensate for the Entry Label padding */ + padding: 2px 0; /* compensate for the Entry Label padding */ }