react-native-bundle-discovery-ui 2.8.0 → 2.9.1

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 (40) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +12 -255
  3. package/assets/global.css +1606 -105
  4. package/dist/bin.js +63 -20
  5. package/dist/client/fonts.js +45 -0
  6. package/dist/client/issues.js +5 -0
  7. package/dist/client/pages/_common.js +279 -98
  8. package/dist/client/pages/_module.js +347 -0
  9. package/dist/client/pages/default.js +144 -63
  10. package/dist/client/pages/module.js +2 -309
  11. package/dist/client/pages/moduleDiff.js +76 -0
  12. package/dist/client/pages/package.js +172 -154
  13. package/dist/client/prepare.js +15 -0
  14. package/dist/client/queryHelpers.js +487 -91
  15. package/dist/client/sizeHeat.js +7 -0
  16. package/dist/client/views/{_theme.js → _colorScheme.js} +2 -9
  17. package/dist/client/views/_html.js +52 -0
  18. package/dist/client/views/codeDiff.js +66 -0
  19. package/dist/client/views/compare.js +237 -0
  20. package/dist/client/views/importGraph.js +255 -0
  21. package/dist/client/views/insights.js +81 -0
  22. package/dist/client/views/moduleOverview.js +122 -0
  23. package/dist/client/views/packageOverview.js +116 -0
  24. package/dist/client/views/persistedFilterInput.js +27 -0
  25. package/dist/client/views/reportBar.js +89 -0
  26. package/dist/client/views/treemap.js +598 -0
  27. package/dist/discoveryrc.js +27 -7
  28. package/dist/rsdoctor.js +19 -99
  29. package/dist/src-DV3VzcQY.js +1548 -0
  30. package/package.json +9 -5
  31. package/assets/highcharts.css +0 -1
  32. package/dist/client/_virtual/_rolldown/runtime.js +0 -37
  33. package/dist/client/views/foamtree.js +0 -103
  34. package/dist/client/views/highcharts.js +0 -33
  35. package/dist/src-DPAqywOJ.js +0 -201
  36. package/vendors/foamtree.js +0 -9008
  37. package/vendors/high-contrast-dark.js +0 -1
  38. package/vendors/highcharts-exporting.js +0 -13
  39. package/vendors/highcharts-networkgraph.js +0 -1
  40. package/vendors/highcharts.js +0 -10
