import { hierarchy, treemap } from 'd3-hierarchy'; import React, { useEffect, useState } from 'react'; import { ExternalLink, Pane, QueryLink, Section, Tag, Tags } from './Inspector'; import { npmsioResponse } from './types'; import { $, fetchJSON, human, simplur } from './util'; import '/css/ModulePane.scss'; function ScoreBar({ title, score, style }) { const perc = (score * 100).toFixed(0) + '%'; const inner = (
{perc}
); return ( <> {title}
{inner}
); } function TreeMap({ data, style, ...props }) { const [leaves, setLeaves] = useState([]); // Render contents as an "effect" because d3 requires the pixel dimensions of the div useEffect(() => { const { clientWidth: w, clientHeight: h } = $('#treemap')[0], m = 1; const { size } = data; // eslint-disable-next-line no-undef const root = hierarchy(data, ({ dependencySizes: nodes }) => { if (!nodes) return; const sum = nodes?.reduce((sum, n) => sum + n.approximateSize, 0); // Normalize sizes to match total package size // See https://github.com/pastelsky/bundlephobia/issues/417 nodes.forEach(node => (node.approximateSize *= size / sum)); // Combine dependencies that are < 1% of bundle size const MIN_SIZE = sum * 0.01; const misc = nodes.filter(n => n.approximateSize < MIN_SIZE); nodes = nodes.filter(n => n.approximateSize >= MIN_SIZE); if (misc.length == 1) { nodes.push(misc[0]); } else if (misc.length > 1) { nodes.push({ name: `${misc.length} small modules`, approximateSize: misc.reduce((sum, n) => sum + n.approximateSize, 0), }); } nodes.sort((a, b) => b.approximateSize - a.approximateSize); return nodes; }) .sum(v => v.approximateSize) .sort((a, b) => b.value - a.value); // eslint-disable-next-line no-undef treemap().size([w, h]).padding(0)(root); setLeaves( root.leaves().map((d, i, a) => { const size = human(d.value, 'B'); const frac = ((d.x1 - d.x0) * (d.y1 - d.y0)) / (w * h); return (
{d.data.name} {size}
); }) ); }, [data]); return (
{leaves}
); } export default function ModulePane({ module, ...props }) { const pkg = module?.package; const [bundleInfo, setBundleInfo] = useState(null); const [npmsInfo, setNpmsInfo] = useState(null); const pn = pkg ? encodeURIComponent(`${pkg.name}@${pkg.version}`) : null; useEffect(() => { setBundleInfo(pkg ? null : Error('No package selected')); setNpmsInfo(null); if (!pkg) return; fetchJSON(`https://bundlephobia.com/api/size?package=${pn}`) .then(setBundleInfo) .catch(setBundleInfo); fetchJSON(`https://api.npms.io/v2/package/${pkg.name}`) .then(search => setNpmsInfo(search.score)) .catch(setNpmsInfo); }, [pkg]); if (!pkg) { return ( No module selected. Click a module in the graph to see details. ); } if (pkg.stub) { return (

{module.name}

Information and dependencies for this module cannot be displayed due to the following error:

{pkg.error?.message}

); } const bpUrl = `https://bundlephobia.com/result?p=${pn}`; const scores = npmsInfo?.detail || {}; if (npmsInfo) scores.final = npmsInfo.final; function BundleStats({ bundleInfo }) { return (
Minified: {human(bundleInfo.size, 'B')} Gzipped: {human(bundleInfo.gzip, 'B')}
); } return (

{pkg.deprecated ? (

Deprecated Module

{pkg.deprecated}
) : null}

{pkg?.description}

npm {module.repoLink ? ( GitHub ) : null} { // Displaying dropped package contents is a bit problematic, but we give it a shot here. module.package?._dropped ? ( package.json ) : ( package.json ) }
{bundleInfo ? : null} {!bundleInfo ? ( Loading ... ) : bundleInfo instanceof Error ? ( Unavailable ) : ( )} {bundleInfo && !(bundleInfo instanceof Error) ? ( <> Data source: BundlePhobia ) : null}
{!npmsInfo ? ( 'Loading' ) : npmsInfo instanceof Error ? ( 'Unavailable' ) : (
)}
{pkg.maintainers.map(({ name, email }) => ( ))}
); }