react-native-bundle-discovery-ui 2.8.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 +26 -0
  2. package/README.md +12 -255
  3. package/assets/global.css +769 -53
  4. package/dist/bin.js +61 -20
  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 +4 -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-DPAqywOJ.js +0 -201
  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
@@ -1,4 +1,36 @@
1
1
  //#region src/client/queryHelpers.ts
2
+ const HIGH_SEVERITY_SHARE = 0.01;
3
+ const TOP_LIST_SIZE = 8;
4
+ const isNodeModule = (m) => m.path.includes("node_modules/");
5
+ const moduleSize = (m) => m.output.sizeInBytes;
6
+ function toListModule(m) {
7
+ return {
8
+ ext: helpers.getFileExtension(m.path),
9
+ name: m.path,
10
+ size: helpers.formatBytes(moduleSize(m)),
11
+ sizeInBytes: moduleSize(m),
12
+ isEntry: !!m.isEntry,
13
+ };
14
+ }
15
+ function stripVersion(id) {
16
+ const at = id.lastIndexOf("@");
17
+ return at > 0 ? id.slice(0, at) : id;
18
+ }
19
+ function getFindingPackageNames(packages) {
20
+ if (!packages) return [];
21
+ if (typeof packages === "string")
22
+ return [packages.split(/ x\d+:/)[0].trim()].filter(Boolean);
23
+ return Array.from(new Set(packages.map(stripVersion)));
24
+ }
25
+ const modulesByAbsolutePath = /* @__PURE__ */ new WeakMap();
26
+ function getModulesByAbsolutePath(report) {
27
+ let map = modulesByAbsolutePath.get(report);
28
+ if (!map) {
29
+ map = new Map(report.modules.map((m) => [m.absolutePath, m]));
30
+ modulesByAbsolutePath.set(report, map);
31
+ }
32
+ return map;
33
+ }
2
34
  const helpers = {
3
35
  prettifyMap: /* @__PURE__ */ new Map(),
4
36
  prettifyJS(sourceStr) {
@@ -20,16 +52,6 @@ const helpers = {
20
52
  return sourceStr;
21
53
  });
22
54
  },
23
- askChatGPTAboutPackages() {
24
- return `https://chat.openai.com/?prompt=${encodeURIComponent(`I have a list of JavaScript packages from my React Native bundle (see below). I want to optimize bundle size by identifying similar or redundant packages — such as multiple versions of similar libraries (e.g., lodash, lodash-es, underscore, etc.), duplicate utilities, or overlapping functionality (e.g., date libraries like moment, dayjs, date-fns).
25
-
26
- Please do the following:
27
-
28
- 1. Group similar or overlapping packages together.
29
- 2. For each group, suggest which one to keep and which ones to consider removing.
30
- 3. For each group, provide a regex string that can be used to filter those packages from the list (e.g., in grep, find, or search tools).
31
- 4. Keep the output concise and copy-paste friendly.`)}`;
32
- },
33
55
  plural(count, [singular, plural]) {
34
56
  return count === 1 ? singular : plural;
35
57
  },
@@ -79,6 +101,7 @@ Please do the following:
79
101
  const result = [];
80
102
  let entryPointPath = null;
81
103
  const data = [];
104
+ const nodes = [];
82
105
  while (queue.length > 0) {
83
106
  const { module: currentModule, parentId, level } = queue.shift();
84
107
  if (level >= Number(maxParentDepth)) break;
@@ -87,6 +110,15 @@ Please do the following:
87
110
  if (visited.has(id)) continue;
88
111
  visited.add(id);
89
112
  }
113
+ nodes.push({
114
+ id,
115
+ size: currentModule.output.sizeInBytes,
116
+ level,
117
+ isCurrent: level === 0,
118
+ isRoot:
119
+ !!currentModule.isEntry || currentModule.dependents.length === 0,
120
+ isNodeModule: id.includes("node_modules/"),
121
+ });
90
122
  if (parentId) {
91
123
  const isEntryPoint = currentModule.dependents.length === 0;
92
124
  result.push({
@@ -112,9 +144,15 @@ Please do the following:
112
144
  break;
113
145
  }
114
146
  }
147
+ const links = data.map(([parentId, id]) => ({
148
+ source: id,
149
+ target: parentId,
150
+ }));
115
151
  return {
116
152
  entryPointPath,
117
153
  data,
154
+ nodes,
155
+ links,
118
156
  };
119
157
  },
