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 }) => (
))}
);
}