Skip to content

Commit ebcfc01

Browse files
authored
feat: celebrate size decreases (#2620)
1 parent 57ed389 commit ebcfc01

7 files changed

Lines changed: 324 additions & 4 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<script setup lang="ts">
2+
import type { InstallSizeDiff } from '~/composables/useInstallSizeDiff'
3+
4+
const props = defineProps<{
5+
diff: InstallSizeDiff
6+
}>()
7+
8+
const bytesFormatter = useBytesFormatter()
9+
const numberFormatter = useNumberFormatter()
10+
11+
const sizePercent = computed(() => Math.round(Math.abs(props.diff.sizeRatio) * 100))
12+
const sizeDecreaseAbs = computed(() => Math.abs(props.diff.sizeIncrease))
13+
const depDecreaseAbs = computed(() => Math.abs(props.diff.depDiff))
14+
</script>
15+
16+
<template>
17+
<div
18+
class="border border-emerald-600/40 bg-emerald-500/10 rounded-lg px-3 py-2 text-base text-emerald-800 dark:text-emerald-400"
19+
>
20+
<h2 class="font-medium mb-1 flex items-center gap-2">
21+
<span class="i-lucide:trending-down w-4 h-4" aria-hidden="true" />
22+
<span>
23+
{{
24+
diff.sizeThresholdExceeded && diff.depThresholdExceeded
25+
? $t('package.size_decrease.title_both', { version: diff.comparisonVersion })
26+
: diff.sizeThresholdExceeded
27+
? $t('package.size_decrease.title_size', { version: diff.comparisonVersion })
28+
: $t('package.size_decrease.title_deps', { version: diff.comparisonVersion })
29+
}}
30+
</span>
31+
<span aria-hidden="true">🎉</span>
32+
</h2>
33+
<p class="text-sm m-0 mt-1">
34+
<i18n-t v-if="diff.sizeThresholdExceeded" keypath="package.size_decrease.size" scope="global">
35+
<template #percent
36+
><strong>{{ sizePercent }}%</strong></template
37+
>
38+
<template #size
39+
><strong>{{ bytesFormatter.format(sizeDecreaseAbs) }}</strong></template
40+
>
41+
</i18n-t>
42+
<template v-if="diff.sizeThresholdExceeded && diff.depThresholdExceeded"> · </template>
43+
<i18n-t v-if="diff.depThresholdExceeded" keypath="package.size_decrease.deps" scope="global">
44+
<template #count
45+
><strong>−{{ numberFormatter.format(depDecreaseAbs) }}</strong></template
46+
>
47+
</i18n-t>
48+
</p>
49+
</div>
50+
</template>

app/composables/useInstallSizeDiff.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { compare, prerelease, valid } from 'semver'
22

33
export interface InstallSizeDiff {
4+
direction: 'increase' | 'decrease'
45
comparisonVersion: string
56
sizeRatio: number
67
sizeIncrease: number
@@ -15,6 +16,8 @@ export interface InstallSizeDiff {
1516

1617
const SIZE_INCREASE_THRESHOLD = 0.25
1718
const DEP_INCREASE_THRESHOLD = 5
19+
const SIZE_DECREASE_THRESHOLD = 0.2
20+
const DEP_DECREASE_THRESHOLD = 3
1821

1922
function getComparisonVersion(pkg: SlimPackument, resolvedVersion: string): string | null {
2023
const isCurrentPrerelease = prerelease(resolvedVersion) !== null
@@ -91,12 +94,23 @@ export function useInstallSizeDiff(
9194
previous.totalSize > 0 ? (current.totalSize - previous.totalSize) / previous.totalSize : 0
9295
const depDiff = current.dependencyCount - previous.dependencyCount
9396

94-
const sizeThresholdExceeded = sizeRatio > SIZE_INCREASE_THRESHOLD
95-
const depThresholdExceeded = depDiff > DEP_INCREASE_THRESHOLD
97+
const increaseSize = sizeRatio > SIZE_INCREASE_THRESHOLD
98+
const increaseDeps = depDiff > DEP_INCREASE_THRESHOLD
99+
const decreaseSize = sizeRatio < -SIZE_DECREASE_THRESHOLD
100+
const decreaseDeps = depDiff < -DEP_DECREASE_THRESHOLD
96101

97-
if (!sizeThresholdExceeded && !depThresholdExceeded) return null
102+
const isIncrease = increaseSize || increaseDeps
103+
const isDecrease =
104+
!isIncrease && sizeRatio <= 0 && depDiff <= 0 && (decreaseSize || decreaseDeps)
105+
106+
if (!isIncrease && !isDecrease) return null
107+
108+
const direction: 'increase' | 'decrease' = isIncrease ? 'increase' : 'decrease'
109+
const sizeThresholdExceeded = isIncrease ? increaseSize : decreaseSize
110+
const depThresholdExceeded = isIncrease ? increaseDeps : decreaseDeps
98111

99112
return {
113+
direction,
100114
comparisonVersion: cv,
101115
sizeRatio,
102116
sizeIncrease: current.totalSize - previous.totalSize,

app/pages/package/[[org]]/[name].vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -919,7 +919,9 @@ const showSkeleton = shallowRef(false)
919919
:replacement="moduleReplacement.replacement"
920920
/>
921921
<!-- Size / dependency increase notice -->
922-
<PackageSizeIncrease v-if="sizeDiff" :diff="sizeDiff" />
922+
<PackageSizeIncrease v-if="sizeDiff?.direction === 'increase'" :diff="sizeDiff" />
923+
<!-- Size / dependency decrease celebration -->
924+
<PackageSizeDecrease v-else-if="sizeDiff?.direction === 'decrease'" :diff="sizeDiff" />
923925
<!-- Vulnerability scan -->
924926
<ClientOnly>
925927
<PackageVulnerabilityTree

i18n/locales/en.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,13 @@
384384
"size": "Install size increased by {percent} ({size} larger)",
385385
"deps": "{count} more dependencies"
386386
},
387+
"size_decrease": {
388+
"title_size": "Package size decreased since v{version}!",
389+
"title_deps": "Dependency count decreased since v{version}!",
390+
"title_both": "Package size and dependency count decreased since v{version}!",
391+
"size": "Install size reduced by {percent} ({size} smaller)",
392+
"deps": "{count} fewer dependencies"
393+
},
387394
"replacement": {
388395
"title": "You might not need this dependency.",
389396
"example": "Example:",

i18n/schema.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,6 +1156,27 @@
11561156
},
11571157
"additionalProperties": false
11581158
},
1159+
"size_decrease": {
1160+
"type": "object",
1161+
"properties": {
1162+
"title_size": {
1163+
"type": "string"
1164+
},
1165+
"title_deps": {
1166+
"type": "string"
1167+
},
1168+
"title_both": {
1169+
"type": "string"
1170+
},
1171+
"size": {
1172+
"type": "string"
1173+
},
1174+
"deps": {
1175+
"type": "string"
1176+
}
1177+
},
1178+
"additionalProperties": false
1179+
},
11591180
"replacement": {
11601181
"type": "object",
11611182
"properties": {

test/nuxt/a11y.spec.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ import FacetBarChart from '~/components/Compare/FacetBarChart.vue'
270270
import FacetScatterChart from '~/components/Compare/FacetScatterChart.vue'
271271
import PackageLikeCard from '~/components/Package/LikeCard.vue'
272272
import SizeIncrease from '~/components/Package/SizeIncrease.vue'
273+
import SizeDecrease from '~/components/Package/SizeDecrease.vue'
273274
import Likes from '~/components/Package/Likes.vue'
274275
import type { VueUiXyDatasetItem } from 'vue-data-ui'
275276

@@ -3966,6 +3967,7 @@ describe('component accessibility audits', () => {
39663967
const component = await mountSuspended(SizeIncrease, {
39673968
props: {
39683969
diff: {
3970+
direction: 'increase',
39693971
comparisonVersion: '1.0.0',
39703972
sizeRatio: 1,
39713973
sizeIncrease: 200,
@@ -3987,6 +3989,7 @@ describe('component accessibility audits', () => {
39873989
const component = await mountSuspended(SizeIncrease, {
39883990
props: {
39893991
diff: {
3992+
direction: 'increase',
39903993
comparisonVersion: '1.0.0',
39913994
sizeRatio: 1,
39923995
sizeIncrease: 200,
@@ -4008,6 +4011,7 @@ describe('component accessibility audits', () => {
40084011
const component = await mountSuspended(SizeIncrease, {
40094012
props: {
40104013
diff: {
4014+
direction: 'increase',
40114015
comparisonVersion: '1.0.0',
40124016
sizeRatio: 0,
40134017
sizeIncrease: 0,
@@ -4026,6 +4030,74 @@ describe('component accessibility audits', () => {
40264030
})
40274031
})
40284032

4033+
describe('SizeDecrease', () => {
4034+
it('should have no accessibility violations', async () => {
4035+
const component = await mountSuspended(SizeDecrease, {
4036+
props: {
4037+
diff: {
4038+
direction: 'decrease',
4039+
comparisonVersion: '1.0.0',
4040+
sizeRatio: -0.5,
4041+
sizeIncrease: -200,
4042+
currentSize: 200,
4043+
previousSize: 400,
4044+
depDiff: -5,
4045+
currentDeps: 5,
4046+
previousDeps: 10,
4047+
sizeThresholdExceeded: true,
4048+
depThresholdExceeded: true,
4049+
},
4050+
},
4051+
})
4052+
const results = await runAxe(component)
4053+
expect(results.violations).toEqual([])
4054+
})
4055+
4056+
it('should have no accessibility violations with only size decrease', async () => {
4057+
const component = await mountSuspended(SizeDecrease, {
4058+
props: {
4059+
diff: {
4060+
direction: 'decrease',
4061+
comparisonVersion: '1.0.0',
4062+
sizeRatio: -0.5,
4063+
sizeIncrease: -200,
4064+
currentSize: 200,
4065+
previousSize: 400,
4066+
depDiff: 0,
4067+
currentDeps: 5,
4068+
previousDeps: 5,
4069+
sizeThresholdExceeded: true,
4070+
depThresholdExceeded: false,
4071+
},
4072+
},
4073+
})
4074+
const results = await runAxe(component)
4075+
expect(results.violations).toEqual([])
4076+
})
4077+
4078+
it('should have no accessibility violations with only dependency decrease', async () => {
4079+
const component = await mountSuspended(SizeDecrease, {
4080+
props: {
4081+
diff: {
4082+
direction: 'decrease',
4083+
comparisonVersion: '1.0.0',
4084+
sizeRatio: 0,
4085+
sizeIncrease: 0,
4086+
currentSize: 200,
4087+
previousSize: 200,
4088+
depDiff: -5,
4089+
currentDeps: 5,
4090+
previousDeps: 10,
4091+
sizeThresholdExceeded: false,
4092+
depThresholdExceeded: true,
4093+
},
4094+
},
4095+
})
4096+
const results = await runAxe(component)
4097+
expect(results.violations).toEqual([])
4098+
})
4099+
})
4100+
40294101
describe('PackageActionBar', () => {
40304102
it('should have no accessibility violations', async () => {
40314103
const component = await mountSuspended(PackageActionBar)

0 commit comments

Comments
 (0)