react-native-bundle-discovery-ui 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +12 -227
  3. package/assets/global.css +769 -53
  4. package/dist/bin.js +61 -16
  5. package/dist/client/issues.js +5 -0
  6. package/dist/client/pages/_common.js +258 -86
  7. package/dist/client/pages/_module.js +347 -0
  8. package/dist/client/pages/default.js +145 -62
  9. package/dist/client/pages/module.js +2 -309
  10. package/dist/client/pages/moduleDiff.js +76 -0
  11. package/dist/client/pages/package.js +172 -154
  12. package/dist/client/prepare.js +15 -0
  13. package/dist/client/queryHelpers.js +445 -81
  14. package/dist/client/views/{_theme.js → _colorScheme.js} +2 -9
  15. package/dist/client/views/_html.js +52 -0
  16. package/dist/client/views/codeDiff.js +66 -0
  17. package/dist/client/views/compare.js +237 -0
  18. package/dist/client/views/importGraph.js +255 -0
  19. package/dist/client/views/insights.js +80 -0
  20. package/dist/client/views/moduleOverview.js +134 -0
  21. package/dist/client/views/packageOverview.js +114 -0
  22. package/dist/client/views/persistedFilterInput.js +27 -0
  23. package/dist/client/views/reportBar.js +89 -0
  24. package/dist/client/views/treemap.js +598 -0
  25. package/dist/discoveryrc.js +26 -7
  26. package/dist/rsdoctor.js +5 -101
  27. package/dist/src-Bp8pPQrQ.js +1545 -0
  28. package/package.json +5 -3
  29. package/assets/highcharts.css +0 -1
  30. package/dist/client/_virtual/_rolldown/runtime.js +0 -37
  31. package/dist/client/views/foamtree.js +0 -103
  32. package/dist/client/views/highcharts.js +0 -33
  33. package/dist/src-BSLX-He2.js +0 -36
  34. package/vendors/foamtree.js +0 -9008
  35. package/vendors/high-contrast-dark.js +0 -1
  36. package/vendors/highcharts-exporting.js +0 -13
  37. package/vendors/highcharts-networkgraph.js +0 -1
  38. package/vendors/highcharts.js +0 -10
