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.
- package/CHANGELOG.md +32 -0
- package/README.md +12 -227
- package/assets/global.css +769 -53
- package/dist/bin.js +61 -16
- package/dist/client/issues.js +5 -0
- package/dist/client/pages/_common.js +258 -86
- package/dist/client/pages/_module.js +347 -0
- package/dist/client/pages/default.js +145 -62
- package/dist/client/pages/module.js +2 -309
- package/dist/client/pages/moduleDiff.js +76 -0
- package/dist/client/pages/package.js +172 -154
- package/dist/client/prepare.js +15 -0
- package/dist/client/queryHelpers.js +445 -81
- package/dist/client/views/{_theme.js → _colorScheme.js} +2 -9
- package/dist/client/views/_html.js +52 -0
- package/dist/client/views/codeDiff.js +66 -0
- package/dist/client/views/compare.js +237 -0
- package/dist/client/views/importGraph.js +255 -0
- package/dist/client/views/insights.js +80 -0
- package/dist/client/views/moduleOverview.js +134 -0
- package/dist/client/views/packageOverview.js +114 -0
- package/dist/client/views/persistedFilterInput.js +27 -0
- package/dist/client/views/reportBar.js +89 -0
- package/dist/client/views/treemap.js +598 -0
- package/dist/discoveryrc.js +26 -7
- package/dist/rsdoctor.js +5 -101
- package/dist/src-Bp8pPQrQ.js +1545 -0
- package/package.json +5 -3
- package/assets/highcharts.css +0 -1
- package/dist/client/_virtual/_rolldown/runtime.js +0 -37
- package/dist/client/views/foamtree.js +0 -103
- package/dist/client/views/highcharts.js +0 -33
- package/dist/src-BSLX-He2.js +0 -36
- package/vendors/foamtree.js +0 -9008
- package/vendors/high-contrast-dark.js +0 -1
- package/vendors/highcharts-exporting.js +0 -13
- package/vendors/highcharts-networkgraph.js +0 -1
- 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 === "
|
|
619
|
+
if (type === "treemap") {
|
|
200
620
|
sumSizes(nodeModulesMap);
|
|
201
621
|
sumSizes(sourceCodeMap);
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
|
651
|
+
function toTreemapNode(node, name) {
|
|
286
652
|
const keys = Object.keys(node.children);
|
|
287
653
|
const common = {
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
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/
|
|
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.
|
|
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
|
+
"&": "&",
|
|
9
|
+
"<": "<",
|
|
10
|
+
">": ">",
|
|
11
|
+
'"': """,
|
|
12
|
+
"'": "'",
|
|
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;
|