120
158
  getExtColor(extName) {
@@ -163,6 +201,387 @@ Please do the following:
163
201
  i = Math.floor(Math.log(bytes) / Math.log(k));
164
202
  return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
165
203
  },
204
+ moduleOverview(report, id) {
205
+ const module = report.modules.find((m) => m.path === id);
206
+ if (!module) return null;
207
+ const size = (m) => m.output.sizeInBytes;
208
+ const totalSize = report.modules.reduce((acc, m) => acc + size(m), 0);
209
+ const largest = report.modules.reduce(
210
+ (acc, m) => Math.max(acc, size(m)),
211
+ 0,
212
+ );
213
+ const rank =
214
+ report.modules.filter((m) => size(m) > size(module)).length + 1;
215
+ const byAbsolutePath = new Map(
216
+ report.modules.map((m) => [m.absolutePath, m]),
217
+ );
218
+ const slash = module.path.lastIndexOf("/");
219
+ const toModule = (m) => ({
220
+ ext: helpers.getFileExtension(m.path),
221
+ name: m.path,
222
+ size: helpers.formatBytes(size(m)),
223
+ sizeInBytes: size(m),
224
+ isEntry: m.isEntry,
225
+ });
226
+ let pkg = null;
227
+ if (module.path.includes("node_modules/")) {
228
+ const name = helpers.getModulesName(module.path);
229
+ const marker = `node_modules/${name}/`;
230
+ const path = module.path.slice(
231
+ 0,
232
+ module.path.lastIndexOf(marker) + marker.length - 1,
233
+ );
234
+ const info = report.packages.find((p) => p.path === path);
235
+ const files = report.modules.filter((m) => m.path.startsWith(`${path}/`));
236
+ const pkgSize = files.reduce((acc, m) => acc + size(m), 0);
237
+ pkg = {
238
+ name,
239
+ path,
240
+ version: info?.version,
241
+ metadata: info?.metadata,
242
+ size: pkgSize,
243
+ files: files.length,
244
+ share: pkgSize ? size(module) / pkgSize : 0,
245
+ };
246
+ }
247
+ const imports = module.dependencies
248
+ .map((d) => {
249
+ const m = byAbsolutePath.get(d.absolutePath);
250
+ return m
251
+ ? {
252
+ specifier: d.name,
253
+ ...toModule(m),
254
+ missing: false,
255
+ }
256
+ : {
257
+ specifier: d.name,
258
+ ext: helpers.getFileExtension(d.path),
259
+ name: d.path,
260
+ size: "",
261
+ sizeInBytes: -1,
262
+ isEntry: false,
263
+ missing: true,
264
+ };
265
+ })
266
+ .sort((a, b) => b.sizeInBytes - a.sizeInBytes);
267
+ const importerPackages = new Set(
268
+ module.dependents.map((m) =>
269
+ m.path.includes("node_modules/") ? helpers.getModulesName(m.path) : "",
270
+ ),
271
+ );
272
+ return {
273
+ path: module.path,
274
+ dir: module.path.slice(0, slash + 1),
275
+ file: module.path.slice(slash + 1),
276
+ ext: helpers.getFileExtension(module.path),
277
+ isEntry: !!module.isEntry,
278
+ injectedBy:
279
+ module.path === "__runtime__"
280
+ ? "Webpack/Rspack"
281
+ : module.path === "__prelude__" ||
282
+ module.path.includes("@react-native/js-polyfills") ||
283
+ module.path.includes("metro-runtime")
284
+ ? "Metro"
285
+ : null,
286
+ outputSize: size(module),
287
+ sourceSize: module.source.sizeInBytes,
288
+ outputLines: module.output.lineCount,
289
+ sourceLines: module.source.lineCount,
290
+ totalSize,
291
+ bundleShare: totalSize ? size(module) / totalSize : 0,
292
+ largestShare: largest ? size(module) / largest : 0,
293
+ rank,
294
+ modulesCount: report.modules.length,
295
+ pkg,
296
+ imports,
297
+ missingImports: imports.filter((i) => i.missing).length,
298
+ importedBy: module.dependents.length,
299
+ importerPackages: importerPackages.size,
300
+ importedByOwnCode: importerPackages.has(""),
301
+ duplicates: module.duplicates.length,
302
+ duplicatesSize: module.duplicates.reduce((acc, m) => acc + size(m), 0),
303
+ };
304
+ },
305
+ reportSummary(report) {
306
+ let totalSize = 0;
307
+ let nodeModulesSize = 0;
308
+ for (const m of report.modules) {
309
+ totalSize += moduleSize(m);
310
+ if (isNodeModule(m)) nodeModulesSize += moduleSize(m);
311
+ }
312
+ const options = report.transformOptions;
313
+ return {
314
+ platform: options?.platform ?? null,
315
+ dev: typeof options?.dev === "boolean" ? options.dev : null,
316
+ minify: typeof options?.minify === "boolean" ? options.minify : null,
317
+ totalSize,
318
+ sourceSize: totalSize - nodeModulesSize,
319
+ nodeModulesSize,
320
+ date: report.date ?? null,
321
+ };
322
+ },
323
+ bundleInsights(report) {
324
+ const totalSize = report.modules.reduce((acc, m) => acc + moduleSize(m), 0);
325
+ const share = (bytes) => (totalSize ? bytes / totalSize : 0);
326
+ const modulesByPath = new Map(report.modules.map((m) => [m.path, m]));
327
+ const packages = /* @__PURE__ */ new Map();
328
+ let nodeModulesSize = 0;
329
+ const ownModules = [];
330
+ for (const m of report.modules) {
331
+ if (!isNodeModule(m)) {
332
+ ownModules.push(m);
333
+ continue;
334
+ }
335
+ nodeModulesSize += moduleSize(m);
336
+ const name = helpers.getModulesName(m.path);
337
+ const marker = `node_modules/${name}/`;
338
+ const pkg = packages.get(name) ?? {
339
+ size: 0,
340
+ copies: /* @__PURE__ */ new Set(),
341
+ };
342
+ pkg.size += moduleSize(m);
343
+ pkg.copies.add(m.path.slice(0, m.path.lastIndexOf(marker)));
344
+ packages.set(name, pkg);
345
+ }
346
+ const sourceSize = totalSize - nodeModulesSize;
347
+ const largestPackage = Math.max(
348
+ 0,
349
+ ...[...packages.values()].map((p) => p.size),
350
+ );
351
+ const largestOwnModule = Math.max(0, ...ownModules.map(moduleSize));
352
+ const countedModules = /* @__PURE__ */ new Set();
353
+ let potentialSavings = 0;
354
+ const findings = (report.recommendations ?? [])
355
+ .map((finding) => {
356
+ const savings = finding.sizeInBytes ?? 0;
357
+ const affected = (finding.modules ?? [])
358
+ .map((path) => modulesByPath.get(path))
359
+ .filter((m) => !!m)
360
+ .sort((a, b) => moduleSize(b) - moduleSize(a));
361
+ const severity =
362
+ savings && share(savings) >= HIGH_SEVERITY_SHARE
363
+ ? "high"
364
+ : savings
365
+ ? "medium"
366
+ : "low";
367
+ return {
368
+ ...finding,
369
+ severity,
370
+ savings,
371
+ savingsShare: share(savings),
372
+ packageNames: getFindingPackageNames(finding.packages),
373
+ packagesText:
374
+ typeof finding.packages === "string" ? finding.packages : null,
375
+ docsUrls: [finding.docsUrl ?? []].flat(),
376
+ affected: affected.map(toListModule),
377
+ affectedSize: affected.reduce((acc, m) => acc + moduleSize(m), 0),
378
+ };
379
+ })
380
+ .sort((a, b) => b.savings - a.savings);
381
+ for (const finding of findings) {
382
+ if (!finding.savings) continue;
383
+ const overlap = finding.affected
384
+ .filter((m) => countedModules.has(m.name))
385
+ .reduce((acc, m) => acc + m.sizeInBytes, 0);
386
+ potentialSavings += Math.max(finding.savings - overlap, 0);
387
+ for (const m of finding.affected) countedModules.add(m.name);
388
+ }
389
+ const duplicatePackages = [...packages.values()].filter(
390
+ (p) => p.copies.size > 1,
391
+ ).length;
392
+ return {
393
+ platform: report.transformOptions?.platform,
394
+ isDev: report.transformOptions?.dev !== false,
395
+ hasRecommendations: report.recommendations !== null,
396
+ totalSize,
397
+ modulesCount: report.modules.length,
398
+ sourceSize,
399
+ sourceShare: share(sourceSize),
400
+ ownModulesCount: ownModules.length,
401
+ nodeModulesSize,
402
+ nodeModulesShare: share(nodeModulesSize),
403
+ packagesCount: packages.size,
404
+ duplicatePackages,
405
+ duplicateModules: report.modules.filter((m) => m.duplicates.length)
406
+ .length,
407
+ potentialSavings,
408
+ potentialSavingsShare: share(potentialSavings),
409
+ findings,
410
+ findingsWithSavings: findings.filter((f) => f.savings).length,
411
+ topPackages: [...packages.entries()]
412
+ .sort((a, b) => b[1].size - a[1].size)
413
+ .slice(0, TOP_LIST_SIZE)
414
+ .map(([name, pkg]) => ({
415
+ name,
416
+ size: pkg.size,
417
+ share: share(pkg.size),
418
+ bar: largestPackage ? pkg.size / largestPackage : 0,
419
+ copies: pkg.copies.size,
420
+ })),
421
+ topPackagesShare: share(
422
+ [...packages.values()]
423
+ .map((p) => p.size)
424
+ .sort((a, b) => b - a)
425
+ .slice(0, 5)
426
+ .reduce((acc, size) => acc + size, 0),
427
+ ),
428
+ topOwnModules: [...ownModules]
429
+ .sort((a, b) => moduleSize(b) - moduleSize(a))
430
+ .slice(0, TOP_LIST_SIZE)
431
+ .map((m) => ({
432
+ name: m.path,
433
+ size: moduleSize(m),
434
+ share: share(moduleSize(m)),
435
+ bar: largestOwnModule ? moduleSize(m) / largestOwnModule : 0,
436
+ })),
437
+ };
438
+ },
439
+ packageOverview(report, name) {
440
+ const isOwn = (m) =>
441
+ isNodeModule(m) && helpers.getModulesName(m.path) === name;
442
+ const files = report.modules.filter(isOwn);
443
+ if (files.length === 0) return null;
444
+ const marker = `node_modules/${name}/`;
445
+ const instancePath = (m) =>
446
+ m.path.slice(0, m.path.lastIndexOf(marker) + marker.length - 1);
447
+ const instances = /* @__PURE__ */ new Map();
448
+ for (const m of files) {
449
+ const path = instancePath(m);
450
+ instances.set(path, [...(instances.get(path) ?? []), m]);
451
+ }
452
+ const totalSize = report.modules.reduce((acc, m) => acc + moduleSize(m), 0);
453
+ const size = files.reduce((acc, m) => acc + moduleSize(m), 0);
454
+ const packageSizes = /* @__PURE__ */ new Map();
455
+ for (const m of report.modules) {
456
+ if (!isNodeModule(m)) continue;
457
+ const pkgName = helpers.getModulesName(m.path);
458
+ packageSizes.set(
459
+ pkgName,
460
+ (packageSizes.get(pkgName) ?? 0) + moduleSize(m),
461
+ );
462
+ }
463
+ const largestPackage = Math.max(...packageSizes.values());
464
+ const rank = [...packageSizes.values()].filter((s) => s > size).length + 1;
465
+ const copies = [...instances.entries()]
466
+ .map(([path, modules]) => {
467
+ const info = report.packages.find((p) => p.path === path);
468
+ return {
469
+ path,
470
+ version: info?.version,
471
+ metadata: info?.metadata ?? null,
472
+ size: modules.reduce((acc, m) => acc + moduleSize(m), 0),
473
+ files: modules.length,
474
+ };
475
+ })
476
+ .sort((a, b) => b.size - a.size);
477
+ const fileSet = new Set(files);
478
+ const importers = /* @__PURE__ */ new Map();
479
+ for (const m of files)
480
+ for (const dependent of m.dependents) {
481
+ if (fileSet.has(dependent)) continue;
482
+ importers.set(
483
+ dependent,
484
+ (importers.get(dependent) ?? /* @__PURE__ */ new Set()).add(m),
485
+ );
486
+ }
487
+ const importerPackages = new Set(
488
+ [...importers.keys()].map((m) =>
489
+ isNodeModule(m) ? helpers.getModulesName(m.path) : "",
490
+ ),
491
+ );
492
+ const largestFile = files.reduce((a, b) =>
493
+ moduleSize(b) > moduleSize(a) ? b : a,
494
+ );
495
+ const withMetadata = copies.find((c) => c.metadata)?.metadata ?? null;
496
+ const findings = (report.recommendations ?? []).filter((f) =>
497
+ getFindingPackageNames(f.packages).includes(name),
498
+ );
499
+ return {
500
+ name,
501
+ paths: copies.map((c) => c.path),
502
+ versions: Array.from(
503
+ new Set(copies.map((c) => c.version).filter(Boolean)),
504
+ ),
505
+ size,
506
+ bundleShare: totalSize ? size / totalSize : 0,
507
+ largestShare: largestPackage ? size / largestPackage : 0,
508
+ rank,
509
+ packagesCount: packageSizes.size,
510
+ filesCount: files.length,
511
+ largestFile: largestFile.path,
512
+ largestFileSize: moduleSize(largestFile),
513
+ copies,
514
+ copiesSavings: size - copies[0].size,
515
+ deprecated: copies.filter((c) => c.metadata?.deprecated),
516
+ latestVersion: withMetadata?.latestVersion ?? null,
517
+ isOutdated: copies.some((c) => c.metadata && !c.metadata.isLatest),
518
+ publishedAt: withMetadata?.createdAt ?? null,
519
+ importedBy: importers.size,
520
+ importerPackages:
521
+ importerPackages.size - (importerPackages.has("") ? 1 : 0),
522
+ importedByOwnCode: importerPackages.has(""),
523
+ importers: [...importers.entries()]
524
+ .map(([m, imported]) => ({
525
+ ...toListModule(m),
526
+ imports: [...imported].map(toListModule),
527
+ }))
528
+ .sort((a, b) => b.sizeInBytes - a.sizeInBytes),
529
+ findings: findings.map((f) => ({
530
+ id: f.id,
531
+ title: f.title,
532
+ })),
533
+ };
534
+ },
535
+ importChain(report, pkgName) {
536
+ const isTarget = (m) =>
537
+ isNodeModule(m) && helpers.getModulesName(m.path) === pkgName;
538
+ const byAbsolutePath = getModulesByAbsolutePath(report);
539
+ const search = (starts) => {
540
+ const prev = /* @__PURE__ */ new Map();
541
+ const queue = [...starts];
542
+ for (const start of starts) prev.set(start, null);
543
+ while (queue.length) {
544
+ const current = queue.shift();
545
+ if (isTarget(current)) {
546
+ const chain = [];
547
+ for (let m = current; m; m = prev.get(m) ?? null) chain.unshift(m);
548
+ return chain;
549
+ }
550
+ for (const dependency of current.dependencies) {
551
+ const next = byAbsolutePath.get(dependency.absolutePath);
552
+ if (next && !prev.has(next)) {
553
+ prev.set(next, current);
554
+ queue.push(next);
555
+ }
556
+ }
557
+ }
558
+ return null;
559
+ };
560
+ let chain = search(report.modules.filter((m) => m.isEntry));
561
+ let fromEntry = true;
562
+ if (!chain) {
563
+ chain = search(report.modules.filter((m) => m.dependents.length === 0));
564
+ fromEntry = false;
565
+ }
566
+ if (!chain) return null;
567
+ return {
568
+ fromEntry,
569
+ steps: chain.map((m, index) => {
570
+ const pkg = isNodeModule(m) ? helpers.getModulesName(m.path) : null;
571
+ const prevModule = chain[index - 1];
572
+ const prevPkg =
573
+ prevModule && isNodeModule(prevModule)
574
+ ? helpers.getModulesName(prevModule.path)
575
+ : null;
576
+ return {
577
+ ...toListModule(m),
578
+ pkg,
579
+ entersPackage: index > 0 && pkg !== prevPkg ? pkg : null,
580
+ isTarget: index === chain.length - 1,
581
+ };
582
+ }),
583
+ };
584
+ },
166
585
  isPackageImport(moduleName) {
167
586
  return moduleName?.[0] !== ".";
168
587
  },
