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
package/dist/bin.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.js");
3
- const require_src = require("./src-DPAqywOJ.js");
3
+ const require_src = require("./src-Bp8pPQrQ.js");
4
4
  const require_discoveryrc = require("./discoveryrc.js");
5
5
  let minimist = require("minimist");
6
6
  minimist = require_rolldown_runtime.__toESM(minimist);
@@ -12,8 +12,23 @@ let _discoveryjs_cli = require("@discoveryjs/cli");
12
12
  let chalk = require("chalk");
13
13
  chalk = require_rolldown_runtime.__toESM(chalk);
14
14
  let _discoveryjs_cli_lib_shared_utils_js = require("@discoveryjs/cli/lib/shared/utils.js");
15
+ //#region src/withCompare.ts
16
+ /** Wraps a report expression of the generated discovery.js config with a "before" report diff */
17
+ function withCompare(reportExpression, beforeJsonPath) {
18
+ return beforeJsonPath
19
+ ? `withComparison(${reportExpression}, require("${beforeJsonPath}"), "${beforeJsonPath}")`
20
+ : reportExpression;
21
+ }
22
+ //#endregion
15
23
  //#region src/build.ts
16
- function buildHtmlPage(filePath, output, clean, singleFile, verbose) {
24
+ function buildHtmlPage(
25
+ filePath,
26
+ output,
27
+ clean,
28
+ singleFile,
29
+ verbose,
30
+ compareFilePath,
31
+ ) {
17
32
  if (verbose)
18
33
  console.info(
19
34
  `Building HTML page from file: ${chalk.default.green(filePath)} options: ${JSON.stringify(
@@ -47,6 +62,22 @@ function buildHtmlPage(filePath, output, clean, singleFile, verbose) {
47
62
  console.error(err.message);
48
63
  process.exit(1);
49
64
  }
65
+ let fullCompareJsonPath = null;
66
+ if (compareFilePath) {
67
+ const compareJsonPath = node_path.default.resolve(
68
+ process.cwd(),
69
+ compareFilePath,
70
+ );
71
+ try {
72
+ fullCompareJsonPath = require.resolve(compareJsonPath);
73
+ } catch (err) {
74
+ console.error(
75
+ `❌Error loading file: ${chalk.default.red(compareJsonPath)}\n\n`,
76
+ );
77
+ console.error(err.message);
78
+ process.exit(1);
79
+ }
80
+ }
50
81
  const configFile = node_path.default.resolve(__dirname, "./.tmp.js");
51
82
  if (verbose)
52
83
  console.info(
@@ -55,7 +86,7 @@ function buildHtmlPage(filePath, output, clean, singleFile, verbose) {
55
86
  const transformRcdoctor = node_path.default.join(__dirname, "rsdoctor.js");
56
87
  node_fs.default.writeFileSync(
57
88
  configFile,
58
- `const {transformRSDoctorData, transformEsbuildMetafile} = require("${transformRcdoctor}");
89
+ `const {transformRSDoctorData, transformEsbuildMetafile, withComparison, withRecommendations} = require("${transformRcdoctor}");
59
90
  module.exports = ${JSON.stringify(
60
91
  {
61
92
  ...require_discoveryrc,
@@ -65,11 +96,7 @@ module.exports = ${JSON.stringify(
65
96
  1,
66
97
  ).replace(
67
98
  `"<tmp>"`,
68
- require_src.isRsdoctorReportPath(fullJsonPath)
69
- ? `() => transformRSDoctorData(require("${fullJsonPath}"))`
70
- : require_src.isEsbuildMetafilePath(fullJsonPath)
71
- ? `() => transformEsbuildMetafile(require("${fullJsonPath}"), "${fullJsonPath}")`
72
- : `() => require("${fullJsonPath}")`,
99
+ `() => ${withCompare(require_src.isRsdoctorReportPath(fullJsonPath) ? `withRecommendations(transformRSDoctorData(require("${fullJsonPath}")))` : require_src.isEsbuildMetafilePath(fullJsonPath) ? `withRecommendations(transformEsbuildMetafile(require("${fullJsonPath}"), "${fullJsonPath}"))` : `withRecommendations(require("${fullJsonPath}"))`, fullCompareJsonPath)}`,
73
100
  )};`,
74
101
  );
75
102
  if (verbose)
@@ -122,7 +149,7 @@ module.exports = ${JSON.stringify(
122
149
  }
123
150
  //#endregion
124
151
  //#region src/server.ts
125
- function serve(filePath, port, verbose) {
152
+ function serve(filePath, port, verbose, compareFilePath) {
126
153
  const PORT = process.env.PORT || port;
127
154
  if (verbose)
128
155
  console.info(
@@ -149,6 +176,22 @@ function serve(filePath, port, verbose) {
149
176
  console.error(err.message);
150
177
  process.exit(1);
151
178
  }
179
+ let fullCompareJsonPath = null;
180
+ if (compareFilePath) {
181
+ const compareJsonPath = node_path.default.resolve(
182
+ process.cwd(),
183
+ compareFilePath,
184
+ );
185
+ try {
186
+ fullCompareJsonPath = require.resolve(compareJsonPath);
187
+ } catch (err) {
188
+ console.error(
189
+ `❌Error loading file: ${chalk.default.red(compareJsonPath)}\n\n`,
190
+ );
191
+ console.error(err.message);
192
+ process.exit(1);
193
+ }
194
+ }
152
195
  const configFile = node_path.default.resolve(__dirname, "./.tmp.js");
153
196
  if (verbose)
154
197
  console.info(
@@ -157,7 +200,7 @@ function serve(filePath, port, verbose) {
157
200
  const transformRcdoctor = node_path.default.join(__dirname, "rsdoctor.js");
158
201
  node_fs.default.writeFileSync(
159
202
  configFile,
160
- `const {transformRSDoctorData, transformEsbuildMetafile} = require("${transformRcdoctor}");
203
+ `const {transformRSDoctorData, transformEsbuildMetafile, withComparison, withRecommendations} = require("${transformRcdoctor}");
161
204
  module.exports = ${JSON.stringify(
162
205
  {
163
206
  ...require_discoveryrc,
@@ -167,11 +210,7 @@ module.exports = ${JSON.stringify(
167
210
  1,
168
211
  ).replace(
169
212
  `"<tmp>"`,
170
- require_src.isRsdoctorReportPath(fullJsonPath)
171
- ? `() => transformRSDoctorData(require("${fullJsonPath}"))`
172
- : require_src.isEsbuildMetafilePath(fullJsonPath)
173
- ? `() => transformEsbuildMetafile(require("${fullJsonPath}"), "${fullJsonPath}")`
174
- : `() => require("${fullJsonPath}")`,
213
+ `() => ${withCompare(require_src.isRsdoctorReportPath(fullJsonPath) ? `withRecommendations(transformRSDoctorData(require("${fullJsonPath}")))` : require_src.isEsbuildMetafilePath(fullJsonPath) ? `withRecommendations(transformEsbuildMetafile(require("${fullJsonPath}"), "${fullJsonPath}"))` : `withRecommendations(require("${fullJsonPath}"))`, fullCompareJsonPath)}`,
175
214
  )};`,
176
215
  );
177
216
  if (verbose)
@@ -200,10 +239,10 @@ function printHelp() {
200
239
  console.log(`react-native-bundle-discovery-ui
201
240
 
202
241
  Usage:
203
- react-native-bundle-discovery-ui <file> [port] [--verbose] # <- server can be omitted
204
- react-native-bundle-discovery-ui server <file> [port] [--verbose]
242
+ react-native-bundle-discovery-ui <file> [port] [--compare <file>] [--verbose] # <- server can be omitted
243
+ react-native-bundle-discovery-ui server <file> [port] [--compare <file>] [--verbose]
205
244
 
206
- react-native-bundle-discovery-ui build <file> [--output <path>] [--clean] [--single-file] [--verbose]
245
+ react-native-bundle-discovery-ui build <file> [--compare <file>] [--output <path>] [--clean] [--single-file] [--verbose]
207
246
 
208
247
  Commands:
209
248
  server <file> [port] Run a web server to show a Metro bundler stat report
@@ -215,6 +254,7 @@ Options:
215
254
  -c, --clean Clean output directory before writing build files (default: true)
216
255
  -s, --single-file Output report build as a single HTML file (default: true)
217
256
  -p, --port <port> Port for server command (same as [port], default: 8079)
257
+ --compare <file> A "before" report to compare <file> with (adds the Compare tab)
218
258
  -h, --help Show help
219
259
  `);
220
260
  }
@@ -234,7 +274,7 @@ function main() {
234
274
  p: "port",
235
275
  },
236
276
  boolean: ["verbose", "help", "clean", "single-file"],
237
- string: ["output"],
277
+ string: ["output", "compare"],
238
278
  default: {
239
279
  clean: true,
240
280
  "single-file": true,
@@ -256,7 +296,7 @@ function main() {
256
296
  argv.port ?? (isImplicitServerCommand ? argv._[1] : argv._[2]) ?? 8079;
257
297
  const port = Number(rawPort);
258
298
  if (!Number.isFinite(port)) fail(`Invalid port: ${rawPort}`);
259
- return serve(file, port, Boolean(argv.verbose));
299
+ return serve(file, port, Boolean(argv.verbose), argv.compare);
260
300
  }
261
301
  if (isBuildCommand) {
262
302
  const file = argv._[1];
@@ -267,6 +307,7 @@ function main() {
267
307
  argv.clean,
268
308
  argv["single-file"],
269
309
  Boolean(argv.verbose),
310
+ argv.compare,
270
311
  );
271
312
  }
272
313
  fail(`Unknown command: ${command}`);
@@ -0,0 +1,5 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/client/issues.ts
3
+ const DUPLICATE_ISSUE = "Duplicate module";
4
+ //#endregion
5
+ exports.DUPLICATE_ISSUE = DUPLICATE_ISSUE;
@@ -1,6 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#region src/client/pages/_common.ts
3
- const platformColor = `transformOptions.platform = 'android' ? 'rgba(194, 239, 116, .4)' : 'rgba(119, 31, 218, .4)'`;
4
3
  function getPackage(entry) {
5
4
  return `
6
5
  $packages: $.packages;
@@ -30,6 +29,86 @@ function getPackage(entry) {
30
29
  }),
31
30
  }))`;
32
31
  }
32
+ function renderSortToggleGroup(el, config, options) {
33
+ let current = null;
34
+ const label = document.createElement("div");
35
+ label.className = "view-toggle-group-before";
36
+ label.textContent = "Sort by:";
37
+ el.classList.add("view-toggle-group", "sort-toggle-group");
38
+ el.append(label);
39
+ const toggles = options.map(({ value, text }) => {
40
+ const toggle = document.createElement("div");
41
+ toggle.className = "view-toggle onclick";
42
+ toggle.textContent = text;
43
+ toggle.addEventListener("click", () => {
44
+ current = current === value ? null : value;
45
+ toggles.forEach((t, i) => {
46
+ t.classList.toggle("checked", options[i].value === current);
47
+ });
48
+ config.onChange?.(current, config.name);
49
+ });
50
+ return el.appendChild(toggle);
51
+ });
52
+ config.onInit?.(current, config.name);
53
+ }
54
+ function getSortableContentFilter({
55
+ data,
56
+ className,
57
+ nameField,
58
+ sizeField,
59
+ duplicatesCount,
60
+ defaultSort,
61
+ content,
62
+ }) {
63
+ const filtered = `.[${nameField} ~= #.filterByPathStr]`;
64
+ return {
65
+ view: "block",
66
+ data,
67
+ className: `view-content-filter sortable-content-filter ${className ?? ""}`,
68
+ content: {
69
+ view: "context",
70
+ modifiers: [
71
+ {
72
+ view: renderSortToggleGroup,
73
+ name: "sortBy",
74
+ data: `
75
+ $hasDuplicates: .[${duplicatesCount} > 0];
76
+ [
77
+ { value: 'size', text: 'Size' },
78
+ { value: 'name', text: 'Name' },
79
+ { value: 'duplicates', text: 'Duplicates' },
80
+ ].[value != 'duplicates' or $hasDuplicates]
81
+ `,
82
+ },
83
+ {
84
+ view: "persisted-filter-input",
85
+ persistKey: `${className}:${nameField}`,
86
+ name: "filterByPathStr",
87
+ placeholder: "Filter",
88
+ },
89
+ ],
90
+ content: [
91
+ {
92
+ view: "badge",
93
+ when: `#.filterByPathStr and ${filtered}`,
94
+ className: "filtered-size",
95
+ data: `{ text: 'Filtered size: ' + ${filtered}.sum(=> ${sizeField}).formatBytes(), color: 'rgba(120, 177, 9, 0.35)' }`,
96
+ },
97
+ {
98
+ view: "block",
99
+ className: "content",
100
+ content: content(`
101
+ $filtered: ${filtered};
102
+ #.sortBy = 'name' ? $filtered.sort(${nameField} asc)
103
+ : #.sortBy = 'duplicates' ? $filtered.sort(${duplicatesCount} desc, ${sizeField} desc)
104
+ : #.sortBy = 'size' ? $filtered.sort(${sizeField} desc)
105
+ : ${defaultSort ? `$filtered.sort(${defaultSort})` : "$filtered"}
106
+ `),
107
+ },
108
+ ],
109
+ },
110
+ };
111
+ }
33
112
  const metadataTooltip = {
34
113
  view: "text",
35
114
  when: "metadata.createdAt",
@@ -170,23 +249,19 @@ function getTreeModule({ hasTextMatch = false, hasPercent = false } = {}) {
170
249
  : null,
171
250
  ].filter(Boolean);
172
251
  }
173
- function getModulesTree({ data, limit }) {
252
+ function getModulesTree({ data, limit, sortable }) {
174
253
  if (!data) throw new Error("[getModulesTree]: data is required");
175
- return {
176
- view: "content-filter",
177
- data,
178
- name: "filterByPathStr",
179
- content: {
180
- view: "list",
181
- limit,
182
- data: ".[name ~= #.filterByPathStr]",
183
- emptyText: "⚠️ No modules found",
184
- item: {
185
- view: "tree",
186
- expanded: false,
187
- itemConfig: {
188
- content: getTreeModule({ hasTextMatch: true }),
189
- children: `
254
+ const getList = (listData) => ({
255
+ view: "list",
256
+ limit,
257
+ data: listData,
258
+ emptyText: "⚠️ No modules found",
259
+ item: {
260
+ view: "tree",
261
+ expanded: false,
262
+ itemConfig: {
263
+ content: getTreeModule({ hasTextMatch: true }),
264
+ children: `
190
265
  [
191
266
  {
192
267
  title:'Imported by modules',
@@ -200,86 +275,183 @@ function getModulesTree({ data, limit }) {
200
275
  }
201
276
  ].filter(=> $.data.size() > 0)
202
277
  `,
203
- itemConfig: {
204
- view: "switch",
205
- content: [
206
- {
207
- when: 'type="reasons"',
208
- content: {
278
+ itemConfig: {
279
+ view: "switch",
280
+ content: [
281
+ {
282
+ when: 'type="reasons"',
283
+ content: {
284
+ view: "tree-leaf",
285
+ content: [
286
+ "text:title",
287
+ "text:' '",
288
+ "badge:{ text: $.data.size() }",
289
+ ],
290
+ children: `$.data`,
291
+ itemConfig: {
209
292
  view: "tree-leaf",
210
- content: [
211
- "text:title",
212
- "text:' '",
213
- "badge:{ text: $.data.size() }",
214
- ],
215
- children: `$.data`,
216
- itemConfig: {
217
- view: "tree-leaf",
218
- content: getTreeModule(),
219
- },
293
+ content: getTreeModule(),
220
294
  },
221
295
  },
222
- {
223
- when: 'type="duplicates"',
224
- content: {
296
+ },
297
+ {
298
+ when: 'type="duplicates"',
299
+ content: {
300
+ view: "tree-leaf",
301
+ content: [
302
+ "text:title",
303
+ "text:' '",
304
+ "badge:{ text: $.data.size() }",
305
+ ],
306
+ children: `$.data`,
307
+ itemConfig: {
225
308
  view: "tree-leaf",
226
- content: [
227
- "text:title",
228
- "text:' '",
229
- "badge:{ text: $.data.size() }",
230
- ],
231
- children: `$.data`,
232
- itemConfig: {
233
- view: "tree-leaf",
234
- content: getTreeModule(),
235
- },
309
+ content: getTreeModule(),
236
310
  },
237
311
  },
238
- ],
239
- },
312
+ },
313
+ ],
240
314
  },
241
315
  },
242
316
  },
317
+ });
318
+ if (sortable)
319
+ return getSortableContentFilter({
320
+ data,
321
+ nameField: "name",
322
+ sizeField: "sizeInBytes",
323
+ duplicatesCount: "duplicates.size()",
324
+ content: getList,
325
+ });
326
+ return {
327
+ view: "content-filter",
328
+ data,
329
+ name: "filterByPathStr",
330
+ content: getList(".[name ~= #.filterByPathStr]"),
243
331
  };
244
332
  }
245
- const metadata = {
246
- platform: {
247
- when: "transformOptions.platform",
248
- view: "badge",
249
- data: `{ prefix: 'Platform: ', text: transformOptions.platform, color: ${platformColor} }`,
250
- },
251
- size: {
252
- when: "modules.filter(=> $.path has 'node_modules').size()",
253
- view: "badge",
254
- data: `{ prefix: 'Size: ', text: modules.sum(=>output.sizeInBytes).formatBytes(), color: ${platformColor} }`,
255
- },
256
- node_modules_size: {
257
- when: "modules.filter(=> $.path has 'node_modules').size()",
258
- view: "badge",
259
- data: "{ prefix: 'node_modules: ', text: modules.filter(=> $.path has 'node_modules').sum(=>output.sizeInBytes).formatBytes(), color: 'rgba(255, 0, 0, 0.35)' }",
260
- },
261
- source_code_size: {
262
- when: "modules.filter(=> $.path has 'node_modules').size()",
263
- view: "badge",
264
- data: `
265
- // vars
266
- $totalSize: modules.sum(=>output.sizeInBytes);
267
- $thirdPartySize: modules.filter(=> $.path has 'node_modules').sum(=>output.sizeInBytes);
268
- // return data
269
- { prefix: 'Source code: ', text: ($totalSize - $thirdPartySize).formatBytes(), color: 'rgba(148, 111, 234, 0.5)' }`,
270
- },
271
- is_dev: {
272
- when: "transformOptions.dev != null",
273
- view: "badge",
274
- data: "{ prefix: '__DEV__: ', text: transformOptions.dev }",
275
- },
276
- is_minified: {
277
- when: "transformOptions.minify != null",
278
- view: "badge",
279
- data: "{ prefix: 'Minify: ', text: transformOptions.minify }",
333
+ const findingItem = {
334
+ view: "expand",
335
+ className: "='in-finding in-finding-' + severity",
336
+ header: [
337
+ {
338
+ view: "pill-badge",
339
+ className: "in-severity",
340
+ data: `{
341
+ text: severity = 'high' ? 'High impact' : severity = 'medium' ? 'Saves size' : 'Advice',
342
+ color: severity = 'high' ? 'rgba(220, 50, 60, 0.3)' : severity = 'medium' ? 'rgba(255, 165, 0, 0.35)' : 'rgba(0, 170, 255, 0.25)',
343
+ }`,
344
+ },
345
+ "text: title",
346
+ {
347
+ view: "pill-badge",
348
+ when: "savings",
349
+ data: "{ text: '~' + savings.formatBytes(), postfix: savingsShare.percent(2), color: 'rgba(120, 177, 9, 0.35)' }",
350
+ tooltip: "text: 'Estimated savings: size and share of the bundle'",
351
+ },
352
+ {
353
+ view: "inline-list",
354
+ className: "in-packages",
355
+ when: "packageNames",
356
+ data: "packageNames",
357
+ item: {
358
+ view: "link",
359
+ className: "mo-chip mo-chip-pkg",
360
+ data: `{ href: $.pageLink("package", {}), text: $ }`,
361
+ },
362
+ },
363
+ ],
364
+ content: {
365
+ view: "block",
366
+ className: "in-finding-content",
367
+ content: [
368
+ {
369
+ view: "markdown",
370
+ data: "message",
371
+ },
372
+ {
373
+ view: "block",
374
+ when: "packagesText",
375
+ className: "in-pre",
376
+ content: "text: packagesText",
377
+ },
378
+ {
379
+ view: "expand",
380
+ when: "affected",
381
+ className: "in-affected",
382
+ header: [
383
+ "text: 'Affected modules '",
384
+ "pill-badge: affected.size()",
385
+ "pill-badge: { text: affectedSize.formatBytes(), color: 'rgba(120, 177, 9, 0.35)' }",
386
+ ],
387
+ content: {
388
+ view: "list",
389
+ data: "affected",
390
+ limit: 20,
391
+ item: {
392
+ view: "block",
393
+ content: getTreeModule(),
394
+ },
395
+ },
396
+ },
397
+ {
398
+ view: "block",
399
+ when: "docsUrls",
400
+ className: "in-docs",
401
+ content: [
402
+ "text: 'Learn more: '",
403
+ {
404
+ view: "inline-list",
405
+ data: "docsUrls",
406
+ item: {
407
+ view: "link",
408
+ external: true,
409
+ data: "{ href: $, text: $ }",
410
+ },
411
+ },
412
+ ],
413
+ },
414
+ ],
280
415
  },
281
416
  };
282
- const externalLinkHtml = `<svg class="my-icon my-icon-link" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14 21 3"></path></svg>`;
417
+ function getInsightsTab() {
418
+ return {
419
+ view: "block",
420
+ className: "tab-scroll insights",
421
+ data: "$.bundleInsights()",
422
+ content: [
423
+ { view: "bundle-insights" },
424
+ {
425
+ view: "h3",
426
+ className: "in-title",
427
+ content: [
428
+ "text: 'Recommendations '",
429
+ {
430
+ view: "pill-badge",
431
+ when: "hasRecommendations",
432
+ data: "findings.size()",
433
+ },
434
+ ],
435
+ },
436
+ {
437
+ view: "alert-warning",
438
+ when: "not hasRecommendations",
439
+ content:
440
+ "html: 'Recommendations are only available for production bundles. Generate the report from a bundle built with <code>--dev false</code> to see them.'",
441
+ },
442
+ {
443
+ view: "alert-success",
444
+ when: "hasRecommendations and not findings",
445
+ content: "text: '✅ No recommendations: the bundle looks good!'",
446
+ },
447
+ {
448
+ view: "list",
449
+ data: "findings",
450
+ item: findingItem,
451
+ },
452
+ ],
453
+ };
454
+ }
283
455
  function getCopyToClipboardButton({ textToCopy, className, text }) {
284
456
  return {
285
457
  view: "button",
@@ -302,10 +474,10 @@ function getCopyToClipboardButton({ textToCopy, className, text }) {
302
474
  };
303
475
  }
304
476
  //#endregion
305
- exports.externalLinkHtml = externalLinkHtml;
306
477
  exports.getCopyToClipboardButton = getCopyToClipboardButton;
478
+ exports.getInsightsTab = getInsightsTab;
307
479
  exports.getModulesTree = getModulesTree;
308
480
  exports.getPackage = getPackage;
309
481
  exports.getPackageList = getPackageList;
482
+ exports.getSortableContentFilter = getSortableContentFilter;
310
483
  exports.getTreeModule = getTreeModule;
311
- exports.metadata = metadata;