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
71 changes: 43 additions & 28 deletions app/components/Package/MetricsBadges.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ const hasCjs = computed(() => {
return analysis.value.moduleFormat === 'cjs' || analysis.value.moduleFormat === 'dual'
})

const isWasm = computed(() => {
if (!analysis.value) return false
return analysis.value.moduleFormat === 'wasm'
})

// Types support
const hasTypes = computed(() => {
if (!analysis.value) return false
Expand Down Expand Up @@ -80,35 +85,45 @@ const typesHref = computed(() => {
</TooltipApp>
</li>

<!-- ESM badge (show with X if missing) -->
<li class="contents">
<TooltipApp
:text="isLoading ? '' : hasEsm ? $t('package.metrics.esm') : $t('package.metrics.no_esm')"
strategy="fixed"
>
<TagStatic
tabindex="0"
:variant="hasEsm && !isLoading ? 'default' : 'ghost'"
:classicon="
isLoading ? 'i-svg-spinners:ring-resize ' : hasEsm ? 'i-lucide:check' : 'i-lucide:x'
"
>
ESM
</TagStatic>
</TooltipApp>
</li>
<template v-if="isWasm && !isLoading">
<li class="contents">
<TooltipApp :text="$t('package.metrics.wasm')" strategy="fixed">
<TagStatic tabindex="0" variant="default">WASM</TagStatic>
</TooltipApp>
</li>
</template>

<!-- CJS badge -->
<li v-if="isLoading || hasCjs" class="contents">
<TooltipApp :text="isLoading ? '' : $t('package.metrics.cjs')" strategy="fixed">
<TagStatic
tabindex="0"
:variant="isLoading ? 'ghost' : 'default'"
:classicon="isLoading ? 'i-svg-spinners:ring-resize ' : 'i-lucide:check'"
<template v-else>
<!-- ESM badge (show with X if missing) -->
<li class="contents">
<TooltipApp
:text="isLoading ? '' : hasEsm ? $t('package.metrics.esm') : $t('package.metrics.no_esm')"
strategy="fixed"
>
CJS
</TagStatic>
</TooltipApp>
</li>
<TagStatic
tabindex="0"
:variant="hasEsm && !isLoading ? 'default' : 'ghost'"
:classicon="
isLoading ? 'i-svg-spinners:ring-resize ' : hasEsm ? 'i-lucide:check' : 'i-lucide:x'
"
>
ESM
</TagStatic>
</TooltipApp>
</li>

<!-- CJS badge -->
<li v-if="isLoading || hasCjs" class="contents">
<TooltipApp :text="isLoading ? '' : $t('package.metrics.cjs')" strategy="fixed">
<TagStatic
tabindex="0"
:variant="isLoading ? 'ghost' : 'default'"
:classicon="isLoading ? 'i-svg-spinners:ring-resize ' : 'i-lucide:check'"
>
CJS
</TagStatic>
</TooltipApp>
</li>
</template>
</ul>
</template>
1 change: 1 addition & 0 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@
"esm": "ES Modules supported",
"cjs": "CommonJS supported",
"no_esm": "ES Modules unsupported",
"wasm": "Has WebAssembly",
"types_label": "Types",
"types_included": "Types included",
"types_available": "Types available via {package}",
Expand Down
3 changes: 3 additions & 0 deletions i18n/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1603,6 +1603,9 @@
"no_esm": {
"type": "string"
},
"wasm": {
"type": "string"
},
"types_label": {
"type": "string"
},
Expand Down
7 changes: 6 additions & 1 deletion shared/utils/package-analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Package analysis utilities for detecting module format and TypeScript support
*/

export type ModuleFormat = 'esm' | 'cjs' | 'dual' | 'unknown'
export type ModuleFormat = 'esm' | 'cjs' | 'dual' | 'wasm' | 'unknown'

export type TypesStatus =
| { kind: 'included' }
Expand Down Expand Up @@ -88,6 +88,11 @@ export function detectModuleFormat(pkg: ExtendedPackageJson): ModuleFormat {
return mainIsCJS ? 'dual' : 'esm'
}

const mainIsWASM = pkg.main?.endsWith('.wasm')
if (mainIsWASM) {
return 'wasm'
}

if (hasModule || isTypeModule) {
return 'esm'
}
Expand Down
66 changes: 66 additions & 0 deletions test/nuxt/components/Package/MetricsBadges.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { mockNuxtImport, mountSuspended } from '@nuxt/test-utils/runtime'
import type { VueWrapper } from '@vue/test-utils'
import { ref } from 'vue'
import PackageMetricsBadges from '~/components/Package/MetricsBadges.vue'

const { mockUsePackageAnalysis } = vi.hoisted(() => ({
mockUsePackageAnalysis: vi.fn(),
}))

mockNuxtImport('usePackageAnalysis', () => mockUsePackageAnalysis)

describe('PackageMetricsBadges', () => {
let wrapper: VueWrapper

afterEach(() => wrapper?.unmount())

it('renders the badges', async () => {
mockUsePackageAnalysis.mockReturnValue({
data: ref({ moduleFormat: 'dual', types: { kind: 'included' } }),
status: ref('success'),
})

wrapper = await mountSuspended(PackageMetricsBadges, {
props: { packageName: 'ufo' },
})

const text = wrapper.text()
expect(text).toContain('Types')
expect(text).toContain('ESM')
expect(text).toContain('CJS')
expect(text).not.toContain('WASM')
})

it('renders the wasm label', async () => {
mockUsePackageAnalysis.mockReturnValue({
data: ref({ moduleFormat: 'wasm' }),
status: ref('success'),
})

wrapper = await mountSuspended(PackageMetricsBadges, {
props: { packageName: 'swc-plugin-transform-webpack-context' },
})

const text = wrapper.text()
expect(text).toContain('WASM')
expect(text).not.toContain('ESM')
})

it('does not render the CJS label when no CJS', async () => {
mockUsePackageAnalysis.mockReturnValue({
data: ref({ moduleFormat: 'esm', types: { kind: 'included' } }),
status: ref('success'),
})

wrapper = await mountSuspended(PackageMetricsBadges, {
props: { packageName: '@nuxt/kit' },
})

const text = wrapper.text()
expect(text).toContain('Types')
expect(text).toContain('ESM')
expect(text).not.toContain('CJS')
expect(text).not.toContain('WASM')
})
})
4 changes: 4 additions & 0 deletions test/unit/shared/utils/package-analysis.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ describe('detectModuleFormat', () => {
}),
).toBe('esm')
})

it('detects WASM from main field', () => {
expect(detectModuleFormat({ main: 'main.wasm' })).toBe('wasm')
})
})

describe('detectTypesStatus', () => {
Expand Down
Loading