@@ -0,0 +1,347 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_pages__common = require("./_common.js");
3
+ //#region src/client/pages/_module.ts
4
+ function getMetroWarn({ when, href, linkText, content }) {
5
+ return {
6
+ view: "alert-warning",
7
+ when,
8
+ className: "m-v-8",
9
+ content: [
10
+ ...content,
11
+ linkText && href
12
+ ? {
13
+ view: "link",
14
+ data: {
15
+ external: true,
16
+ href,
17
+ text: linkText,
18
+ },
19
+ }
20
+ : null,
21
+ ].filter(Boolean),
22
+ };
23
+ }
24
+ function getCodePanes() {
25
+ return {
26
+ view: "hstack",
27
+ className: "flex-no-wrap",
28
+ content: [
29
+ {
30
+ view: "block",
31
+ when: "not isImageOrFontAsset",
32
+ className: "width-50p",
33
+ content: [
34
+ {
35
+ view: "h5",
36
+ className: "mo-pane-title",
37
+ content: [
38
+ "text: 'Source'",
39
+ "text: ' · ' + overview.sourceLines.pluralWithCount(['line', 'lines']) + ' · ' + overview.sourceSize.formatBytes()",
40
+ ],
41
+ },
42
+ {
43
+ view: "source",
44
+ syntax: "ts",
45
+ source: "=$.currentModule.source.code",
46
+ },
47
+ ],
48
+ },
49
+ {
50
+ view: "block",
51
+ className: (data) => (data.isImageOrFontAsset ? "" : "width-50p"),
52
+ content: [
53
+ {
54
+ view: "h5",
55
+ className: "mo-pane-title",
56
+ content: [
57
+ "text: 'Output'",
58
+ "text: ' · ' + overview.outputLines.pluralWithCount(['line', 'lines']) + ' · ' + overview.outputSize.formatBytes()",
59
+ ],
60
+ },
61
+ {
62
+ view: "context",
63
+ modifiers: [
64
+ {
65
+ view: "checkbox",
66
+ name: "prettify",
67
+ checked: true,
68
+ className: "prettify-checkbox",
69
+ content: 'text:"Prettify"',
70
+ },
71
+ ],
72
+ content: [
73
+ {
74
+ view: "source-prettify",
75
+ syntax: "ts",
76
+ source:
77
+ "=(#.prettify ? $.currentModule.output.code.prettifyJS() : $.currentModule.output.code)",
78
+ },
79
+ ],
80
+ },
81
+ ],
82
+ },
83
+ ],
84
+ };
85
+ }
86
+ /** Module page config, shared by the "module" and "module-diff" pages */
87
+ function getModulePage({
88
+ codeTabText = "Code",
89
+ extraData = "",
90
+ codeContent = getCodePanes(),
91
+ } = {}) {
92
+ return {
93
+ view: "context",
94
+ data: `{
95
+ $currentModule: $.modules.[path = #.id].pick(0);
96
+ ...$,
97
+ currentModule: $currentModule,
98
+ overview: $.moduleOverview(#.id),
99
+ isEntry: $currentModule.isEntry,
100
+ isInjectedByMetro: $currentModule.path = '__prelude__' or $currentModule.path has '@react-native/js-polyfills',
101
+ isWebpackRuntime: $currentModule.path has '__runtime__',
102
+ // Keep in sync with IMAGE_FONT_ASSET_RE in packages/serializer
103
+ isImageOrFontAsset: $currentModule.path ~= /\\.(png|jpe?g|gif|webp|bmp|psd|tiff?|ico|heic|avif|ttf|otf|woff2?|eot)$/i,
104
+ ${extraData}
105
+ }`,
106
+ content: [
107
+ {
108
+ view: "block",
109
+ className: "mo-wrap",
110
+ content: [
111
+ {
112
+ view: "module-overview",
113
+ data: "overview",
114
+ },
115
+ require_pages__common.getCopyToClipboardButton({
116
+ textToCopy: `$.currentModule.path`,
117
+ className: "mo-copy",
118
+ }),
119
+ ],
120
+ },
121
+ {
122
+ view: "tabs",
123
+ name: "tabs",
124
+ className: "mo-tabs",
125
+ tabs: [
126
+ {
127
+ value: "mcontent",
128
+ text: codeTabText,
129
+ },
130
+ {
131
+ value: "mimports",
132
+ when: "overview.imports",
133
+ content: ["text:'Imports '", "pill-badge: overview.imports.size()"],
134
+ },
135
+ {
136
+ value: "networkGraph",
137
+ when: "not isInjectedByMetro and not isEntry and not isWebpackRuntime",
138
+ content: ["text:'Imported by '", "pill-badge: overview.importedBy"],
139
+ },
140
+ {
141
+ value: "mduplicates",
142
+ when: "not isInjectedByMetro and not isEntry and not isWebpackRuntime and currentModule.duplicates",
143
+ content: ["text:'Duplicates '", "pill-badge: overview.duplicates"],
144
+ },
145
+ ],
146
+ content: {
147
+ view: "switch",
148
+ content: [
149
+ {
150
+ when: '#.tabs="mcontent"',
151
+ content: [
152
+ getMetroWarn({
153
+ when: "$.currentModule.path = '__prelude__'",
154
+ content: [
155
+ "html: '<b>__prelude__</b>'",
156
+ "text: ' is a special runtime code that was generated by the Metro bundler, see: '",
157
+ ],
158
+ href: "https://github.com/facebook/metro/blob/f595e9e1c9021ad372b489be8958cc298eb1e0d9/packages/metro/src/lib/getPreludeCode.js#L25-L33",
159
+ linkText: "getPreludeCode.js",
160
+ }),
161
+ getMetroWarn({
162
+ when: "$.currentModule.path = '__runtime__'",
163
+ content: [
164
+ "html: '<b>__runtime__</b>'",
165
+ "text: ' is a special runtime code that was generated by the Webpack/Rspack bundler'",
166
+ ],
167
+ }),
168
+ getMetroWarn({
169
+ when: "$.currentModule.path has '@react-native/js-polyfills'",
170
+ content: [
171
+ "html: '<b>@react-native/js-polyfills/*</b>'",
172
+ "text: ' is a special package that was injected by the Metro bundler, see: '",
173
+ ],
174
+ href: "https://github.com/facebook/react-native/blob/198c6fc81cda67a46fff457cc39eb2a297eddc01/packages/metro-config/src/index.flow.js#L61",
175
+ linkText: "rn/metro-config/index.js",
176
+ }),
177
+ getMetroWarn({
178
+ when: "$.currentModule.path has 'metro-runtime'",
179
+ content: [
180
+ "html: '<b>metro-runtime/*</b>'",
181
+ "text: ' is a special package that was injected by the Metro bundler, see: '",
182
+ ],
183
+ href: "https://github.com/facebook/metro/blob/f595e9e1c9021ad372b489be8958cc298eb1e0d9/packages/metro-config/src/defaults/defaults.js#L61-L63",
184
+ linkText: "metro-config/index.js",
185
+ }),
186
+ codeContent,
187
+ ],
188
+ },
189
+ {
190
+ when: '#.tabs="mimports"',
191
+ content: {
192
+ view: "content-filter",
193
+ data: "overview.imports",
194
+ name: "filterByPathStr",
195
+ content: {
196
+ view: "list",
197
+ data: ".[name ~= #.filterByPathStr or specifier ~= #.filterByPathStr]",
198
+ emptyText: "This module doesn't import anything",
199
+ item: {
200
+ view: "block",
201
+ className: "mo-import",
202
+ content: [
203
+ {
204
+ view: "text-match",
205
+ className: "mo-import-specifier",
206
+ data: "{ text: specifier, match: #.filterByPathStr }",
207
+ },
208
+ "html: '<span class=\"mo-arrow\">→</span>'",
209
+ {
210
+ view: "switch",
211
+ content: [
212
+ {
213
+ when: "missing",
214
+ content: [
215
+ "text: name",
216
+ "badge: { text: 'not in bundle', color: 'rgba(128,128,128,.25)' }",
217
+ ],
218
+ },
219
+ {
220
+ content: require_pages__common.getTreeModule({
221
+ hasTextMatch: true,
222
+ }),
223
+ },
224
+ ],
225
+ },
226
+ ],
227
+ },
228
+ },
229
+ },
230
+ },
231
+ {
232
+ when: '#.tabs="networkGraph"',
233
+ content: {
234
+ view: "context",
235
+ context: `{ ...#, maxParentDepth: $.currentModule.getBestNetworkGraphSize() }`,
236
+ modifiers: [
237
+ "html: '<br/>'",
238
+ "text:'Max parent depth: '",
239
+ {
240
+ view: "input",
241
+ name: "maxParentDepth",
242
+ className: "inline-block",
243
+ htmlType: "number",
244
+ htmlMin: 1,
245
+ htmlMax: 10,
246
+ },
247
+ ],
248
+ content: [
249
+ {
250
+ view: "context",
251
+ data: `{
252
+ // Vars
253
+ $tmp: $.currentModule.getNetworkGraph({ maxParentDepth: #.maxParentDepth });
254
+ $limit: 150;
255
+ // Return data
256
+ currentModule: $.currentModule,
257
+ entryPointPath: $tmp.entryPointPath,
258
+ limit: $limit,
259
+ data: $tmp.data,
260
+ graph: { nodes: $tmp.nodes, links: $tmp.links },
261
+ isGraphTooBig: $tmp.data.size() > $limit,
262
+ }`,
263
+ content: [
264
+ {
265
+ when: "isGraphTooBig",
266
+ view: "block",
267
+ className: "m-v-8",
268
+ content: [
269
+ {
270
+ view: "alert-warning",
271
+ data: '"Amount of dependencies "+$.data.size()+" (limit: "+$.limit+") are too big to display as a graph :("',
272
+ },
273
+ ],
274
+ },
275
+ {
276
+ view: "hstack",
277
+ className: "flex-no-wrap two-column",
278
+ content: [
279
+ {
280
+ when: "not isGraphTooBig",
281
+ view: "import-graph",
282
+ className: "ig",
283
+ data: "graph",
284
+ },
285
+ {
286
+ view: "block",
287
+ content: [
288
+ "h4: 'Modules that import this file: ' + $.currentModule.path",
289
+ {
290
+ view: "list",
291
+ data: `
292
+ $totalSize: modules.sum(=>output.sizeInBytes);
293
+ $toModule: => {
294
+ $deepIdx: $$ + 1;
295
+ ext: $.path.getFileExtension(),
296
+ name: $.path,
297
+ size: $.output.sizeInBytes.formatBytes(),
298
+ percent: ($.output.sizeInBytes / $totalSize).percent(3),
299
+ children: $deepIdx < 2 ? $.dependents.map(=> $.$toModule()) : null,
300
+ };
301
+ $.currentModule.dependents.map(=> $.$toModule(0))
302
+ `,
303
+ emptyText: "⚠️ No importing modules found",
304
+ item: {
305
+ view: "tree",
306
+ expanded: false,
307
+ itemConfig: {
308
+ content:
309
+ require_pages__common.getTreeModule({
310
+ hasTextMatch: true,
311
+ }),
312
+ children: `$.children`,
313
+ },
314
+ },
315
+ },
316
+ ],
317
+ },
318
+ ],
319
+ },
320
+ ],
321
+ },
322
+ ],
323
+ },
324
+ },
325
+ {
326
+ when: '#.tabs="mduplicates"',
327
+ content: require_pages__common.getModulesTree({
328
+ data: `
329
+ $totalSize: modules.sum(=>output.sizeInBytes);
330
+ $toModule: => {
331
+ ext: $.path.getFileExtension(),
332
+ name: $.path,
333
+ size: $.output.sizeInBytes.formatBytes(),
334
+ percent: ($.output.sizeInBytes / $totalSize).percent(3),
335
+ };
336
+ currentModule.duplicates.map(=> $.$toModule())
337
+ `,
338
+ }),
339
+ },
340
+ ],
341
+ },
342
+ },
343
+ ],
344
+ };
345
+ }
346
+ //#endregion
347
+ exports.getModulePage = getModulePage;
@@ -1,34 +1,44 @@
1
1
  const require_pages__common = require("./_common.js");