@@ -175,7 +594,7 @@ Please do the following:
175
594
  children: {},
176
595
  size: 0,
177
596
  };
178
- files.forEach(({ path, size }) => {
597
+ files.forEach(({ path, size, issues }) => {
179
598
  if (path === "__prelude__") path = "node_modules/__prelude__";
180
599
  if (path === "__runtime__") path = "node_modules/__runtime__";
181
600
  const shortPath = path.replace(`${rootFolder}/`, "");
@@ -192,79 +611,26 @@ Please do the following:
192
611
  current[part].size = size;
193
612
  current[part].path = shortPath;
194
613
  current[part].fullPath = path;
614
+ current[part].issues = issues;
195
615
  }
196
616
  current = current[part].children;
197
617
  });
198
618
  });
199
- if (type === "foamtree") {
619
+ if (type === "treemap") {
200
620
  sumSizes(nodeModulesMap);
201
621
  sumSizes(sourceCodeMap);
202
- const sourceCodeGroup = toGroups(sourceCodeMap, "Source Code");
203
- const nodeModulesGroup = nodeModulesMap?.children?.node_modules
204
- ? toGroups(nodeModulesMap.children.node_modules, "node_modules")
205
- : {};
206
- if (!sourceCodeGroup.groups) return nodeModulesGroup;
207
- if (!nodeModulesGroup.groups) return sourceCodeGroup;
208
- const topLevelNode = { groups: [sourceCodeGroup, nodeModulesGroup] };
209
- topLevelNode.weight = topLevelNode.groups.reduce(
210
- (acc, group) => acc + group.weight,
211
- 0,
212
- );
213
- return topLevelNode;
214
- }
215
- if (type === "highcharts-treemap") {
216
- const ROOT_ID_1 = "~";
217
- const ROOT_ID_2 = ".";
218
- return [
219
- {
220
- id: ROOT_ID_1,
221
- name: "node_modules",
222
- },
223
- {
224
- id: ROOT_ID_2,
225
- name: "Source Code",
226
- },
227
- ]
228
- .concat(flattenTree(nodeModulesMap.children.node_modules, ROOT_ID_1))
229
- .concat(flattenTree(sourceCodeMap, ROOT_ID_2));
622
+ const roots = [];
623
+ if (Object.keys(sourceCodeMap.children).length > 0)
624
+ roots.push(toTreemapNode(sourceCodeMap, "Source Code"));
625
+ if (nodeModulesMap.children.node_modules)
626
+ roots.push(
627
+ toTreemapNode(nodeModulesMap.children.node_modules, "node_modules"),
628
+ );
629
+ return roots;
230
630
  }
231
631
  throw new Error(`Unsupported type: ${type}`);
232
632
  },
