react-native-bundle-discovery-ui 2.1.0 → 2.2.0-rc.2
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 +28 -19
- package/lib/build.js +10 -2
- package/lib/isRsdoctorReport.js +23 -0
- package/lib/rsdoctor.js +149 -0
- package/lib/server.js +10 -2
- package/package.json +6 -2
- package/queryHelpers.js +3 -0
package/README.md
CHANGED
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/react-native-bundle-discovery)
|
|
4
4
|
[](https://www.npmtrends.com/react-native-bundle-discovery)
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
A simple package that helps developers visualize and analyze the bundle size of React Native apps.
|
|
8
7
|
With this tool, you can easily explore your app's codebase, identify large or heavy packages, and inspect the structure of modules and code within your project.
|
|
9
8
|
|
|
10
9
|
<img width="800" alt="" src="./assets/img.png" />
|
|
11
10
|
|
|
12
|
-
|
|
13
11
|
### Packages:
|
|
14
12
|
|
|
15
13
|
- `react-native-bundle-discovery` - simple JS library to generate a JSON report of the bundle.
|
|
@@ -17,13 +15,20 @@ With this tool, you can easily explore your app's codebase, identify large or he
|
|
|
17
15
|
- `react-native-bundle-discovery-cli` - CLI to analyze the bundle report.
|
|
18
16
|
- `react-native-bundle-discovery-rozenite-plugin` - Rozenite plugin to integrate UI tool to [React Native DevTools](https://reactnative.dev/docs/react-native-devtools).
|
|
19
17
|
|
|
18
|
+
---
|
|
19
|
+
|
|
20
20
|
### Setup:
|
|
21
21
|
|
|
22
22
|
There are two ways to install the package:
|
|
23
23
|
|
|
24
|
-
1. As in independent tool (UI + CLI)
|
|
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
|
+
> 🚀 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
|
+
>
|
|
30
|
+
> 💡 Learn more: 📖 [Re.Pack.md](./Re.Pack.md)
|
|
31
|
+
|
|
27
32
|
#### 1. Install (independent tool)
|
|
28
33
|
|
|
29
34
|
```bash
|
|
@@ -62,7 +67,7 @@ See: [_rozenite/README.md](_rozenite/README.md)
|
|
|
62
67
|
|
|
63
68
|
### 3. Build the app
|
|
64
69
|
|
|
65
|
-
|
|
70
|
+
For example, for iOS you can run the following command, and it will generate the `metro-stats.json` file in the root of your project:
|
|
66
71
|
|
|
67
72
|
```bash
|
|
68
73
|
npx react-native bundle \
|
|
@@ -112,30 +117,34 @@ npx react-native-bundle-discovery-ui build metro-stats.json
|
|
|
112
117
|
| outputJsonPath: string | `<root>/metro-stats.json` | The path where the JSON report will be saved. Defaults to `metro-stats.json` in project root. |
|
|
113
118
|
| includeCode: boolean | `true` | Whether to include the source and output code in the JSON report. |
|
|
114
119
|
|
|
115
|
-
|
|
120
|
+
**⚠️ Be cautious sharing reports with `includeCode` enabled**
|
|
121
|
+
|
|
122
|
+
Enabling this option significantly increases report size and **embeds the source code** of your assets.
|
|
116
123
|
|
|
124
|
+
If your code is proprietary 🔒, share these reports responsibly 🤝!
|
|
125
|
+
|
|
126
|
+
---
|
|
117
127
|
|
|
118
|
-
|
|
119
|
-
| ---------------------- | ------------------------- | ----------------------------- |
|
|
120
|
-
| removePromisePolyfill: boolean | `false` | Remove the Promise polyfill as Hermes provide own impl. (issue: [#57702](https://github.com/react/react-native/issues/57702)) |
|
|
121
|
-
| removeOldRenderer: boolean | `false` | Whether to remove the old renderer. Set to `true` when New Arch is enabled |
|
|
122
|
-
| removeNewRenderer: boolean | `false` | Whether to remove the new renderer. Set to `true` when New Arch is disabled |
|
|
123
|
-
| removeUTFSequence: boolean | `false` | Remove useless undocumented RN module. |
|
|
128
|
+
### `createResolveRequest(options: ResolveRequestOptions)`
|
|
124
129
|
|
|
130
|
+
| Prop | Default value | Description |
|
|
131
|
+
| ------------------------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
132
|
+
| removePromisePolyfill: boolean | `false` | Remove the Promise polyfill as Hermes provide own impl. (issue: [#57702](https://github.com/react/react-native/issues/57702)) |
|
|
133
|
+
| removeUTFSequence: boolean | `false` | Remove useless undocumented RN module. |
|
|
125
134
|
|
|
126
|
-
|
|
135
|
+
Helper that creates a custom resolve request function to filter out unnecessary modules from the bundle.
|
|
127
136
|
You can get recommendations during the analysis of the bundle report using the CLI tool.
|
|
128
137
|
|
|
129
138
|
```js
|
|
130
139
|
// metro.config.js
|
|
131
|
-
const {
|
|
140
|
+
const { createResolveRequest } = require("react-native-bundle-discovery");
|
|
141
|
+
const resolveRequest = createResolveRequest({
|
|
142
|
+
removePromisePolyfill: true,
|
|
143
|
+
removeUTFSequence: true,
|
|
144
|
+
});
|
|
132
145
|
const config = {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
removePromisePolyfill: true,
|
|
136
|
-
removeOldRenderer: true,
|
|
137
|
-
removeUTFSequence: true,
|
|
138
|
-
}),
|
|
146
|
+
resolver: {
|
|
147
|
+
resolveRequest,
|
|
139
148
|
},
|
|
140
149
|
};
|
|
141
150
|
```
|
package/lib/build.js
CHANGED
|
@@ -3,6 +3,7 @@ const path = require("path");
|
|
|
3
3
|
const chalk = require("chalk");
|
|
4
4
|
const { build } = require("@discoveryjs/cli");
|
|
5
5
|
const config = require("../.discoveryrc.js");
|
|
6
|
+
const { isRsdoctorReport } = require("./isRsdoctorReport.js");
|
|
6
7
|
|
|
7
8
|
function buildHtmlPage(filePath, output, clean, singleFile, verbose) {
|
|
8
9
|
if (verbose) {
|
|
@@ -46,13 +47,20 @@ function buildHtmlPage(filePath, output, clean, singleFile, verbose) {
|
|
|
46
47
|
`Creating temporary config file at: ${chalk.green(configFile)}, for discovery.js`,
|
|
47
48
|
);
|
|
48
49
|
}
|
|
50
|
+
const transformRcdoctor = require.resolve("./rsdoctor.js");
|
|
49
51
|
fs.writeFileSync(
|
|
50
52
|
configFile,
|
|
51
|
-
`
|
|
53
|
+
`const {transformRSDoctorData} = require("${transformRcdoctor}");
|
|
54
|
+
module.exports = ${JSON.stringify(
|
|
52
55
|
{ ...config, data: "<tmp>" },
|
|
53
56
|
null,
|
|
54
57
|
1,
|
|
55
|
-
).replace(
|
|
58
|
+
).replace(
|
|
59
|
+
`"<tmp>"`,
|
|
60
|
+
isRsdoctorReport(fullJsonPath)
|
|
61
|
+
? `() => transformRSDoctorData(require("${fullJsonPath}"))`
|
|
62
|
+
: `() => require("${fullJsonPath}")`,
|
|
63
|
+
)};`,
|
|
56
64
|
);
|
|
57
65
|
|
|
58
66
|
if (verbose) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
|
|
3
|
+
const isRsdoctorReport = (fullJsonPath) => {
|
|
4
|
+
try {
|
|
5
|
+
const json = require(fullJsonPath);
|
|
6
|
+
if (
|
|
7
|
+
Array.isArray(json?.data?.moduleGraph?.modules) ||
|
|
8
|
+
Array.isArray(json?.data?.packageGraph?.packages)
|
|
9
|
+
) {
|
|
10
|
+
console.info(
|
|
11
|
+
`Detected ${chalk.red("rsdoctor")} format in the input file: ${chalk.green(fullJsonPath)}`,
|
|
12
|
+
);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
isRsdoctorReport,
|
|
23
|
+
};
|
package/lib/rsdoctor.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const { resolve } = require("path");
|
|
2
|
+
|
|
3
|
+
const possibleEntryFiles = [
|
|
4
|
+
"index.js",
|
|
5
|
+
"index.jsx",
|
|
6
|
+
"index.ts",
|
|
7
|
+
"index.tsx",
|
|
8
|
+
"index.mjs",
|
|
9
|
+
"index.cjs",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const doStructureCheck = (rsdoctorData) => {
|
|
13
|
+
if (!(
|
|
14
|
+
Array.isArray(rsdoctorData?.data?.configs) &&
|
|
15
|
+
rsdoctorData.data.configs.length === 1
|
|
16
|
+
)) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`Unexpected number of "data.configs[]" in rsdoctor-data.json. Work only with single config. Found: ${rsdoctorData?.data?.configs?.length}`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!Array.isArray(rsdoctorData?.data?.packageGraph?.packages)) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Unexpected "data.packageGraph.packages[]" in rsdoctor-data.json. Expected array. Found: ${typeof rsdoctorData?.data?.packageGraph?.packages}`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const firstConfig = rsdoctorData.data.configs[0];
|
|
29
|
+
|
|
30
|
+
if (!(firstConfig.name === "rspack" || firstConfig.name === "webpack")) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Unsupported bundler: ${firstConfig.name}@${firstConfig.version}. Only 'rspack' is supported.`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const getDependenciesMap = (rsdoctorData) => {
|
|
38
|
+
const depsMap = new Map(
|
|
39
|
+
rsdoctorData.data.moduleGraph.dependencies.map((dep) => [dep.id, dep]),
|
|
40
|
+
);
|
|
41
|
+
return depsMap;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const getEntryFile = (rsdoctorData) => {
|
|
45
|
+
const firstConfig = rsdoctorData?.data?.configs?.[0];
|
|
46
|
+
const entry = firstConfig?.config?.entry;
|
|
47
|
+
const entryFile =
|
|
48
|
+
typeof entry?.main === "string"
|
|
49
|
+
? entry.main
|
|
50
|
+
: typeof entry?.main?.import === "string"
|
|
51
|
+
? entry.main.import
|
|
52
|
+
: entry?.main.import[0];
|
|
53
|
+
|
|
54
|
+
if (entryFile) {
|
|
55
|
+
return resolve(firstConfig.root, entryFile);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const pathsToCheck = possibleEntryFiles.map((f) =>
|
|
59
|
+
resolve(firstConfig.root, f),
|
|
60
|
+
);
|
|
61
|
+
const indexFile = rsdoctorData.data.moduleGraph.modules.find((mdl) =>
|
|
62
|
+
pathsToCheck.some((entry) => mdl.path === entry),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (indexFile && indexFile.imported?.length === 0) {
|
|
66
|
+
return indexFile.path;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return `${firstConfig.root}/index.js`;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const transformRSDoctorData = (rsdoctorData) => {
|
|
73
|
+
doStructureCheck(rsdoctorData);
|
|
74
|
+
const tmpDependenciesMap = getDependenciesMap(rsdoctorData);
|
|
75
|
+
const firstConfig = rsdoctorData.data.configs[0];
|
|
76
|
+
return {
|
|
77
|
+
date: Date.now(), // no info in rspack report
|
|
78
|
+
entryPoint: getEntryFile(rsdoctorData),
|
|
79
|
+
rootFolder: firstConfig.root,
|
|
80
|
+
// envs: {},
|
|
81
|
+
transformOptions: {
|
|
82
|
+
// customTransformOptions: {},
|
|
83
|
+
dev: firstConfig.config.mode !== "production",
|
|
84
|
+
minify: firstConfig.config.optimization?.minimize ?? false,
|
|
85
|
+
platform: firstConfig.config.name ?? "ios",
|
|
86
|
+
// type: "module",
|
|
87
|
+
// unstable_transformProfile: "default",
|
|
88
|
+
},
|
|
89
|
+
// Example of package:
|
|
90
|
+
// {
|
|
91
|
+
// name: "@react-native/js-polyfills",
|
|
92
|
+
// absolutePath:
|
|
93
|
+
// "/Users/xd/tmp/RN84/node_modules/@react-native/js-polyfills",
|
|
94
|
+
// version: "0.84.1",
|
|
95
|
+
// }
|
|
96
|
+
packages: rsdoctorData.data.packageGraph.packages.map((pkg) => ({
|
|
97
|
+
name: pkg.name,
|
|
98
|
+
absolutePath: pkg.root,
|
|
99
|
+
version: pkg.version,
|
|
100
|
+
source: { sizeInBytes: pkg.size?.sourceSize ?? 1 },
|
|
101
|
+
output: { sizeInBytes: pkg.size?.parsedSize ?? 1 },
|
|
102
|
+
})),
|
|
103
|
+
// Example of module:
|
|
104
|
+
// {
|
|
105
|
+
// "path": "/Users/xd/tmp/RN84/node_modules/react-native/Libraries/BatchedBridge/BatchedBridge.js",
|
|
106
|
+
// "source": {
|
|
107
|
+
// "code": "",
|
|
108
|
+
// "lineCount": 32,
|
|
109
|
+
// "sizeInBytes": 954
|
|
110
|
+
// },
|
|
111
|
+
// "output": {
|
|
112
|
+
// "code": "",
|
|
113
|
+
// "lineCount": 1,
|
|
114
|
+
// "sizeInBytes": 219
|
|
115
|
+
// },
|
|
116
|
+
// "dependencies": [
|
|
117
|
+
// {
|
|
118
|
+
// "absolutePath": "/Users/xd/tmp/RN84/node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js",
|
|
119
|
+
// "name": "./MessageQueue"
|
|
120
|
+
// }
|
|
121
|
+
// ]
|
|
122
|
+
// },
|
|
123
|
+
modules: rsdoctorData.data.moduleGraph.modules.map((mdl) => ({
|
|
124
|
+
path: mdl.path,
|
|
125
|
+
source: {
|
|
126
|
+
code: "", // no code in rspack report
|
|
127
|
+
lineCount: 0, // no code to count lines in rspack report
|
|
128
|
+
sizeInBytes: mdl.size?.sourceSize ?? 0,
|
|
129
|
+
},
|
|
130
|
+
output: {
|
|
131
|
+
code: "",
|
|
132
|
+
lineCount: 0,
|
|
133
|
+
sizeInBytes: mdl.size?.parsedSize ?? 0,
|
|
134
|
+
},
|
|
135
|
+
dependencies: mdl.dependencies
|
|
136
|
+
.map((depNumId) => {
|
|
137
|
+
const dep = tmpDependenciesMap.get(depNumId);
|
|
138
|
+
return dep
|
|
139
|
+
? { absolutePath: dep.resolvedRequest, name: dep.request }
|
|
140
|
+
: null;
|
|
141
|
+
})
|
|
142
|
+
.filter((e) => e !== null),
|
|
143
|
+
})),
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
module.exports = {
|
|
148
|
+
transformRSDoctorData,
|
|
149
|
+
};
|
package/lib/server.js
CHANGED
|
@@ -4,6 +4,7 @@ const chalk = require("chalk");
|
|
|
4
4
|
const { createServer } = require("@discoveryjs/cli");
|
|
5
5
|
const { silent } = require("@discoveryjs/cli/lib/shared/utils.js");
|
|
6
6
|
const config = require("../.discoveryrc.js");
|
|
7
|
+
const { isRsdoctorReport } = require("./isRsdoctorReport.js");
|
|
7
8
|
|
|
8
9
|
function serve(filePath, port, verbose) {
|
|
9
10
|
const PORT = process.env.PORT || port;
|
|
@@ -45,13 +46,20 @@ function serve(filePath, port, verbose) {
|
|
|
45
46
|
`Creating temporary config file at: ${chalk.green(configFile)}, for discovery.js`,
|
|
46
47
|
);
|
|
47
48
|
}
|
|
49
|
+
const transformRcdoctor = require.resolve("./rsdoctor.js");
|
|
48
50
|
fs.writeFileSync(
|
|
49
51
|
configFile,
|
|
50
|
-
`
|
|
52
|
+
`const {transformRSDoctorData} = require("${transformRcdoctor}");
|
|
53
|
+
module.exports = ${JSON.stringify(
|
|
51
54
|
{ ...config, data: "<tmp>" },
|
|
52
55
|
null,
|
|
53
56
|
1,
|
|
54
|
-
).replace(
|
|
57
|
+
).replace(
|
|
58
|
+
`"<tmp>"`,
|
|
59
|
+
isRsdoctorReport(fullJsonPath)
|
|
60
|
+
? `() => transformRSDoctorData(require("${fullJsonPath}"))`
|
|
61
|
+
: `() => require("${fullJsonPath}")`,
|
|
62
|
+
)};`,
|
|
55
63
|
);
|
|
56
64
|
|
|
57
65
|
if (verbose) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-bundle-discovery-ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0-rc.2",
|
|
4
4
|
"main": "lib/bin.js",
|
|
5
5
|
"bin": "lib/bin.js",
|
|
6
6
|
"repository": "git@github.com:retyui/react-native-bundle-discovery.git",
|
|
@@ -38,5 +38,9 @@
|
|
|
38
38
|
"queryHelpers.js",
|
|
39
39
|
".discoveryrc.js"
|
|
40
40
|
],
|
|
41
|
-
"packageManager": "yarn@4.18.0"
|
|
41
|
+
"packageManager": "yarn@4.18.0",
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"acorn": "^8.18.0",
|
|
44
|
+
"acorn-walk": "^8.3.5"
|
|
45
|
+
}
|
|
42
46
|
}
|