@@ -0,0 +1,66 @@
1
+ const require_views__html = require("./_html.js");
2
+ let diff = require("diff");
3
+ //#region src/client/views/codeDiff.ts
4
+ const CONTEXT = 3;
5
+ function toLines(before, after) {
6
+ const lines = [];
7
+ let oldNo = 1;
8
+ let newNo = 1;
9
+ (0, diff.diffLines)(before, after).forEach((part) => {
10
+ const kind = part.added ? "add" : part.removed ? "del" : "same";
11
+ part.value
12
+ .replace(/\n$/, "")
13
+ .split("\n")
14
+ .forEach((text) => {
15
+ lines.push({
16
+ kind,
17
+ text,
18
+ oldNo: kind === "add" ? null : oldNo++,
19
+ newNo: kind === "del" ? null : newNo++,
20
+ });
21
+ });
22
+ });
23
+ return lines;
24
+ }
25
+ function lineHTML({ kind, text, oldNo, newNo }) {
26
+ return `<div class="cd-line cd-${kind}"><span class="cd-no">${oldNo ?? ""}</span><span class="cd-no">${newNo ?? ""}</span><span class="cd-sign">${kind === "add" ? "+" : kind === "del" ? "−" : " "}</span><span class="cd-text">${require_views__html.escapeHTML(text)}</span></div>`;
27
+ }
28
+ function render(lines) {
29
+ const added = lines.filter((line) => line.kind === "add").length;
30
+ const removed = lines.filter((line) => line.kind === "del").length;
31
+ if (added + removed === 0)
32
+ return `<div class="cd-empty">No differences</div>`;
33
+ const visible = lines.map((_, i) =>
34
+ lines
35
+ .slice(Math.max(0, i - CONTEXT), i + CONTEXT + 1)
36
+ .some((line) => line.kind !== "same"),
37
+ );
38
+ let html = "";
39
+ let i = 0;
40
+ while (i < lines.length) {
41
+ if (visible[i]) {
42
+ html += lineHTML(lines[i]);
43
+ i++;
44
+ continue;
45
+ }
46
+ const start = i;
47
+ while (i < lines.length && !visible[i]) i++;
48
+ const folded = lines.slice(start, i);
49
+ html += `<button type="button" class="cd-fold">⋯ ${folded.length} unchanged ${folded.length === 1 ? "line" : "lines"}</button><div class="cd-folded" hidden>${folded.map(lineHTML).join("")}</div>`;
50
+ }
51
+ return `
52
+ <div class="cd-stats"><span class="cd-stat-add">+${added}</span> <span class="cd-stat-del">−${removed}</span> lines</div>
53
+ <div class="cd-code">${html}</div>`;
54
+ }
55
+ discovery.view.define("code-diff", async (el, _config, data) => {
56
+ const { before, after } = data;
57
+ const root = el;
58
+ root.innerHTML = render(toLines(await before, await after));
59
+ root.addEventListener("click", (event) => {
60
+ const button = event.target.closest(".cd-fold");
61
+ if (!button) return;
62
+ button.nextElementSibling?.removeAttribute("hidden");
63
+ button.remove();
64
+ });
65
+ });
66
+ //#endregion
@@ -0,0 +1,237 @@
1
+ const require_views__html = require("./_html.js");
2
+ //#region src/client/views/compare.ts
3
+ const ROWS_LIMIT = 10;
4
+ function formatDelta(bytes) {
5
+ if (bytes === 0) return "0 B";
6
+ return `${bytes > 0 ? "+" : "−"}${require_views__html.formatBytes(Math.abs(bytes))}`;
7
+ }
8
+ function deltaHTML(bytes) {
9
+ return `<span class="${bytes > 0 ? "mo-bad" : bytes < 0 ? "mo-good" : ""}">${formatDelta(bytes)}</span>`;
10
+ }
11
+ function change(delta, amount, before, from) {
12
+ const percent = before
13
+ ? ` · ${((Math.abs(delta) / before) * 100).toFixed(2)}%`
14
+ : "";
15
+ return `<span class="cmp-change">${delta === 0 ? `<span class="cmp-pill">= no change</span>` : `<span class="cmp-pill cmp-pill-${delta > 0 ? "up" : "down"}">${delta > 0 ? "▲" : "▼"} ${amount}${percent}</span>`}<span class="cmp-from">from ${from}</span></span>`;
16
+ }
17
+ function countChange(before, after) {
18
+ const delta = after - before;
19
+ return change(
20
+ delta,
21
+ Math.abs(delta).toLocaleString(),
22
+ before,
23
+ before.toLocaleString(),
24
+ );
25
+ }
26
+ function row({ name, href, badge = "", detail = "", delta }) {
27
+ const tag = href ? "a" : "div";
28
+ return `
29
+ <${tag} class="cmp-row"${href ? ` href="${require_views__html.escapeHTML(href)}"` : ""}>
30
+ <span class="cmp-row-name" title="${require_views__html.escapeHTML(name)}">${require_views__html.escapeHTML(name)}${badge}</span>
31
+ <span class="cmp-row-detail">${detail}</span>
32
+ <span class="cmp-row-delta">${deltaHTML(delta)}</span>
33
+ </${tag}>`;
34
+ }
35
+ function section(title, total, rows) {
36
+ if (rows.length === 0) return "";
37
+ const visible = rows.slice(0, ROWS_LIMIT).join("");
38
+ const hidden = rows.slice(ROWS_LIMIT).join("");
39
+ return `
40
+ <div class="in-top cmp-section">
41
+ <div class="mo-card-label">${title} · ${total}</div>
42
+ ${visible}
43
+ ${
44
+ hidden
45
+ ? `<div class="cmp-more" hidden>${hidden}</div>
46
+ <button type="button" class="cmp-more-btn">Show ${rows.length - ROWS_LIMIT} more</button>`
47
+ : ""
48
+ }
49
+ </div>`;
50
+ }
51
+ function sumDelta(items) {
52
+ return items.reduce((sum, item) => sum + item.deltaInBytes, 0);
53
+ }
54
+ function renderCards({ summary, packages }) {
55
+ const { before, after, deltaInBytes } = summary;
56
+ return `<div class="mo-cards">${[
57
+ require_views__html.card({
58
+ label: "Bundle size",
59
+ value: require_views__html.formatBytes(after.sizeInBytes),
60
+ sub: change(
61
+ deltaInBytes,
62
+ require_views__html.formatBytes(Math.abs(deltaInBytes)),
63
+ before.sizeInBytes,
64
+ require_views__html.formatBytes(before.sizeInBytes),
65
+ ),
66
+ kind: deltaInBytes > 0 ? "danger" : deltaInBytes < 0 ? "good" : "accent",
67
+ }),
68
+ require_views__html.card({
69
+ label: "Modules",
70
+ value: after.modules.toLocaleString(),
71
+ sub: countChange(before.modules, after.modules),
72
+ }),
73
+ require_views__html.card({
74
+ label: "Packages",
75
+ value: after.packages.toLocaleString(),
76
+ sub: countChange(before.packages, after.packages),
77
+ }),
78
+ require_views__html.card({
79
+ label: "New duplicates",
80
+ value: require_views__html.plural(
81
+ packages.newDuplicates.length,
82
+ "package",
83
+ ),
84
+ sub: "packages bundled more times than before",
85
+ kind: packages.newDuplicates.length ? "danger" : "",
86
+ }),
87
+ require_views__html.card({
88
+ label: "New deprecated",
89
+ value: require_views__html.plural(
90
+ packages.deprecated.added.length,
91
+ "package",
92
+ ),
93
+ sub: countChange(
94
+ packages.deprecated.beforeCount,
95
+ packages.deprecated.afterCount,
96
+ ),
97
+ kind: packages.deprecated.added.length ? "danger" : "",
98
+ }),
99
+ ].join("")}</div>`;
100
+ }
101
+ function renderWarnings({ transformOptionsDiff, summary }) {
102
+ const items = transformOptionsDiff.map(
103
+ ({ option, before, after }) =>
104
+ `<b>${require_views__html.escapeHTML(option)}</b>: ${require_views__html.escapeHTML(before)} → ${require_views__html.escapeHTML(after)}`,
105
+ );
106
+ if (summary.before.platform !== summary.after.platform)
107
+ items.unshift(
108
+ `<b>platform</b>: ${require_views__html.escapeHTML(summary.before.platform)} → ${require_views__html.escapeHTML(summary.after.platform)}`,
109
+ );
110
+ if (items.length === 0) return "";
111
+ return `
112
+ <div class="cmp-warning">
113
+ ⚠️ The reports were built with different options, so sizes are not directly comparable: ${items.join(", ")}
114
+ </div>`;
115
+ }
116
+ function render({ comparison: c, modulePaths }) {
117
+ const paths = new Set(modulePaths);
118
+ const moduleHref = (path) =>
119
+ paths.has(path) ? discovery.encodePageHash("module", path) : null;
120
+ const packageHref = (name) => discovery.encodePageHash("package", name);
121
+ const versions = (list) => require_views__html.escapeHTML(list.join(", "));
122
+ const contributors = c.biggestContributors.map((item) =>
123
+ row({
124
+ name: item.name,
125
+ detail: item.note ? require_views__html.escapeHTML(item.note) : "",
126
+ delta: item.deltaInBytes,
127
+ }),
128
+ );
129
+ const versionChanges = c.packages.versionChanges.map((pkg) =>
130
+ row({
131
+ name: pkg.name,
132
+ href: packageHref(pkg.name),
133
+ detail: `${versions(pkg.beforeVersions)} → ${versions(pkg.afterVersions)}`,
134
+ delta: pkg.deltaInBytes,
135
+ }),
136
+ );
137
+ const addedPackages = c.packages.added.map((pkg) =>
138
+ row({
139
+ name: pkg.name,
140
+ href: packageHref(pkg.name),
141
+ detail: versions(pkg.versions),
142
+ delta: pkg.sizeInBytes,
143
+ }),
144
+ );
145
+ const removedPackages = c.packages.removed.map((pkg) =>
146
+ row({
147
+ name: pkg.name,
148
+ detail: versions(pkg.versions),
149
+ delta: -pkg.sizeInBytes,
150
+ }),
151
+ );
152
+ const newDuplicates = c.packages.newDuplicates.map((dup) =>
153
+ row({
154
+ name: dup.name,
155
+ href: packageHref(dup.name),
156
+ badge: ` ${require_views__html.chip(`×${dup.count}`, "danger")}`,
157
+ detail: require_views__html.escapeHTML(
158
+ dup.entries.map((entry) => entry.version).join(", "),
159
+ ),
160
+ delta: dup.sizeInBytes,
161
+ }),
162
+ );
163
+ const newDeprecated = c.packages.deprecated.added.map((pkg) => {
164
+ const name = pkg.id.slice(0, pkg.id.lastIndexOf("@"));
165
+ return row({
166
+ name: pkg.id,
167
+ href: packageHref(name),
168
+ badge: ` ${require_views__html.chip("deprecated", "warn")}`,
169
+ detail: require_views__html.escapeHTML(pkg.reason ?? ""),
170
+ delta: 0,
171
+ });
172
+ });
173
+ const addedModules = c.modules.added.map((m) =>
174
+ row({
175
+ name: m.path,
176
+ href: moduleHref(m.path),
177
+ delta: m.deltaInBytes,
178
+ }),
179
+ );
180
+ const removedModules = c.modules.removed.map((m) =>
181
+ row({
182
+ name: m.path,
183
+ delta: m.deltaInBytes,
184
+ }),
185
+ );
186
+ const changedModules = c.modules.changed.map((m) =>
187
+ row({
188
+ name: m.path,
189
+ href: paths.has(m.path)
190
+ ? discovery.encodePageHash("module-diff", m.path)
191
+ : null,
192
+ detail: `${require_views__html.formatBytes(m.beforeSizeInBytes)} → ${require_views__html.formatBytes(m.afterSizeInBytes)}`,
193
+ delta: m.deltaInBytes,
194
+ }),
195
+ );
196
+ const hasChanges =
197
+ c.summary.deltaInBytes !== 0 ||
198
+ c.modules.added.length +
199
+ c.modules.removed.length +
200
+ c.modules.changed.length >
201
+ 0 ||
202
+ c.packages.versionChanges.length +
203
+ c.packages.added.length +
204
+ c.packages.removed.length >
205
+ 0;
206
+ return `
207
+ <div class="cmp-title">
208
+ Compared with ${require_views__html.chip(require_views__html.escapeHTML(c.beforeFile))}
209
+ </div>
210
+ ${renderWarnings(c)}
211
+ ${renderCards(c)}
212
+ ${hasChanges ? "" : `<div class="dup-empty cmp-empty">✅ No differences between the reports</div>`}
213
+ <div class="cmp-sections">
214
+ ${section("Biggest growth", "by package / folder", contributors)}
215
+ ${section("Package version changes", require_views__html.plural(versionChanges.length, "package"), versionChanges)}
216
+ ${section("New duplicates", require_views__html.plural(newDuplicates.length, "package"), newDuplicates)}
217
+ ${section("New deprecated packages", require_views__html.plural(newDeprecated.length, "package"), newDeprecated)}
218
+ ${section("Added packages", `${require_views__html.plural(addedPackages.length, "package")} · ${formatDelta(c.packages.added.reduce((sum, pkg) => sum + pkg.sizeInBytes, 0))}`, addedPackages)}
219
+ ${section("Removed packages", `${require_views__html.plural(removedPackages.length, "package")} · ${formatDelta(-c.packages.removed.reduce((sum, pkg) => sum + pkg.sizeInBytes, 0))}`, removedPackages)}
220
+ ${section("Added modules", `${require_views__html.plural(addedModules.length, "module")} · ${formatDelta(sumDelta(c.modules.added))}`, addedModules)}
221
+ ${section("Removed modules", `${require_views__html.plural(removedModules.length, "module")} · ${formatDelta(sumDelta(c.modules.removed))}`, removedModules)}
222
+ ${section("Changed modules", `${require_views__html.plural(changedModules.length, "module")} · net ${formatDelta(sumDelta(c.modules.changed))}`, changedModules)}
223
+ </div>`;
224
+ }
225
+ discovery.view.define("bundle-compare", (el, _config, data) => {
226
+ const viewData = data;
227
+ if (!viewData?.comparison) return;
228
+ const root = el;
229
+ root.innerHTML = render(viewData);
230
+ root.addEventListener("click", (event) => {
231
+ const button = event.target.closest(".cmp-more-btn");
232
+ if (!button) return;
233
+ button.previousElementSibling?.removeAttribute("hidden");
234
+ button.remove();
235
+ });
236
+ });
237
+ //#endregion
@@ -0,0 +1,255 @@
1
+ const require_views__colorScheme = require("./_colorScheme.js");
2
+ const require_views__html = require("./_html.js");
3
+ let d3_hierarchy = require("d3-hierarchy");
4
+ //#region src/client/views/importGraph.ts
5
+ const SVG_NS = "http://www.w3.org/2000/svg";
6
+ const ROW_HEIGHT = 24;
7
+ const LEVEL_GAP = 220;
8
+ const MIN_HEIGHT = 240;
9
+ const MAX_HEIGHT = 640;
10
+ const MIN_RADIUS = 4;
11
+ const LABEL_WIDTH = 180;
12
+ const MIN_SCALE = 0.2;
13
+ const MAX_SCALE = 6;
14
+ const COLORS = {
15
+ current: "#e5484d",
16
+ root: "#f5b700",
17
+ own: "#8e6fe0",
18
+ nodeModule: "#2b9fd8",
19
+ };
20
+ function nodeColor(node) {
21
+ if (node.isCurrent) return COLORS.current;
22
+ if (node.isRoot) return COLORS.root;
23
+ return node.isNodeModule ? COLORS.nodeModule : COLORS.own;
24
+ }
25
+ function basename(path) {
26
+ return path.slice(path.lastIndexOf("/") + 1);
27
+ }
28
+ function svg(tag, attrs = {}) {
29
+ const el = document.createElementNS(SVG_NS, tag);
30
+ for (const [key, value] of Object.entries(attrs))
31
+ el.setAttribute(key, String(value));
32
+ return el;
33
+ }
34
+ function legend() {
35
+ return `${[
36
+ [COLORS.current, "Current module"],
37
+ [COLORS.root, "Entry point"],
38
+ [COLORS.own, "Your code"],
39
+ [COLORS.nodeModule, "node_modules"],
40
+ ]
41
+ .map(
42
+ ([color, text]) =>
43
+ `<span><span class="ig-dot" style="background:${color}"></span>${text}</span>`,
44
+ )
45
+ .join(
46
+ "",
47
+ )}<span class="ig-hint">Arrows point to the imported module · scroll to zoom · drag to pan · click to open</span>`;
48
+ }
49
+ function layout(data) {
50
+ const parentOf = new Map(data.links.map((l) => [l.source, l.target]));
51
+ const root = (0, d3_hierarchy.stratify)()
52
+ .id((d) => d.id)
53
+ .parentId((d) => parentOf.get(d.id))(data.nodes)
54
+ .sort((a, b) => b.data.size - a.data.size);
55
+ return (0, d3_hierarchy.tree)().nodeSize([ROW_HEIGHT, LEVEL_GAP])(root);
56
+ }
57
+ function createGraph(host, data) {
58
+ const dark = require_views__colorScheme.isDark();
59
+ const points = layout(data).descendants();
60
+ const maxSize = Math.max(1, ...points.map((p) => p.data.size));
61
+ const radius = (p) => MIN_RADIUS + Math.sqrt(p.data.size / maxSize) * 7;
62
+ const minX = Math.min(...points.map((p) => p.x)) - ROW_HEIGHT;
63
+ const maxX = Math.max(...points.map((p) => p.x)) + ROW_HEIGHT;
64
+ const maxY = Math.max(...points.map((p) => p.y)) + LABEL_WIDTH;
65
+ const height = Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, maxX - minX));
66
+ host.innerHTML = `<div class="ig-legend">${legend()}</div>`;
67
+ const rootEl = svg("svg", {
68
+ class: "ig-svg",
69
+ height,
70
+ });
71
+ const defs = svg("defs");
72
+ const marker = svg("marker", {
73
+ id: "ig-arrow",
74
+ viewBox: "0 -4 8 8",
75
+ refX: 8,
76
+ refY: 0,
77
+ markerWidth: 7,
78
+ markerHeight: 7,
79
+ orient: "auto",
80
+ });
81
+ marker.append(
82
+ svg("path", {
83
+ d: "M0,-4L8,0L0,4",
84
+ class: "ig-arrow",
85
+ }),
86
+ );
87
+ defs.append(marker);
88
+ const viewport = svg("g");
89
+ const linksLayer = svg("g", { class: "ig-links" });
90
+ const nodesLayer = svg("g", { class: "ig-nodes" });
91
+ viewport.append(linksLayer, nodesLayer);
92
+ rootEl.append(defs, viewport);
93
+ const tooltip = document.createElement("div");
94
+ tooltip.className = "treemap-tooltip";
95
+ host.append(rootEl, tooltip);
96
+ const linkEls = /* @__PURE__ */ new Map();
97
+ for (const point of points) {
98
+ const parent = point.parent;
99
+ if (!parent) continue;
100
+ const startY = point.y - radius(point);
101
+ const endY = parent.y + radius(parent) + 2;
102
+ const midY = (startY + endY) / 2;
103
+ const path = svg("path", {
104
+ d: `M${startY},${point.x}C${midY},${point.x} ${midY},${parent.x} ${endY},${parent.x}`,
105
+ "marker-end": "url(#ig-arrow)",
106
+ });
107
+ linksLayer.append(path);
108
+ linkEls.set(point, path);
109
+ }
110
+ const nodeEls = /* @__PURE__ */ new Map();
111
+ const pointByEl = /* @__PURE__ */ new Map();
112
+ for (const point of points) {
113
+ const group = svg("g", {
114
+ class: "ig-node",
115
+ transform: `translate(${point.y},${point.x})`,
116
+ });
117
+ group.append(
118
+ svg("circle", {
119
+ r: radius(point),
120
+ fill: nodeColor(point.data),
121
+ stroke: dark ? "#111" : "#fff",
122
+ "stroke-width": 1.5,
123
+ }),
124
+ );
125
+ const label = svg("text", {
126
+ x: radius(point) + 4,
127
+ y: 4,
128
+ });
129
+ label.textContent = basename(point.data.id);
130
+ group.append(label);
131
+ nodesLayer.append(group);
132
+ nodeEls.set(point, group);
133
+ pointByEl.set(group, point);
134
+ }
135
+ const width = host.clientWidth;
136
+ let scale = Math.min(
137
+ 1,
138
+ Math.max(MIN_SCALE, Math.min(width / maxY, height / (maxX - minX))),
139
+ );
140
+ let tx = 22 * scale;
141
+ let ty = (height - (maxX - minX) * scale) / 2 - minX * scale;
142
+ function applyTransform() {
143
+ viewport.setAttribute(
144
+ "transform",
145
+ `translate(${tx},${ty}) scale(${scale})`,
146
+ );
147
+ }
148
+ applyTransform();
149
+ function highlight(point) {
150
+ rootEl.classList.toggle("ig-has-hover", !!point);
151
+ const active = new Set(
152
+ point ? [...point.ancestors(), ...(point.children ?? [])] : [],
153
+ );
154
+ for (const [p, el] of nodeEls)
155
+ el.classList.toggle("ig-active", active.has(p));
156
+ for (const [p, el] of linkEls)
157
+ el.classList.toggle(
158
+ "ig-active",
159
+ active.has(p) && !!p.parent && active.has(p.parent),
160
+ );
161
+ }
162
+ function moveTooltip(event) {
163
+ const box = host.getBoundingClientRect();
164
+ const x = event.clientX - box.left;
165
+ const y = event.clientY - box.top;
166
+ tooltip.style.left = `${Math.max(0, Math.min(x + 12, box.width - tooltip.offsetWidth - 4))}px`;
167
+ tooltip.style.top = `${y + 16}px`;
168
+ }
169
+ let drag = null;
170
+ for (const [point, el] of nodeEls) {
171
+ el.addEventListener("pointerenter", (event) => {
172
+ if (drag) return;
173
+ highlight(point);
174
+ const via = point.depth - 1;
175
+ tooltip.innerHTML = [
176
+ `<b>${require_views__html.escapeHTML(point.data.id)}</b>`,
177
+ `<b>Size</b>: ${require_views__html.formatBytes(point.data.size)}`,
178
+ point.data.isCurrent
179
+ ? "Current module"
180
+ : via === 0
181
+ ? "Imports it directly"
182
+ : `Imports it via ${via} ${via === 1 ? "module" : "modules"}`,
183
+ ].join("<br/>");
184
+ tooltip.style.display = "block";
185
+ moveTooltip(event);
186
+ });
187
+ el.addEventListener("pointermove", moveTooltip);
188
+ el.addEventListener("pointerleave", () => {
189
+ highlight(null);
190
+ tooltip.style.display = "none";
191
+ });
192
+ }
193
+ rootEl.addEventListener("pointerdown", (event) => {
194
+ const nodeEl = event.target.closest(".ig-node");
195
+ drag = {
196
+ x: event.clientX,
197
+ y: event.clientY,
198
+ moved: false,
199
+ point: nodeEl ? pointByEl.get(nodeEl) : void 0,
200
+ };
201
+ rootEl.setPointerCapture(event.pointerId);
202
+ });
203
+ rootEl.addEventListener("pointermove", (event) => {
204
+ if (!drag) return;
205
+ const dx = event.clientX - drag.x;
206
+ const dy = event.clientY - drag.y;
207
+ if (!drag.moved && Math.abs(dx) + Math.abs(dy) < 3) return;
208
+ drag.moved = true;
209
+ drag.x = event.clientX;
210
+ drag.y = event.clientY;
211
+ tx += dx;
212
+ ty += dy;
213
+ tooltip.style.display = "none";
214
+ applyTransform();
215
+ });
216
+ rootEl.addEventListener("pointerup", () => {
217
+ const current = drag;
218
+ drag = null;
219
+ if (current?.point && !current.moved && !current.point.data.isCurrent)
220
+ window.location.hash = discovery.encodePageHash(
221
+ "module",
222
+ current.point.data.id,
223
+ );
224
+ });
225
+ rootEl.addEventListener(
226
+ "wheel",
227
+ (event) => {
228
+ event.preventDefault();
229
+ const next = Math.min(
230
+ MAX_SCALE,
231
+ Math.max(MIN_SCALE, scale * Math.exp(-event.deltaY * 0.002)),
232
+ );
233
+ const box = rootEl.getBoundingClientRect();
234
+ const x = event.clientX - box.left;
235
+ const y = event.clientY - box.top;
236
+ tx = x - ((x - tx) * next) / scale;
237
+ ty = y - ((y - ty) * next) / scale;
238
+ scale = next;
239
+ applyTransform();
240
+ },
241
+ { passive: false },
242
+ );
243
+ }
244
+ discovery.view.define("import-graph", (el, _config, data) => {
245
+ const host = el;
246
+ const graph = data;
247
+ if (!graph?.nodes?.length) return;
248
+ const observer = new ResizeObserver(() => {
249
+ if (!host.isConnected || host.clientWidth === 0) return;
250
+ observer.disconnect();
251
+ createGraph(host, graph);
252
+ });
253
+ observer.observe(host);
254
+ });
255
+ //#endregion
@@ -0,0 +1,80 @@
1
+ const require_views__html = require("./_html.js");
2
+ //#region src/client/views/insights.ts
3
+ function topList(title, items, page, badge) {
4
+ if (items.length === 0) return "";
5
+ return `
6
+ <div class="in-top">
7
+ <div class="mo-card-label">${title}</div>
8
+ ${items
9
+ .map(
10
+ (item) => `
11
+ <a class="in-row" href="${require_views__html.escapeHTML(discovery.encodePageHash(page, item.name))}">
12
+ <span class="in-row-name" title="${require_views__html.escapeHTML(item.name)}">${require_views__html.escapeHTML(item.name)}${badge ? badge(item) : ""}</span>
13
+ <span class="in-row-size">${require_views__html.formatBytes(item.size)}</span>
14
+ <span class="in-row-share">${require_views__html.formatPercent(item.share)}</span>
15
+ <span class="in-row-bar"><span style="width:${Math.max(item.bar * 100, 1).toFixed(2)}%"></span></span>
16
+ </a>`,
17
+ )
18
+ .join("")}
19
+ </div>`;
20
+ }
21
+ function renderCards(o) {
22
+ const savingsSub = !o.hasRecommendations
23
+ ? "production report needed"
24
+ : o.potentialSavings
25
+ ? `${require_views__html.formatPercent(o.potentialSavingsShare)} of bundle · ${require_views__html.plural(o.findingsWithSavings, "fix", "fixes")}`
26
+ : "nothing to remove";
27
+ return `<div class="mo-cards">${[
28
+ require_views__html.card({
29
+ label: "Bundle size",
30
+ value: require_views__html.formatBytes(o.totalSize),
31
+ sub: [o.platform, require_views__html.plural(o.modulesCount, "module")]
32
+ .filter(Boolean)
33
+ .join(" · "),
34
+ kind: "accent",
35
+ }),
36
+ require_views__html.card({
37
+ label: "Your code",
38
+ value: require_views__html.formatBytes(o.sourceSize),
39
+ sub: `${require_views__html.formatPercent(o.sourceShare)} of bundle · ${require_views__html.plural(o.ownModulesCount, "module")}`,
40
+ bar: o.sourceShare,
41
+ }),
42
+ require_views__html.card({
43
+ label: "node_modules",
44
+ value: require_views__html.formatBytes(o.nodeModulesSize),
45
+ sub: `${require_views__html.formatPercent(o.nodeModulesShare)} of bundle · ${require_views__html.plural(o.packagesCount, "package")}`,
46
+ bar: o.nodeModulesShare,
47
+ }),
48
+ require_views__html.card({
49
+ label: "Duplicates",
50
+ value: require_views__html.plural(o.duplicatePackages, "package"),
51
+ sub: `${require_views__html.plural(o.duplicateModules, "module")} with copies`,
52
+ kind: o.duplicatePackages ? "danger" : "",
53
+ }),
54
+ require_views__html.card({
55
+ label: "Potential savings",
56
+ value: o.hasRecommendations
57
+ ? `~${require_views__html.formatBytes(o.potentialSavings)}`
58
+ : "–",
59
+ sub: savingsSub,
60
+ kind: o.potentialSavings ? "good" : "",
61
+ }),
62
+ ].join("")}</div>`;
63
+ }
64
+ function renderTopLists(o) {
65
+ const copiesBadge = (pkg) =>
66
+ pkg.copies > 1
67
+ ? ` <span class="mo-chip mo-chip-danger">×${pkg.copies}</span>`
68
+ : "";
69
+ return `
70
+ <div class="in-tops">
71
+ ${topList(`Heaviest packages · top 5 = ${require_views__html.formatPercent(o.topPackagesShare)} of bundle`, o.topPackages, "package", copiesBadge)}
72
+ ${topList("Heaviest modules in your code", o.topOwnModules, "module")}
73
+ </div>`;
74
+ }
75
+ discovery.view.define("bundle-insights", (el, _config, data) => {
76
+ const insights = data;
77
+ if (!insights) return;
78
+ el.innerHTML = renderCards(insights) + renderTopLists(insights);
79
+ });
80
+ //#endregion