233
633
  };
234
- function flattenTree(
235
- node,
236
- parentId,
237
- prevWasSkipped = false,
238
- lvl = 0,
239
- result = [],
240
- overrideParentId,
241
- ) {
242
- const nodeChildrenCount = Object.keys(node.children);
243
- for (const key of nodeChildrenCount) {
244
- const childNode = node.children[key];
245
- const childId = `${parentId}/${key}`;
246
- const childrenCount = Object.keys(childNode.children).length;
247
- const hasChildren = childrenCount > 0;
248
- const item = {
249
- id: childId,
250
- name: prevWasSkipped ? parentId : key,
251
- parent: overrideParentId ?? parentId,
252
- };
253
- if (!hasChildren) item.value = childNode.size;
254
- if (lvl === 0) item.color = Highcharts.getOptions().colors[randomInt(0, 9)];
255
- const skipThisNode = nodeChildrenCount.length === 1 && childrenCount === 1;
256
- if (!skipThisNode) result.push(item);
257
- flattenTree(
258
- childNode,
259
- childId,
260
- skipThisNode,
261
- lvl + 1,
262
- result,
263
- skipThisNode ? (overrideParentId ?? parentId) : void 0,
264
- );
265
- }
266
- return result;
267
- }
268
634
  function sumSizes(node) {
269
635
  const isFile = Object.keys(node.children).length === 0;
270
636
  let totalFiles = isFile ? 1 : 0;
@@ -282,24 +648,22 @@ function sumSizes(node) {
282
648
  totalFiles,
283
649
  };
284
650
  }
