react-native-bundle-discovery-ui 2.3.0 → 2.5.0-rc.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.
package/README.md CHANGED
@@ -24,7 +24,6 @@ There are two ways to install the package:
24
24
  1. As in independent tool (UI + CLI)
25
25
  2. Or as a [Rozenite](_rozenite/README.md) plugin (see: [_rozenite/README.md](_rozenite/README.md))
26
26
 
27
-
28
27
  > 🚀 If you've migrated to [Re.Pack](https://re-pack.dev/), you can still use this package to analyze 📊 JS bundles 📦 generated by Re.Pack.
29
28
  >
30
29
  > 💡 Learn more: 📖 [Re.Pack.md](./Re.Pack.md)
@@ -90,6 +89,35 @@ npx react-native-bundle-discovery-cli metro-stats.json
90
89
 
91
90
  # Display all packages in the bundle report
92
91
  npx react-native-bundle-discovery-cli packages metro-stats.json [--sort size|name] [--format json|table|default]
92
+
93
+ # Display the heaviest modules in the bundle report (default limit: 50, --limit 0 shows all)
94
+ # --filter accepts plain text (case-insensitive) or a regexp: --filter '/\.json/i'
95
+ npx react-native-bundle-discovery-cli modules metro-stats.json [--limit 50] [--filter <text|/regexp/>] [--sort size|name] [--format json|table|default]
96
+
97
+ # Compare two bundle reports: total size, added/removed/changed modules and packages,
98
+ # version changes, new duplicates and new deprecated packages
99
+ npx react-native-bundle-discovery-cli compare --before main-stats.json --after pr-stats.json [--limit 50] [--format json|markdown|default]
100
+ ```
101
+
102
+ ##### Bundle size checks in CI
103
+
104
+ `compare` exits with code `1` when one of the checks fails:
105
+
106
+ ```bash
107
+ # --fail-on-increase: max growth, in bytes (50KB, 0.5MB, 51200) or percent of the "before" size (5%)
108
+ # --max-size: absolute limit for the "after" bundle
109
+ # --fail-on: fail when new duplicate or new deprecated packages appear
110
+ npx react-native-bundle-discovery-cli compare --before main-stats.json --after pr-stats.json \
111
+ --fail-on-increase 50KB \
112
+ --max-size 3MB \
113
+ --fail-on new-duplicates,new-deprecated
114
+ ```
115
+
116
+ On GitHub Actions no extra setup is needed: the markdown report is added to the job summary
117
+ and failed checks are shown as error annotations. Use `--format markdown` to get the same report for a PR comment.
118
+
119
+ ```yaml
120
+ - run: npx react-native-bundle-discovery-cli compare --before main-stats.json --after metro-stats.json --fail-on-increase 5%
93
121
  ```
94
122
 
95
123
  #### UI package
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "react-native-bundle-discovery-ui",
3
- "version": "2.3.0",
3
+ "version": "2.5.0-rc.1",
4
4
  "main": "lib/bin.js",
5
5
  "bin": "lib/bin.js",
6
6
  "repository": "git@github.com:retyui/react-native-bundle-discovery.git",
7
7
  "author": "David <4661784+retyui@users.noreply.github.com>",
8
8
  "license": "MIT",
9
9
  "scripts": {
10
+ "srv": "node ./lib/bin.js server",
11
+ "analyze": "node ./_cli/bin.js analyze",
12
+ "tmp": "yarn srv /Users/davyd.narbutovich/tmp/RNDiscovery/metro-stats.json",
10
13
  "format": "prettier --write .",
11
14
  "start": "NODE_ENV=development npx discovery --config .discoveryrc.js",
12
15
  "build": "npx discovery-build --config .discoveryrc.js --output build --serve-only-assets --single-file"
package/pages/_common.js CHANGED
@@ -17,17 +17,56 @@ function getPackage(entry) {
17
17
  pkgName: $pkgName, // example: lodash
18
18
  size: $.value.sum(=>output.sizeInBytes),
19
19
  pkgInstances: $.value
20
- .group(=> path.split($pkgName).pick(0) + $pkgName)
20
+ .group(=> path.split('node_modules/' + $pkgName).pick(0) + 'node_modules/' + $pkgName)
21
21
  .map(=> {
22
22
  $pkgNameWithPath: $.key;
23
+ $pkgInfo: $packages.[path = $pkgNameWithPath][0];
23
24
  pkgName: $pkgNameWithPath, // example: node_modules/lodash
24
- version: $packages.[path = $pkgNameWithPath][0].version,
25
+ version: $pkgInfo.version,
26
+ metadata: $pkgInfo.metadata,
25
27
  size: $.value.sum(=> output.sizeInBytes),
26
28
  modules: $.value.map(=> $.$toModule()),
27
29
  }),
28
30
  }))`;
