Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions app/components/Package/SizeDecrease.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script setup lang="ts">
import type { InstallSizeDiff } from '~/composables/useInstallSizeDiff'

const props = defineProps<{
diff: InstallSizeDiff
}>()

const bytesFormatter = useBytesFormatter()
const numberFormatter = useNumberFormatter()

const sizePercent = computed(() => Math.round(Math.abs(props.diff.sizeRatio) * 100))
const sizeDecreaseAbs = computed(() => Math.abs(props.diff.sizeIncrease))
const depDecreaseAbs = computed(() => Math.abs(props.diff.depDiff))
</script>

<template>
<div
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"
>
<h2 class="font-medium mb-1 flex items-center gap-2">
<span class="i-lucide:trending-down w-4 h-4" aria-hidden="true" />
<span>
{{
diff.sizeThresholdExceeded && diff.depThresholdExceeded
? $t('package.size_decrease.title_both', { version: diff.comparisonVersion })
: diff.sizeThresholdExceeded
? $t('package.size_decrease.title_size', { version: diff.comparisonVersion })
: $t('package.size_decrease.title_deps', { version: diff.comparisonVersion })
}}
</span>
<span aria-hidden="true">🎉</span>
</h2>
<p class="text-sm m-0 mt-1">
<i18n-t v-if="diff.sizeThresholdExceeded" keypath="package.size_decrease.size" scope="global">
<template #percent
><strong>{{ sizePercent }}%</strong></template
>
<template #size
><strong>{{ bytesFormatter.format(sizeDecreaseAbs) }}</strong></template
>
</i18n-t>
<template v-if="diff.sizeThresholdExceeded && diff.depThresholdExceeded"> · </template>
<i18n-t v-if="diff.depThresholdExceeded" keypath="package.size_decrease.deps" scope="global">
<template #count
><strong>−{{ numberFormatter.format(depDecreaseAbs) }}</strong></template
>
</i18n-t>
</p>
</div>
</template>
20 changes: 17 additions & 3 deletions app/composables/useInstallSizeDiff.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { compare, prerelease, valid } from 'semver'