285
- function toGroups(node, label) {
651
+ function toTreemapNode(node, name) {
286
652
  const keys = Object.keys(node.children);
287
653
  const common = {
288
- label,
289
- weight: node.size,
654
+ name,
655
+ value: node.size,
290
656
  fullPath: node.fullPath,
291
657
  files: node.files,
292
658
  type: node.type,
293
659
  size: helpers.formatBytes(node.size),
660
+ issues: node.issues,
294
661
  };
295
662
  if (keys.length === 0) return common;
296
663
  return Object.assign(common, {
297
- groups: keys.map((key) => toGroups(node.children[key], key)),
664
+ children: keys.map((key) => toTreemapNode(node.children[key], key)),
298
665
  });
299
666
  }
300
- function randomInt(min, max) {
301
- return Math.floor(Math.random() * (max - min + 1)) + min;
302
- }
303
667
  const nm = "node_modules/";
304
668
  function shortenPath(path, nodeModulesMap) {
305
669
  if (path.startsWith(`${nm}.pnpm/`)) path = path.replaceAll(`${nm}.pnpm/`, "");
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region src/client/views/_theme.ts
2
+ //#region src/client/views/_colorScheme.ts
3
3
  function isDark() {
4
4
  const isSystemDarkMode = window?.matchMedia?.(
5
5
  "(prefers-color-scheme: dark)",
@@ -7,12 +7,5 @@ function isDark() {
7
7
  const userMode = localStorage.getItem("discoveryjs:color-scheme");
8
8
  return userMode === "auto" ? isSystemDarkMode : userMode === "dark";
9
9
  }
10
- function doTheming(el) {
11
- const dark = isDark();
12
- el.classList.add("highcharts-light");
13
- el.classList[!dark ? "add" : "remove"]("my-light");
14
- el.classList[dark ? "add" : "remove"]("highcharts-dark");
15
- }
16
- if (isDark()) require("../../../vendors/high-contrast-dark.js");
17
10
  //#endregion
18
- exports.doTheming = doTheming;
11
+ exports.isDark = isDark;
@@ -0,0 +1,52 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/client/views/_html.ts
3
+ function escapeHTML(str) {
4
+ return String(str ?? "").replace(
5
+ /[&<>"']/g,
6
+ (c) =>
7
+ ({
8
+ "&": "&amp;",
9
+ "<": "&lt;",
10
+ ">": "&gt;",
11
+ '"': "&quot;",
12
+ "'": "&#39;",
13
+ })[c],
14
+ );
15
+ }
16
+ function formatBytes(bytes) {
17
+ if (!bytes) return "0 B";
18
+ const units = ["B", "KB", "MB", "GB"];
19
+ const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), 3);
20
+ return `${Number.parseFloat((bytes / 1024 ** i).toFixed(i ? 1 : 0))} ${units[i]}`;
21
+ }
22
+ function formatPercent(value) {
23
+ const pct = value * 100;
24
+ if (pct === 0) return "0%";
25
+ if (pct < 0.01) return "<0.01%";
26
+ return `${pct < 10 ? pct.toFixed(2) : pct.toFixed(1)}%`;
27
+ }
28
+ function plural(count, singular, pluralForm = `${singular}s`) {
29
+ return `${count.toLocaleString()} ${count === 1 ? singular : pluralForm}`;
30
+ }
31
+ function card({ label, value, sub, kind = "", bar }) {
32
+ return `
33
+ <div class="mo-card${kind ? ` mo-card-${kind}` : ""}">
34
+ <div class="mo-card-label">${label}</div>
35
+ <div class="mo-card-value">${value}</div>
36
+ ${sub ? `<div class="mo-card-sub">${sub}</div>` : ""}
37
+ ${bar === void 0 ? "" : `<div class="mo-bar"><div style="width:${Math.max(bar * 100, 1).toFixed(2)}%"></div></div>`}
38
+ </div>`;
39
+ }
40
+ function chip(html, kind = "", href) {
41
+ const cls = `mo-chip${kind ? ` mo-chip-${kind}` : ""}`;
42
+ return href
43
+ ? `<a class="${cls}" href="${escapeHTML(href)}">${html}</a>`
44
+ : `<span class="${cls}">${html}</span>`;
45
+ }
46
+ //#endregion
47
+ exports.card = card;
48
+ exports.chip = chip;
49
+ exports.escapeHTML = escapeHTML;
50
+ exports.formatBytes = formatBytes;
51
+ exports.formatPercent = formatPercent;
52
+ exports.plural = plural;