Skip to content

Commit ffbd262

Browse files
committed
feat: display total install size (based on linux-x64-glibc deps)
1 parent a45a1c5 commit ffbd262

6 files changed

Lines changed: 1335 additions & 41 deletions

File tree

app/components/PackageDependencies.vue

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ const props = defineProps<{
44
dependencies?: Record<string, string>
55
peerDependencies?: Record<string, string>
66
peerDependenciesMeta?: Record<string, { optional?: boolean }>
7+
optionalDependencies?: Record<string, string>
78
}>()
89
910
// Expanded state for each section
1011
const depsExpanded = ref(false)
1112
const peerDepsExpanded = ref(false)
13+
const optionalDepsExpanded = ref(false)
1214
1315
// Sort dependencies alphabetically
1416
const sortedDependencies = computed(() => {
@@ -32,6 +34,12 @@ const sortedPeerDependencies = computed(() => {
3234
return a.name.localeCompare(b.name)
3335
})
3436
})
37+
38+
// Sort optional dependencies alphabetically
39+
const sortedOptionalDependencies = computed(() => {
40+
if (!props.optionalDependencies) return []
41+
return Object.entries(props.optionalDependencies).sort(([a], [b]) => a.localeCompare(b))
42+
})
3543
</script>
3644

3745
<template>
@@ -117,5 +125,49 @@ const sortedPeerDependencies = computed(() => {
117125
show all {{ sortedPeerDependencies.length }} peer deps
118126
</button>
119127
</section>
128+
129+
<!-- Optional Dependencies -->
130+
<section
131+
v-if="sortedOptionalDependencies.length > 0"
132+
aria-labelledby="optional-dependencies-heading"
133+
>
134+
<h2
135+
id="optional-dependencies-heading"
136+
class="text-xs text-fg-subtle uppercase tracking-wider mb-3"
137+
>
138+
Optional Dependencies ({{ sortedOptionalDependencies.length }})
139+
</h2>
140+
<ul class="space-y-1 list-none m-0 p-0" aria-label="Package optional dependencies">
141+
<li
142+
v-for="[dep, version] in sortedOptionalDependencies.slice(
143+
0,
144+
optionalDepsExpanded ? undefined : 10,
145+
)"
146+
:key="dep"
147+
class="flex items-center justify-between py-1 text-sm gap-2"
148+
>
149+
<NuxtLink
150+
:to="{ name: 'package', params: { package: dep.split('/') } }"
151+
class="font-mono text-fg-muted hover:text-fg transition-colors duration-200 truncate min-w-0"
152+
>
153+
{{ dep }}
154+
</NuxtLink>
155+
<span
156+
class="font-mono text-xs text-fg-subtle max-w-[50%] text-right truncate"
157+
:title="version"
158+
>
159+
{{ version }}
160+
</span>
161+
</li>
162+
</ul>
163+
<button
164+
v-if="sortedOptionalDependencies.length > 10 && !optionalDepsExpanded"
165+
type="button"
166+
class="mt-2 font-mono text-xs text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
167+
@click="optionalDepsExpanded = true"
168+
>
169+
show all {{ sortedOptionalDependencies.length }} optional deps
170+
</button>
171+
</section>
120172
</div>
121173
</template>

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

Lines changed: 73 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,37 @@ const { data: jsrInfo } = useLazyFetch<JsrPackageInfo>(() => `/api/jsr/${package
6767
immediate: computed(() => packageName.value.startsWith('@')).value,
6868
})
6969
70+
// Fetch total install size (lazy, can be slow for large dependency trees)
71+
interface InstallSizeResult {
72+
package: string
73+
version: string
74+
selfSize: number
75+
totalSize: number
76+
dependencyCount: number
77+
}
78+
const { data: installSize, status: installSizeStatus } = useLazyFetch<InstallSizeResult | null>(
79+
() => {
80+
const base = `/api/registry/install-size/${packageName.value}`
81+
const version = requestedVersion.value
82+
return version ? `${base}/v/${version}` : base
83+
},
84+
{
85+
server: false,
86+
},
87+
)
88+
89+
const sizeTooltip = computed(() => {
90+
const chunks = [
91+
displayVersion.value &&
92+
displayVersion.value.dist.unpackedSize &&
93+
`${formatBytes(displayVersion.value.dist.unpackedSize)} unpacked size (this package)`,
94+
installSize.value &&
95+
installSize.value.dependencyCount &&
96+
`${formatBytes(installSize.value.totalSize)} total unpacked size (including all ${installSize.value.dependencyCount} dependencies for linux-x64)`,
97+
]
98+
return chunks.filter(Boolean).join('\n')
99+
})
100+
70101
// Get the version to display (requested or latest)
71102
const displayVersion = computed(() => {
72103
if (!pkg.value) return null
@@ -93,7 +124,12 @@ const hasDependencies = computed(() => {
93124
if (!displayVersion.value) return false
94125
const deps = displayVersion.value.dependencies
95126
const peerDeps = displayVersion.value.peerDependencies
96-
return (deps && Object.keys(deps).length > 0) || (peerDeps && Object.keys(peerDeps).length > 0)
127+
const optionalDeps = displayVersion.value.optionalDependencies
128+
return (
129+
(deps && Object.keys(deps).length > 0) ||
130+
(peerDeps && Object.keys(peerDeps).length > 0) ||
131+
(optionalDeps && Object.keys(optionalDeps).length > 0)
132+
)
97133
})
98134
99135
const repositoryUrl = computed(() => {
@@ -353,23 +389,6 @@ defineOgImageComponent('Package', {
353389
</dd>
354390
</div>
355391

356-
<div v-if="displayVersion?.dist?.unpackedSize" class="space-y-1">
357-
<dt class="text-xs text-fg-subtle uppercase tracking-wider">Size</dt>
358-
<dd class="font-mono text-sm text-fg flex items-baseline justify-start gap-2">
359-
{{ formatBytes(displayVersion.dist.unpackedSize) }}
360-
<a
361-
:href="`https://pkg-size.dev/${pkg.name}`"
362-
target="_blank"
363-
rel="noopener noreferrer"
364-
class="text-fg-subtle hover:text-fg transition-colors duration-200"
365-
title="View bundle size analysis"
366-
>
367-
<span class="i-carbon-launch w-3.5 h-3.5 inline-block" aria-hidden="true" />
368-
<span class="sr-only">View bundle size analysis</span>
369-
</a>
370-
</dd>
371-
</div>
372-
373392
<div v-if="getDependencyCount(displayVersion) > 0" class="space-y-1">
374393
<dt class="text-xs text-fg-subtle uppercase tracking-wider">Deps</dt>
375394
<dd class="font-mono text-sm text-fg flex items-baseline justify-start gap-2">
@@ -387,7 +406,41 @@ defineOgImageComponent('Package', {
387406
</dd>
388407
</div>
389408

390-
<div v-if="pkg.time?.modified" class="space-y-1 col-span-2">
409+
<div class="space-y-1 col-span-2">
410+
<dt class="text-xs text-fg-subtle uppercase tracking-wider flex items-center gap-1">
411+
Install Size
412+
<span
413+
class="i-carbon-information w-3 h-3 text-fg-subtle"
414+
aria-hidden="true"
415+
:title="sizeTooltip"
416+
/>
417+
</dt>
418+
<dd class="font-mono text-sm text-fg">
419+
<!-- Package size (greyed out) -->
420+
<span class="text-fg-muted">
421+
<span v-if="displayVersion?.dist?.unpackedSize">
422+
{{ formatBytes(displayVersion.dist.unpackedSize) }}
423+
</span>
424+
<span v-else>-</span>
425+
</span>
426+
427+
<!-- Separator and install size -->
428+
<span class="text-fg-subtle mx-1">/</span>
429+
430+
<span
431+
v-if="installSizeStatus === 'pending'"
432+
class="inline-flex items-center gap-1 text-fg-subtle"
433+
>
434+
<span class="i-carbon-circle-dash w-3 h-3 animate-spin" aria-hidden="true" />
435+
</span>
436+
<span v-else-if="installSize?.totalSize">
437+
{{ formatBytes(installSize.totalSize) }}
438+
</span>
439+
<span v-else class="text-fg-subtle">-</span>
440+
</dd>
441+
</div>
442+
443+
<div v-if="pkg.time?.modified" class="space-y-1">
391444
<dt class="text-xs text-fg-subtle uppercase tracking-wider">Updated</dt>
392445
<dd class="font-mono text-sm text-fg">
393446
<time :datetime="pkg.time.modified">{{ formatDate(pkg.time.modified) }}</time>
@@ -664,6 +717,7 @@ defineOgImageComponent('Package', {
664717
:dependencies="displayVersion?.dependencies"
665718
:peer-dependencies="displayVersion?.peerDependencies"
666719
:peer-dependencies-meta="displayVersion?.peerDependenciesMeta"
720+
:optional-dependencies="displayVersion?.optionalDependencies"
667721
/>
668722
</aside>
669723
</div>

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"dependencies": {
2929
"@iconify-json/simple-icons": "^1.2.67",
3030
"@iconify-json/vscode-icons": "^1.2.40",
31+
"@npmcli/arborist": "^9.1.10",
3132
"@nuxt/fonts": "^0.13.0",
3233
"@nuxt/scripts": "^0.13.2",
3334
"@nuxtjs/html-validator": "^2.1.0",
@@ -52,6 +53,7 @@
5253
"@npm/types": "^2.1.0",
5354
"@nuxt/test-utils": "https://pkg.pr.new/@nuxt/test-utils@1499a48",
5455
"@playwright/test": "1.57.0",
56+
"@types/npmcli__arborist": "^6.3.2",
5557
"@types/sanitize-html": "^2.16.0",
5658
"@types/semver": "^7.7.1",
5759
"@unocss/nuxt": "^66.6.0",

0 commit comments

Comments
 (0)