export interface InstallSizeDiff {
direction: 'increase' | 'decrease'
comparisonVersion: string
sizeRatio: number
sizeIncrease: number
Expand All @@ -15,6 +16,8 @@ export interface InstallSizeDiff {

const SIZE_INCREASE_THRESHOLD = 0.25
const DEP_INCREASE_THRESHOLD = 5
const SIZE_DECREASE_THRESHOLD = 0.2
const DEP_DECREASE_THRESHOLD = 3

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

const sizeThresholdExceeded = sizeRatio > SIZE_INCREASE_THRESHOLD
const depThresholdExceeded = depDiff > DEP_INCREASE_THRESHOLD
const increaseSize = sizeRatio > SIZE_INCREASE_THRESHOLD
const increaseDeps = depDiff > DEP_INCREASE_THRESHOLD
const decreaseSize = sizeRatio < -SIZE_DECREASE_THRESHOLD
const decreaseDeps = depDiff < -DEP_DECREASE_THRESHOLD

if (!sizeThresholdExceeded && !depThresholdExceeded) return null
const isIncrease = increaseSize || increaseDeps
const isDecrease =
!isIncrease && sizeRatio <= 0 && depDiff <= 0 && (decreaseSize || decreaseDeps)

if (!isIncrease && !isDecrease) return null

const direction: 'increase' | 'decrease' = isIncrease ? 'increase' : 'decrease'
const sizeThresholdExceeded = isIncrease ? increaseSize : decreaseSize
const depThresholdExceeded = isIncrease ? increaseDeps : decreaseDeps

return {
direction,
comparisonVersion: cv,
sizeRatio,
sizeIncrease: current.totalSize - previous.totalSize,
Expand Down
4 changes: 3 additions & 1 deletion app/pages/package/[[org]]/[name].vue
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,9 @@ const showSkeleton = shallowRef(false)
:replacement="moduleReplacement.replacement"
/>
<!-- Size / dependency increase notice -->
<PackageSizeIncrease v-if="sizeDiff" :diff="sizeDiff" />
<PackageSizeIncrease v-if="sizeDiff?.direction === 'increase'" :diff="sizeDiff" />
<!-- Size / dependency decrease celebration -->
<PackageSizeDecrease v-else-if="sizeDiff?.direction === 'decrease'" :diff="sizeDiff" />
<!-- Vulnerability scan -->
<ClientOnly>
<PackageVulnerabilityTree
Expand Down
7 changes: 7 additions & 0 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,13 @@
"size": "Install size increased by {percent} ({size} larger)",
"deps": "{count} more dependencies"
},
"size_decrease": {
"title_size": "Package size decreased since v{version}!",
"title_deps": "Dependency count decreased since v{version}!",
"title_both": "Package size and dependency count decreased since v{version}!",
"size": "Install size reduced by {percent} ({size} smaller)",
"deps": "{count} fewer dependencies"
},
"replacement": {
"title": "You might not need this dependency.",
"example": "Example:",
Expand Down
21 changes: 21 additions & 0 deletions i18n/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,27 @@
},
"additionalProperties": false
},
"size_decrease": {
"type": "object",
"properties": {
"title_size": {
"type": "string"
},
"title_deps": {
"type": "string"
},
"title_both": {
"type": "string"
},
"size": {
"type": "string"
},
"deps": {
"type": "string"
}
},
"additionalProperties": false
},
"replacement": {
"type": "object",
"properties": {
Expand Down
72 changes: 72 additions & 0 deletions test/nuxt/a11y.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ import FacetBarChart from '~/components/Compare/FacetBarChart.vue'
import FacetScatterChart from '~/components/Compare/FacetScatterChart.vue'
import PackageLikeCard from '~/components/Package/LikeCard.vue'
import SizeIncrease from '~/components/Package/SizeIncrease.vue'
import SizeDecrease from '~/components/Package/SizeDecrease.vue'
import Likes from '~/components/Package/Likes.vue'
import type { VueUiXyDatasetItem } from 'vue-data-ui'

Expand Down Expand Up @@ -3966,6 +3967,7 @@ describe('component accessibility audits', () => {
const component = await mountSuspended(SizeIncrease, {
props: {
diff: {
direction: 'increase',
comparisonVersion: '1.0.0',
sizeRatio: 1,
sizeIncrease: 200,
Expand All @@ -3987,6 +3989,7 @@ describe('component accessibility audits', () => {
const component = await mountSuspended(SizeIncrease, {
props: {
diff: {
direction: 'increase',
comparisonVersion: '1.0.0',
sizeRatio: 1,
sizeIncrease: 200,
Expand All @@ -4008,6 +4011,7 @@ describe('component accessibility audits', () => {
const component = await mountSuspended(SizeIncrease, {
props: {
diff: {
direction: 'increase',
comparisonVersion: '1.0.0',
sizeRatio: 0,
sizeIncrease: 0,
Expand All @@ -4026,6 +4030,74 @@ describe('component accessibility audits', () => {
})
})

describe('SizeDecrease', () => {
it('should have no accessibility violations', async () => {
const component = await mountSuspended(SizeDecrease, {
props: {
diff: {
direction: 'decrease',
comparisonVersion: '1.0.0',
sizeRatio: -0.5,
sizeIncrease: -200,
currentSize: 200,
previousSize: 400,
depDiff: -5,
currentDeps: 5,
previousDeps: 10,
sizeThresholdExceeded: true,
depThresholdExceeded: true,
},
},
})
const results = await runAxe(component)
expect(results.violations).toEqual([])
})

it('should have no accessibility violations with only size decrease', async () => {
const component = await mountSuspended(SizeDecrease, {
props: {
diff: {
direction: 'decrease',
comparisonVersion: '1.0.0',
sizeRatio: -0.5,
sizeIncrease: -200,
currentSize: 200,
previousSize: 400,
depDiff: 0,
currentDeps: 5,
previousDeps: 5,
sizeThresholdExceeded: true,
depThresholdExceeded: false,
},
},
})
const results = await runAxe(component)
expect(results.violations).toEqual([])
})

it('should have no accessibility violations with only dependency decrease', async () => {
const component = await mountSuspended(SizeDecrease, {
props: {
diff: {
direction: 'decrease',
comparisonVersion: '1.0.0',
sizeRatio: 0,
sizeIncrease: 0,
currentSize: 200,
previousSize: 200,
depDiff: -5,
currentDeps: 5,
previousDeps: 10,
sizeThresholdExceeded: false,
depThresholdExceeded: true,
},
},
})
const results = await runAxe(component)
expect(results.violations).toEqual([])
})
})

describe('PackageActionBar', () => {
it('should have no accessibility violations', async () => {
const component = await mountSuspended(PackageActionBar)
Expand Down
Loading
Loading