29
31
  }
30
32
 
33
+ const metadataTooltip = {
34
+ view: "text",
35
+ when: "metadata.createdAt",
36
+ data: "'Published ' + metadata.createdAt.formatDate() + ' (' + metadata.createdAt.timeAgo() + ')'",
37
+ };
38
+ const outdatedColor = "rgba(255, 165, 0, 0.35)";
39
+
40
+ // Compact npm metadata badges (`metadata` from the report)
41
+ function getMetadataBadges({
42
+ deprecated,
43
+ outdated,
44
+ latestVersion,
45
+ renderLatestVersion,
46
+ }) {
47
+ return [
48
+ {
49
+ view: "pill-badge",
50
+ when: deprecated,
51
+ data: `{ text: '⚠️ deprecated', message: ${deprecated} }`,
52
+ color: "#cf222e",
53
+ textColor: "white",
54
+ darkColor: "#f85149",
55
+ darkTextColor: "white",
56
+ tooltip: "text: message",
57
+ },
58
+ renderLatestVersion
59
+ ? {
60
+ view: "pill-badge",
61
+ when: outdated,
62
+ data: `{ text: '↑ v' + ${latestVersion} }`,
63
+ color: outdatedColor,
64
+ tooltip: "text: 'Latest version on npm'",
65
+ }
66
+ : null,
67
+ ].filter(Boolean);
68
+ }
69
+
31
70
  function getPackageList({
32
71
  data,
33
72
  itemPkgName,
@@ -49,6 +88,13 @@ function getPackageList({
49
88
  itemPkgName,
50
89
  "text: ' '",
51
90
  "pill-badge:{ text: size.formatBytes(), color: 'rgba(120, 177, 9, 0.35)' }",
91
+ // Summary of all instances (visible when the tree is collapsed)
92
+ ...getMetadataBadges({
93
+ deprecated: "pkgInstances.metadata.deprecated[0]",
94
+ outdated: "pkgInstances.[metadata and not metadata.isLatest]",
95
+ latestVersion: "pkgInstances.metadata.latestVersion[0]",
96
+ renderLatestVersion: false,
97
+ }),
52
98
  showCopiesBadge
53
99
  ? {
54
100
  view: "pill-badge",
@@ -70,12 +116,23 @@ function getPackageList({
70
116
  //
71
117
  "text:pkgName",
72
118
  "text:' '",
73
- "pill-badge:{ text: 'v' + version, color: '#0af' }",
119
+ {
120
+ view: "pill-badge",
121
+ data: "{ text: 'v' + version, metadata }",
122
+ color: "#0af",
123
+ tooltip: metadataTooltip,
124
+ },
74
125
  "pill-badge:{ text: size.formatBytes(), color: 'rgba(120, 177, 9, 0.35)' }",
75
126
  {
76
127
  view: "pill-badge",
77
128
  data: "modules.size().pluralBadge(['file','files'])",
78
129
  },
130
+ ...getMetadataBadges({
131
+ deprecated: "metadata.deprecated",
132
+ outdated: "metadata and not metadata.isLatest",
133
+ latestVersion: "metadata.latestVersion",
134
+ renderLatestVersion: true,
135
+ }),
79
136
  ],
80
137
  children: `$.modules`,
81
138
  itemConfig: {
package/pages/module.js CHANGED
@@ -11,15 +11,17 @@ function getMetroWarn({ when, href, linkText, content }) {
11
11
  className: "m-v-8",
12
12
  content: [
13
13
  ...content,
14
- {
15
- view: "link",
16
- data: {
17
- external: true,
18
- href,
19
- text: linkText,
20
- },
21
- },
22
- ],
14
+ linkText && href
15
+ ? {
16
+ view: "link",
17
+ data: {
18
+ external: true,
19
+ href,
20
+ text: linkText,
21
+ },
22
+ }
23
+ : null,
24
+ ].filter(Boolean),
23
25
  };
24
26
  }
25
27
 
@@ -30,7 +32,8 @@ discovery.page.define("module", {
30
32
  ...$,
31
33
  currentModule: $currentModule,
32
34
  isEntry: $currentModule.isEntry,
33
- isInjectedByMetro: $currentModule.path = '__prelude__' or $currentModule.path has '@react-native/js-polyfills',
35
+ isInjectedByMetro: $currentModule.path = '__prelude__' or $currentModule.path has '@react-native/js-polyfills',
36
+ isWebpackRuntime: $currentModule.path has '__runtime__',
34
37
  }`,
35
38
  content: [
36
39
  {
@@ -57,12 +60,12 @@ discovery.page.define("module", {
57
60
  },
58
61
  {
59
62
  value: "networkGraph",
60
- when: "not isInjectedByMetro and not isEntry",
63
+ when: "not isInjectedByMetro and not isEntry and not isWebpackRuntime",
61
64
  text: "Imported by modules",
62
65
  },
63
66
  {
64
67
  value: "mduplicates",
65
- when: "not isInjectedByMetro and not isEntry and currentModule.duplicates",
68
+ when: "not isInjectedByMetro and not isEntry and not isWebpackRuntime and currentModule.duplicates",
66
69
  text: "Duplicates",
67
70
  },
68
71
  ],
@@ -81,6 +84,13 @@ discovery.page.define("module", {
81
84
  href: "https://github.com/facebook/metro/blob/f595e9e1c9021ad372b489be8958cc298eb1e0d9/packages/metro/src/lib/getPreludeCode.js#L25-L33",
82
85
  linkText: "getPreludeCode.js",
83
86
  }),
87
+ getMetroWarn({
88
+ when: "$.currentModule.path = '__runtime__'",
89
+ content: [
90
+ "html: '<b>__runtime__</b>'",
91
+ "text: ' is a special runtime code that was generated by the Webpack/Rspack bundler'",
92
+ ],
93
+ }),
84
94
  getMetroWarn({
85
95
  when: "$.currentModule.path has '@react-native/js-polyfills'",
86
96
  content: [
package/pages/package.js CHANGED
@@ -24,6 +24,10 @@ discovery.page.define("package", {
24
24
  currentPkgWithUniqVer: $pkg.pkgName + ($copies = 0 ? '@' + $ver : ''),
25
25
  currentPkgWithUniqVerNpm: $pkg.pkgName + ($copies = 0 ? '/v/' + $ver : ''),
26
26
  currentPkg: $pkg,
27
+ currentPkgInstancesWithMetadata: $pkg.pkgInstances.[metadata],
28
+ currentPkgDeprecated: $pkg.pkgInstances.[metadata.deprecated],
29
+ currentPkgOutdated: $pkg.pkgInstances.[metadata and not metadata.isLatest],
30
+ currentPkgLatestVersion: $pkg.pkgInstances.metadata.latestVersion[0],
27
31
  }
28
32
  `,
29
33
  content: [
@@ -56,6 +60,22 @@ discovery.page.define("package", {
56
60
  }`,
57
61
  ],
58
62
  },
63
+ {
64
+ when: "$.currentPkgDeprecated",
65
+ view: "h2",
66
+ className: "inline-block no-margin",
67
+ content: `badge: { text: 'deprecated', color: "rgba(255, 0, 0, 0.35)" }`,
68
+ },
69
+ {
70
+ when: "$.currentPkgOutdated",
71
+ view: "h2",
72
+ className: "inline-block no-margin",
73
+ content: `badge: {
74
+ prefix: 'latest',
75
+ text: 'v' + $.currentPkgLatestVersion,
76
+ color: "rgba(255, 165, 0, 0.35)"
77
+ }`,
78
+ },
59
79
  getCopyToClipboardButton({
60
80
  textToCopy: `$.currentPkg.pkgName`,
61
81
  className: "m-l-0_5em",
@@ -63,6 +83,20 @@ discovery.page.define("package", {
63
83
  ],
64
84
  },
65
85
 
86
+ {
87
+ when: "$.currentPkgDeprecated",
88
+ view: "list",
89
+ data: "$.currentPkgDeprecated",
90
+ item: {
91
+ view: "alert-danger",
92
+ content: [
93
+ "html: '<b>Deprecated</b> '",
94
+ "text: 'v' + version + (#.data.currentPkgHasCopies ? ' (' + pkgName + ')' : '') + ': '",
95
+ "text: metadata.deprecated",
96
+ ],
97
+ },
98
+ },
99
+
66
100
  "html: '<br>'",
67
101
 
68
102
  {
@@ -112,6 +146,66 @@ discovery.page.define("package", {
112
146
  ],
113
147
  },
114
148
 
149
+ {
150
+ when: "$.currentPkgInstancesWithMetadata",
151
+ view: "block",
152
+ content: [
153
+ "h3: 'Versions'",
154
+ {
155
+ view: "table",
156
+ data: "$.currentPkgInstancesWithMetadata",
157
+ cols: [
158
+ { header: "Path", content: "text: pkgName" },
159
+ {
160
+ header: "Version",
161
+ content: "pill-badge:{ text: 'v' + version, color: '#0af' }",
162
+ },
163
+ {
164
+ header: "Published",
165
+ data: "metadata.createdAt",
166
+ content: [
167
+ "text: formatDate()",
168
+ "text: ' '",
169
+ "pill-badge:{ text: timeAgo() }",
170
+ ],
171
+ },
172
+ {
173
+ header: "Latest",
174
+ content: [
175
+ {
176
+ view: "link",
177
+ external: true,
178
+ data: `{
179
+ href: "https://www.npmjs.com/package/" + #.data.currentPkg.pkgName + "/v/" + metadata.latestVersion,
180
+ text: 'v' + metadata.latestVersion,
181
+ }`,
182
+ },
183
+ ],
184
+ },
185
+ {
186
+ header: "Status",
187
+ content: {
188
+ view: "switch",
189
+ content: [
190
+ {
191
+ when: "metadata.deprecated",
192
+ content: `badge: { text: 'deprecated', color: "rgba(255, 0, 0, 0.35)" }`,
193
+ },
194
+ {
195
+ when: "metadata.isLatest",
196
+ content: `badge: { text: 'latest', color: "rgba(120, 177, 9, 0.35)" }`,
197
+ },
198
+ {
199
+ content: `badge: { text: 'outdated', color: "rgba(255, 165, 0, 0.35)" }`,
200
+ },
201
+ ],
202
+ },
203
+ },
204
+ ],
205
+ },
206
+ ],
207
+ },
208
+
115
209
  "html: '<hr>'",
116
210
 
117
211
  {
package/queryHelpers.js CHANGED
@@ -144,6 +144,24 @@ Please do the following:
144
144
  const idx = filename.lastIndexOf(".");
145
145
  return idx === -1 ? "js" : filename.slice(idx + 1);
146
146
  },
147
+ // "2022-06-14T19:46:38.369Z" -> "2022-06-14"
148
+ formatDate(isoDate) {
149
+ return isoDate ? new Date(isoDate).toISOString().slice(0, 10) : "";
150
+ },
151
+ // "2022-06-14T19:46:38.369Z" -> "3 years ago"
152
+ timeAgo(isoDate) {
153
+ if (!isoDate) return "";
154
+ const days = Math.floor((Date.now() - new Date(isoDate)) / 86_400_000);
155
+ const [count, unit] =
156
+ days >= 365
157
+ ? [Math.floor(days / 365), ["year", "years"]]
158
+ : days >= 30
159
+ ? [Math.floor(days / 30), ["month", "months"]]
160
+ : [days, ["day", "days"]];
161
+ return count === 0
162
+ ? "today"
163
+ : `${helpers.pluralWithCount(count, unit)} ago`;
164
+ },
147
165
  toFixed(value, fractionDigits = 2) {
148
166
  return Number(value).toFixed(fractionDigits);
149
167
  },
@@ -161,14 +179,6 @@ Please do the following:
161
179
  isPackageImport(moduleName) {
162
180
  return moduleName?.[0] !== ".";
163
181
  },
164
- // isRuntimeCode(moduleName) {
165
- // return (
166
- // moduleName === "__prelude__" ||
167
- // moduleName.includes("@babel/runtime") ||
168
- // moduleName.includes("metro-runtime") ||
169
- // moduleName.includes("@react-native/js-polyfills")
170
- // );
171
- // },
172
182
  transformFilesList(files, rootFolder, type) {
173
183
  const nodeModulesMap = { children: {}, size: 0 };
174
184
  const sourceCodeMap = { children: {}, size: 0 };
@@ -177,6 +187,9 @@ Please do the following:
177
187
  if (path === "__prelude__") {
178
188
  path = "node_modules/__prelude__";
179
189
  }
190
+ if (path === "__runtime__") {
191
+ path = "node_modules/__runtime__";
192
+ }
180
193
 
181
194
  const shortPath = path.replace(rootFolder + "/", "");
182
195
  const isNodeModule = shortPath.includes("node_modules/");
@@ -195,6 +208,7 @@ Please do the following:
195
208
  if (index === parts.length - 1) {
196
209
  current[part].size = size;
197
210
  current[part].path = shortPath;
211
+ current[part].fullPath = path;
198
212
  }
199
213
 
200
214
  current = current[part].children;
@@ -310,6 +324,7 @@ function toGroups(node, label) {
310
324
  const common = {
311
325
  label,
312
326
  weight: node.size,
327
+ fullPath: node.fullPath,
313
328
  files: node.files,
314
329
  type: node.type,
315
330
  size: helpers.formatBytes(node.size),
@@ -332,6 +347,9 @@ function randomInt(min, max) {
332
347
 
333
348
  const nm = "node_modules/";
334
349
  function shortenPath(path, nodeModulesMap) {
350
+ if (path.startsWith(nm + ".pnpm/")) {
351
+ path = path.replaceAll(nm + ".pnpm/", "");
352
+ }
335
353
  let index = path.lastIndexOf(nm);
336
354
 
337
355
  if (index > 0) {