2
2
  //#region src/client/pages/default.ts
3
- const topMetaData = [
4
- require_pages__common.metadata.platform,
5
- require_pages__common.metadata.size,
6
- require_pages__common.metadata.source_code_size,
7
- require_pages__common.metadata.node_modules_size,
8
- require_pages__common.metadata.is_dev,
9
- require_pages__common.metadata.is_minified,
10
- ];
11
3
  const TABS = {
12
- TREEMAP_FOAMTREE: "treemap-foamtree",
4
+ INSIGHTS: "insights",
5
+ TREEMAP: "treemap",
13
6
  MODULES: "modules",
14
7
  PACKAGES: "packages",
15
8
  DUPLICATES: "duplicates",
9
+ COMPARE: "compare",
16
10
  };
17
11
  function parseHashRef(url = window.location.href) {
18
12
  return new URL(url).hash.split(":")?.[1];
19
13
  }
20
14
  discovery.page.define("default", [
21
- ...topMetaData,
15
+ {
16
+ view: "report-bar",
17
+ className: "report-bar",
18
+ data: "$.reportSummary()",
19
+ },
22
20
  {
23
21
  view: "tabs",
24
22
  name: "mainTabs",
25
23
  className: "main-tabs",
26
- value: parseHashRef() ?? TABS.TREEMAP_FOAMTREE,
24
+ value: parseHashRef() ?? TABS.INSIGHTS,
27
25
  tabs: [
28
26
  {
29
- value: TABS.TREEMAP_FOAMTREE,
27
+ value: TABS.INSIGHTS,
28
+ className: `main-tabs-${TABS.INSIGHTS}`,
29
+ content: [
30
+ "text:'Insights '",
31
+ {
32
+ view: "pill-badge",
33
+ when: "recommendations",
34
+ data: "recommendations.size()",
35
+ },
36
+ ],
37
+ },
38
+ {
39
+ value: TABS.TREEMAP,
30
40
  text: "Treemap chart",
31
- className: `main-tabs-${TABS.TREEMAP_FOAMTREE}`,
41
+ className: `main-tabs-${TABS.TREEMAP}`,
32
42
  },
33
43
  {
34
44
  value: TABS.PACKAGES,
@@ -45,12 +55,28 @@ discovery.page.define("default", [
45
55
  },
46
56
  {
47
57
  value: TABS.DUPLICATES,
58
+ when: "modules.filter(=> duplicates)",
48
59
  className: `main-tabs-${TABS.DUPLICATES}`,
49
60
  content: [
50
- "text:'Duplicate modules '",
61
+ "text:'Duplicates '",
51
62
  "pill-badge: modules.filter(=> duplicates).size()",
52
63
  ],
53
64
  },
65
+ {
66
+ value: TABS.COMPARE,
67
+ when: "comparison",
68
+ className: `main-tabs-${TABS.COMPARE}`,
69
+ content: [
70
+ "text:'Compare '",
71
+ {
72
+ view: "pill-badge",
73
+ data: "comparison.summary.deltaInBytes",
74
+ className: (delta) =>
75
+ `cmp-tab-delta-${delta > 0 ? "bad" : delta < 0 ? "good" : "ok"}`,
76
+ text: `=($ > 0 ? '+' : $ < 0 ? '−' : '') + ($ < 0 ? -$ : $).formatBytes()`,
77
+ },
78
+ ],
79
+ },
54
80
  ],
55
81
  content: {
56
82
  view: "switch",
@@ -63,7 +89,7 @@ discovery.page.define("default", [
63
89
  ref: isHashChangeEvent ? discovery.pageRef : context.mainTabs,
64
90
  });
65
91
  discovery.cancelScheduledRender();
66
- const id = discovery.pageRef ?? TABS.TREEMAP_FOAMTREE;
92
+ const id = discovery.pageRef ?? TABS.INSIGHTS;
67
93
  setTimeout(() => {
68
94
  discovery.dom.root
69
95
  .querySelectorAll(".main-tabs>div>.onclick")
@@ -83,28 +109,24 @@ discovery.page.define("default", [
83
109
  },
84
110
  content: [
85
111
  {
86
- when: `#.id="${TABS.TREEMAP_FOAMTREE}"`,
112
+ when: `#.id="${TABS.INSIGHTS}"`,
113
+ content: require_pages__common.getInsightsTab(),
114
+ },
115
+ {
116
+ when: `#.id="${TABS.TREEMAP}"`,
87
117
  content: {
88
118
  view: "content-filter",
89
119
  name: "filterByPathStr",
90
120
  debounce: 300,
91
- className: "foamtree-filter",
121
+ className: "treemap-filter",
92
122
  content: {
93
- view: "foamtree",
123
+ view: "treemap",
94
124
  data: `
95
125
  $root: $.rootFolder;
96
126
  $applyFilter: => #.filterByPathStr ? $.modules.filter(=> $.path ~= #.filterByPathStr) : $.modules;
97
- $dataObject: $.$applyFilter()
98
- .map(=> {path, size: $.output.sizeInBytes})
99
- .transformFilesList($root, "foamtree");
100
-
101
- {
102
- options: {
103
- dataObject: $dataObject,
104
- descriptionGroupSize: 0.05,
105
- descriptionGroupMinHeight: 30,
106
- }
107
- }
127
+ $.$applyFilter()
128
+ .map(=> {path, size: $.output.sizeInBytes, issues})
129
+ .transformFilesList($root, "treemap")
108
130
  `,
109
131
  },
110
132
  },
@@ -113,6 +135,7 @@ discovery.page.define("default", [
113
135
  when: `#.id="${TABS.MODULES}"`,
114
136
  content: require_pages__common.getModulesTree({
115
137
  limit: 200,
138
+ sortable: true,
116
139
  data: `
117
140
  $entryPoint: $.entryPointPath;
118
141
  $totalSize: modules.sum(=>output.sizeInBytes);
@@ -124,58 +147,103 @@ discovery.page.define("default", [
124
147
  };
125
148
  modules.map(=> {
126
149
  ...$.$toModule(),
150
+ sizeInBytes: $.output.sizeInBytes,
127
151
  isEntry: $.isEntry,
128
152
  reasons: $.dependents.map(=> $.$toModule()),
129
153
  duplicates: $.duplicates.map(=> $.$toModule()),
130
- })
154
+ }).withSizeHeat("sizeInBytes")
131
155
  `,
132
156
  }),
133
157
  },
134
158
  {
135
159
  when: `#.id="${TABS.PACKAGES}"`,
136
160
  content: [
137
- require_pages__common.getCopyToClipboardButton({
138
- text: "Copy list",
139
- className: "copy-before-ask-chatgpt",
140
- textToCopy: `"- " + modules.filter(=> path has "node_modules").group(=> path.getModulesName()).map(=> $.key).join("\\n- ")`,
141
- }),
142
- {
143
- view: "link",
144
- className: "ask-chatgpt view-button",
145
- text: "and Ask ChatGPT",
146
- external: true,
147
- data: `{
148
- href: $.askChatGPTAboutPackages()
149
- }`,
150
- },
151
- {
152
- view: "content-filter",
153
- data: `${require_pages__common.getPackage(`modules.filter(=> path has "node_modules")`)}.sort(pkgInstances desc, size desc)`,
161
+ require_pages__common.getSortableContentFilter({
162
+ data: `(${require_pages__common.getPackage(`modules.filter(=> path has "node_modules")`)}).withSizeHeat("size")`,
154
163
  className: "packages-content",
155
- name: "filterByPathStr",
156
- content: require_pages__common.getPackageList({
157
- showCopiesBadge: true,
158
- expanded: false,
159
- limit: 200,
160
- subLimit: 50,
161
- data: ".[pkgName ~= #.filterByPathStr]",
162
- itemPkgName: {
163
- view: "link",
164
- content: "text-match",
165
- data: `{
164
+ nameField: "pkgName",
165
+ sizeField: "size",
166
+ duplicatesCount: "(pkgInstances.size() - 1)",
167
+ defaultSort: "pkgInstances desc, size desc",
168
+ content: (listData) =>
169
+ require_pages__common.getPackageList({
170
+ showCopiesBadge: true,
171
+ expanded: false,
172
+ limit: 200,
173
+ subLimit: 50,
174
+ data: listData,
175
+ itemPkgName: {
176
+ view: "link",
177
+ content: "text-match",
178
+ data: `{
166
179
  href: pkgName.pageLink("package", {}),
167
180
  text: pkgName,
168
181
  match: #.filterByPathStr
169
182
  }`,
170
- },
171
- }),
172
- },
183
+ },
184
+ }),
185
+ }),
173
186
  ],
174
187
  },
175
188
  {
176
189
  when: `#.id="${TABS.DUPLICATES}"`,
177
- content: require_pages__common.getModulesTree({
190
+ content: {
191
+ view: "block",
192
+ className: "tab-scroll",
178
193
  data: `
194
+ $allPackages: (${require_pages__common.getPackage(`modules.filter(=> path has "node_modules")`)});
195
+ {
196
+ ...$,
197
+ duplicatePackages: $allPackages
198
+ .[pkgInstances.size() > 1]
199
+ // Keeping only the heaviest copy saves the rest
200
+ .({ ...$, savings: size - pkgInstances.sort(size desc)[0].size })
201
+ .sort(savings desc),
202
+ }
203
+ `,
204
+ content: [
205
+ {
206
+ view: "h3",
207
+ className: "dup-title",
208
+ content: [
209
+ "text: 'Duplicate packages '",
210
+ "pill-badge: duplicatePackages.size()",
211
+ {
212
+ view: "pill-badge",
213
+ when: "duplicatePackages",
214
+ data: "{ text: '~' + duplicatePackages.sum(=> savings).formatBytes(), postfix: 'can be saved', color: 'rgba(120, 177, 9, 0.35)' }",
215
+ },
216
+ ],
217
+ },
218
+ {
219
+ view: "context",
220
+ when: "duplicatePackages",
221
+ content: require_pages__common.getPackageList({
222
+ showCopiesBadge: true,
223
+ expanded: false,
224
+ data: "duplicatePackages",
225
+ itemPkgName: {
226
+ view: "link",
227
+ data: `{ href: pkgName.pageLink("package", {}), text: pkgName }`,
228
+ },
229
+ }),
230
+ },
231
+ {
232
+ view: "text",
233
+ when: "not duplicatePackages",
234
+ className: "dup-empty",
235
+ data: "'Every package is bundled only once.'",
236
+ },
237
+ {
238
+ view: "h3",
239
+ className: "dup-title",
240
+ content: [
241
+ "text: 'Duplicate modules '",
242
+ "pill-badge: modules.filter(=> duplicates).size()",
243
+ ],
244
+ },
245
+ require_pages__common.getModulesTree({
246
+ data: `
179
247
  // values
180
248
  $duplicatesOnly: modules.filter(=> duplicates);
181
249
  $totalSize: $duplicatesOnly.sum(=>output.sizeInBytes);
@@ -192,7 +260,20 @@ discovery.page.define("default", [
192
260
  duplicates: $.duplicates.map(=> $.$toModule()),
193
261
  })
194
262
  `,
195
- }),
263
+ }),
264
+ ],
265
+ },
266
+ },
267
+ {
268
+ when: `#.id="${TABS.COMPARE}"`,
269
+ content: {
270
+ view: "block",
271
+ className: "tab-scroll compare",
272
+ content: {
273
+ view: "bundle-compare",
274
+ data: "{ comparison, modulePaths: modules.path }",
275
+ },
276
+ },
196
277
  },
197
278
  ],
198
279
  },