Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<script setup lang="ts">
import type { PackageNode } from 'node-modules-tools'
import { computed } from 'vue'
import { settings } from '../../state/settings'

const props = defineProps<{
pkg: PackageNode
class?: string
}>()

const sideEffects = computed(() => props.pkg.resolved?.sideEffects)

const isShown = computed(() => {
if (!settings.value.showSideEffectsBadge)
return false
return sideEffects.value !== undefined
})

const icon = computed(() => {
if (sideEffects.value === false)
return 'i-ph:tree-structure-duotone text-green-400'
if (sideEffects.value === true)
return 'i-ph:warning-circle-duotone text-amber-400'
// array of file paths
return 'i-ph:warning-circle-duotone text-yellow-400'
})

const description = computed(() => {
if (sideEffects.value === false)
return 'This package has no side effects (safe to tree-shake)'
if (sideEffects.value === true)
return 'This package has side effects (may not be fully tree-shakeable)'
if (Array.isArray(sideEffects.value))
return `This package has side effects in ${sideEffects.value.length} file pattern(s) (partially tree-shakeable)`
return undefined
})
</script>

<template>
<span
v-if="isShown" v-tooltip="description"
:class="[props.class, icon]"
h-1.1em inline-flex align-middle
/>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import DisplayDateBadge from '../display/DateBadge.vue'
import DisplayFileSizeBadge from '../display/FileSizeBadge.vue'
import DisplayModuleType from '../display/ModuleType'
import DisplayPackageSpec from '../display/PackageSpec.vue'
import DisplaySideEffectsBadge from '../display/SideEffectsBadge.vue'
import DisplaySourceTypeBadge from '../display/SourceTypeBadge.vue'
import UiPackageBorder from '../ui/PackageBorder.vue'

Expand Down Expand Up @@ -55,6 +56,8 @@ defineProps<{

<DisplaySourceTypeBadge :pkg />

<DisplaySideEffectsBadge :pkg />

<!--
<span op-mute>Β·</span>
<div op75>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import DisplayDateBadge from '../display/DateBadge.vue'
import DisplayModuleType from '../display/ModuleType'
import DisplayNodeVersionRange from '../display/NodeVersionRange.vue'
import DisplayPackageName from '../display/PackageName.vue'
import DisplaySideEffectsBadge from '../display/SideEffectsBadge.vue'
import DisplaySourceTypeBadge from '../display/SourceTypeBadge.vue'
import DisplaySupplyChainBadge from '../display/SupplyChainBadge.vue'
import DisplayVersionWithUpdates from '../display/VersionWithUpdates.vue'
Expand Down Expand Up @@ -59,6 +60,7 @@ function showDuplicatedGraph(pkgs: PackageNode[]) {
>
<template #after>
<DisplaySupplyChainBadge :pkg class="ml-1" />
<DisplaySideEffectsBadge :pkg class="ml-1" />
</template>
</DisplayPackageName>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const showConfigDialog = ref(false)
:titles="['Present', 'Absent', 'None']"
/>
</OptionItem>
<OptionItem :div="true" title="Show side effects badge" description="Show whether a package has side effects">
<FormCheckbox v-model="settings.showSideEffectsBadge" />
</OptionItem>
<OptionItem :div="true" title="Colorize size badge" description="Colorize package size badge">
<FormCheckbox v-model="settings.colorizePackageSize" />
</OptionItem>
Expand Down
1 change: 1 addition & 0 deletions packages/node-modules-inspector/src/app/state/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const SETTINGS_DEFAULT: SettingsOptions = {
showInstallSizeBadge: true,
showPublishTimeBadge: false,
showSupplyChainBadge: 'present',
showSideEffectsBadge: false,
showFileComposition: false,
showDependencySourceBadge: 'dev',
showPublintMessages: false,
Expand Down
1 change: 1 addition & 0 deletions packages/node-modules-inspector/src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export interface SettingsOptions {
showInstallSizeBadge: boolean
showPublishTimeBadge: boolean
showSupplyChainBadge: 'present' | 'absent' | 'none'
showSideEffectsBadge: boolean
showFileComposition: boolean
showDependencySourceBadge: 'none' | 'dev' | 'prod' | 'both'
treatFauxAsESM: boolean
Expand Down
4 changes: 2 additions & 2 deletions packages/node-modules-tools/src/resolve-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import type { PackageJson } from 'pkg-types'
import type { PackageNode } from './types'
import { objectPick } from '@antfu/utils'
import { analyzePackageModuleType } from './analyze-esm'
import { normalizePkgAuthors, normalizePkgFundings, normalizePkgLicense, normalizePkgRepository } from './utils/package-json'

import { normalizePkgAuthors, normalizePkgFundings, normalizePkgLicense, normalizePkgRepository, normalizePkgSideEffects } from './utils/package-json'
// @keep-unique
// @keep-sorted
export const PACKAGE_JSON_KEYS = [
Expand Down Expand Up @@ -46,5 +45,6 @@ export function resolvePackageJsonFields(json: PackageJson): Omit<PackageNode['r
repository: normalizePkgRepository(json),
license: normalizePkgLicense(json),
fundings: normalizePkgFundings(json),
sideEffects: normalizePkgSideEffects(json),
}
}
8 changes: 8 additions & 0 deletions packages/node-modules-tools/src/types/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ export interface PackageNode extends PackageNodeBase {
license?: ParsedLicense
/** Parsed funding entries with avatars when known */
fundings?: ParsedFunding[]
/***
* Whether the package has side effects.
* - `true` (default): the package has side effects (cannot be tree-shaken)
* - `false`: the package has no side effects (safe to tree-shake)
* - `string[]`: specific file paths that have side effects
* - `undefined`: not declared in package.json
*/
sideEffects?: boolean | string[]
}
}
export type AuditLevelString = 'low' | 'moderate' | 'high' | 'critical'
Expand Down
17 changes: 17 additions & 0 deletions packages/node-modules-tools/src/utils/package-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,23 @@ export interface ParsedRepository {
org?: string
}

/**
* Normalize the `sideEffects` field from package.json.
* Can be a boolean or an array of file paths.
* Returns `undefined` if not declared.
*/

export function normalizePkgSideEffects(json: PackageJson): boolean | string[] | undefined {
const raw = (json as Record<string, unknown>).sideEffects
if (raw === undefined)
return undefined
if (typeof raw === 'boolean')
return raw
if (Array.isArray(raw))
return raw.filter((item): item is string => typeof item === 'string')
return undefined
}

export function normalizePkgRepository(json: PackageJson): ParsedRepository | undefined {
if (!json.repository && !json.bugs)
return undefined
Expand Down