rn-bundle-analyzer 1.1.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/README.md +80 -0
- package/dist/builders/treeBuilder.d.ts +2 -0
- package/dist/builders/treeBuilder.js +47 -0
- package/dist/collectors/fileCollector.d.ts +2 -0
- package/dist/collectors/fileCollector.js +27 -0
- package/dist/collectors/inverseDependencyCollector.d.ts +2 -0
- package/dist/collectors/inverseDependencyCollector.js +29 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +15 -0
- package/dist/fileInfoCollector.d.ts +4 -0
- package/dist/fileInfoCollector.js +44 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +48 -0
- package/dist/templates/analyse.html +1082 -0
- package/dist/templates/analyse.pug +321 -0
- package/dist/templates/analyseTemplate.d.ts +3 -0
- package/dist/templates/analyseTemplate.js +41 -0
- package/dist/templates/icons-inline.js +27 -0
- package/dist/templates/lucideIcons.d.ts +2 -0
- package/dist/templates/lucideIcons.js +28 -0
- package/dist/templates/tailwindcss.js +83 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.js +2 -0
- package/dist/utils/envSetup.d.ts +1 -0
- package/dist/utils/envSetup.js +21 -0
- package/dist/utils/fileUtils.d.ts +3 -0
- package/dist/utils/fileUtils.js +27 -0
- package/dist/writers/fileInfoWriter.d.ts +12 -0
- package/dist/writers/fileInfoWriter.js +42 -0
- package/dist/writers/inverseDependencyWriter.d.ts +2 -0
- package/dist/writers/inverseDependencyWriter.js +48 -0
- package/dist/writers/treeWriter.d.ts +2 -0
- package/dist/writers/treeWriter.js +26 -0
- package/index.js +1 -0
- package/package.json +24 -0
- package/scripts/compile-pug.js +17 -0
- package/src/builders/treeBuilder.ts +57 -0
- package/src/collectors/fileCollector.ts +24 -0
- package/src/collectors/inverseDependencyCollector.ts +32 -0
- package/src/config.ts +14 -0
- package/src/fileInfoCollector.ts +50 -0
- package/src/index.ts +15 -0
- package/src/templates/analyse-app.js +988 -0
- package/src/templates/analyse.pug +321 -0
- package/src/templates/analyseTemplate.ts +45 -0
- package/src/templates/icons-inline.js +27 -0
- package/src/templates/tailwindcss.js +83 -0
- package/src/types.ts +43 -0
- package/src/utils/envSetup.ts +18 -0
- package/src/utils/fileUtils.ts +21 -0
- package/src/writers/fileInfoWriter.ts +44 -0
- package/src/writers/inverseDependencyWriter.ts +50 -0
- package/src/writers/treeWriter.ts +24 -0
- package/tsconfig.json +20 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# rn-bundle-analyzer
|
|
2
|
+
|
|
3
|
+
`rn-bundle-analyzer` is a React Native bundle analysis tool.
|
|
4
|
+
It collects module data during the Metro bundling process and generates a visual report.
|
|
5
|
+
It does not depend on SourceMap parsing, which makes it useful for bundle-size diagnostics and dependency hotspot investigation.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- File list view: inspect bundled files by size, path, and type
|
|
10
|
+
- Directory tree view: analyze size distribution by folder hierarchy
|
|
11
|
+
- Inverse dependency view: see which files depend on a target file
|
|
12
|
+
- Static report output: generate HTML + JS data files for sharing and archiving
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
yarn add -D rn-package-analyse
|
|
18
|
+
# or
|
|
19
|
+
npm i -D rn-package-analyse
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Setup
|
|
23
|
+
|
|
24
|
+
Add `createProcessModuleFilter` to your `metro.config.js`:
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
28
|
+
const { mergeConfig } = require('@react-native/metro-config')
|
|
29
|
+
const { getMetroConfig } = require('@jdtaro/plugin-platform-jdrn/dist/supporter')
|
|
30
|
+
const { createProcessModuleFilter } = require('rn-package-analyse')
|
|
31
|
+
|
|
32
|
+
module.exports = (async function () {
|
|
33
|
+
const baseConfig = await getMetroConfig()
|
|
34
|
+
|
|
35
|
+
// Preserve any existing processModuleFilter logic.
|
|
36
|
+
const originalProcessModuleFilter = baseConfig.serializer?.processModuleFilter
|
|
37
|
+
|
|
38
|
+
return mergeConfig(
|
|
39
|
+
{
|
|
40
|
+
serializer: {
|
|
41
|
+
// Collect module data during bundling and write report artifacts.
|
|
42
|
+
processModuleFilter: createProcessModuleFilter(originalProcessModuleFilter),
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
baseConfig
|
|
46
|
+
)
|
|
47
|
+
})()
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Generate the Report
|
|
51
|
+
|
|
52
|
+
After setup, run your normal React Native bundle/build command (for example, a release build or bundle command).
|
|
53
|
+
When bundling completes, a `rn-package-analyse` folder will be generated in your project root.
|
|
54
|
+
|
|
55
|
+
## Typical Use Cases
|
|
56
|
+
|
|
57
|
+
- Bundle size suddenly increases and you need a fast root-cause analysis
|
|
58
|
+
- A core file appears over-coupled and you want inverse dependency tracing
|
|
59
|
+
- Compare bundle structure across builds/releases for regression checks
|
|
60
|
+
|
|
61
|
+
## Notes
|
|
62
|
+
|
|
63
|
+
- Ensure Metro `serializer.processModuleFilter` is correctly wired.
|
|
64
|
+
- If you already have a custom `processModuleFilter`, pass it through as shown above.
|
|
65
|
+
- Use consistent build parameters in CI for reliable cross-build comparison.
|
|
66
|
+
|
|
67
|
+
## Local Development
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm run build
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Build steps:
|
|
74
|
+
|
|
75
|
+
1. Compile TypeScript into `dist`
|
|
76
|
+
2. Copy template assets into `dist/templates`
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
ISC
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildTreeStructure = buildTreeStructure;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
function buildTreeStructure(fileInfoArray, totalSize) {
|
|
9
|
+
const tree = {};
|
|
10
|
+
fileInfoArray.forEach(fileInfo => {
|
|
11
|
+
const pathParts = fileInfo.path.split(path_1.default.sep).filter(part => part !== '');
|
|
12
|
+
let currentLevel = tree;
|
|
13
|
+
let currentPath = '';
|
|
14
|
+
pathParts.forEach((part, index) => {
|
|
15
|
+
currentPath = currentPath ? path_1.default.join(currentPath, part) : part;
|
|
16
|
+
if (!currentLevel[part]) {
|
|
17
|
+
currentLevel[part] = {
|
|
18
|
+
path: currentPath,
|
|
19
|
+
size: 0,
|
|
20
|
+
files: [],
|
|
21
|
+
children: {},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
currentLevel[part].size += fileInfo.size;
|
|
25
|
+
if (index === pathParts.length - 1) {
|
|
26
|
+
currentLevel[part].files.push(fileInfo);
|
|
27
|
+
}
|
|
28
|
+
currentLevel = currentLevel[part].children;
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
const convertToTargetFormat = (treeNode, nodePath) => {
|
|
32
|
+
const result = {
|
|
33
|
+
path: nodePath,
|
|
34
|
+
size: treeNode.size,
|
|
35
|
+
ratio: Math.round((treeNode.size / totalSize) * 10000) / 10000,
|
|
36
|
+
};
|
|
37
|
+
const childrenArray = Object.values(treeNode.children).map(childNode => convertToTargetFormat(childNode, childNode.path));
|
|
38
|
+
if (childrenArray.length > 0) {
|
|
39
|
+
result.child = childrenArray.sort((a, b) => b.size - a.size);
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
const pathAnalyse = Object.values(tree)
|
|
44
|
+
.map(rootNode => convertToTargetFormat(rootNode, rootNode.path))
|
|
45
|
+
.sort((a, b) => b.size - a.size);
|
|
46
|
+
return { totalSize, pathAnalyse };
|
|
47
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.collectFileInfo = collectFileInfo;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fileUtils_1 = require("../utils/fileUtils");
|
|
10
|
+
function collectFileInfo(filePath) {
|
|
11
|
+
try {
|
|
12
|
+
const stats = fs_1.default.statSync(filePath);
|
|
13
|
+
const relativePath = (0, fileUtils_1.toRelativePath)(filePath);
|
|
14
|
+
console.log('📦 RN 打包统计:', relativePath, `${(stats.size / 1024).toFixed(2)} KB`);
|
|
15
|
+
return {
|
|
16
|
+
path: relativePath,
|
|
17
|
+
size: stats.size,
|
|
18
|
+
sizeKB: Math.round(stats.size / 1024 * 100) / 100,
|
|
19
|
+
lastModified: stats.mtime.toISOString(),
|
|
20
|
+
extension: path_1.default.extname(filePath),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.warn(`⚠️ 无法获取文件信息: ${filePath}`, error.message);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectInverseDependencies = collectInverseDependencies;
|
|
4
|
+
const fileUtils_1 = require("../utils/fileUtils");
|
|
5
|
+
function collectInverseDependencies(filePath, inverseDependencies) {
|
|
6
|
+
try {
|
|
7
|
+
const relativePath = (0, fileUtils_1.toRelativePath)(filePath);
|
|
8
|
+
if (!inverseDependencies || inverseDependencies.length === 0) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const relativeInverseDeps = inverseDependencies
|
|
12
|
+
.filter(dep => dep && typeof dep === 'string')
|
|
13
|
+
.map(dep => (0, fileUtils_1.toRelativePath)(dep))
|
|
14
|
+
.filter(dep => dep && !dep.startsWith('..'));
|
|
15
|
+
if (relativeInverseDeps.length === 0) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
console.log(`🔗 反向依赖收集: ${relativePath} <- ${relativeInverseDeps.length} 个依赖者`);
|
|
19
|
+
return {
|
|
20
|
+
path: relativePath,
|
|
21
|
+
dependents: relativeInverseDeps,
|
|
22
|
+
dependentCount: relativeInverseDeps.length,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.warn(`⚠️ 收集反向依赖失败: ${filePath}`, error.message);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const SAVE_DIR = "rn-package-analyse";
|
|
2
|
+
export declare const OUTPUT_FILES: {
|
|
3
|
+
readonly HTML: "rn-package-analyse.html";
|
|
4
|
+
readonly ICONS_INLINE: "icons-inline.js";
|
|
5
|
+
readonly TAILWIND_JS: "tailwindcss.js";
|
|
6
|
+
readonly PACKAGE_DATA: "rn-bundle-files-data.js";
|
|
7
|
+
readonly TREE_DATA: "rn-bundle-tree-data.js";
|
|
8
|
+
readonly INVERSE_DEPS: "rn-bundle-inverse-deps-data.js";
|
|
9
|
+
};
|
|
10
|
+
export declare const SAVE_DELAY = 3000;
|
|
11
|
+
export declare const TREE_DISPLAY_LIMIT = 5;
|
|
12
|
+
export declare const TOP_DEPENDED_LIMIT = 10;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TOP_DEPENDED_LIMIT = exports.TREE_DISPLAY_LIMIT = exports.SAVE_DELAY = exports.OUTPUT_FILES = exports.SAVE_DIR = void 0;
|
|
4
|
+
exports.SAVE_DIR = 'rn-package-analyse';
|
|
5
|
+
exports.OUTPUT_FILES = {
|
|
6
|
+
HTML: 'rn-package-analyse.html',
|
|
7
|
+
ICONS_INLINE: 'icons-inline.js',
|
|
8
|
+
TAILWIND_JS: 'tailwindcss.js',
|
|
9
|
+
PACKAGE_DATA: 'rn-bundle-files-data.js',
|
|
10
|
+
TREE_DATA: 'rn-bundle-tree-data.js',
|
|
11
|
+
INVERSE_DEPS: 'rn-bundle-inverse-deps-data.js',
|
|
12
|
+
};
|
|
13
|
+
exports.SAVE_DELAY = 3000;
|
|
14
|
+
exports.TREE_DISPLAY_LIMIT = 5;
|
|
15
|
+
exports.TOP_DEPENDED_LIMIT = 10;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
declare function onCollectFileInfo(filePath: string): void;
|
|
2
|
+
declare function onCollectInverseDependencies(filePath: string, deps: string[]): void;
|
|
3
|
+
declare function scheduleSave(): void;
|
|
4
|
+
export { onCollectFileInfo as collectFileInfo, onCollectInverseDependencies as collectInverseDependencies, scheduleSave };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectFileInfo = onCollectFileInfo;
|
|
4
|
+
exports.collectInverseDependencies = onCollectInverseDependencies;
|
|
5
|
+
exports.scheduleSave = scheduleSave;
|
|
6
|
+
const config_1 = require("./config");
|
|
7
|
+
const fileCollector_1 = require("./collectors/fileCollector");
|
|
8
|
+
const inverseDependencyCollector_1 = require("./collectors/inverseDependencyCollector");
|
|
9
|
+
const treeBuilder_1 = require("./builders/treeBuilder");
|
|
10
|
+
const fileInfoWriter_1 = require("./writers/fileInfoWriter");
|
|
11
|
+
const treeWriter_1 = require("./writers/treeWriter");
|
|
12
|
+
const inverseDependencyWriter_1 = require("./writers/inverseDependencyWriter");
|
|
13
|
+
const files = new Map();
|
|
14
|
+
const inverseDependenciesMap = new Map();
|
|
15
|
+
let saveTimeout = null;
|
|
16
|
+
function onCollectFileInfo(filePath) {
|
|
17
|
+
const info = (0, fileCollector_1.collectFileInfo)(filePath);
|
|
18
|
+
if (info) {
|
|
19
|
+
files.set(info.path, info);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function onCollectInverseDependencies(filePath, deps) {
|
|
23
|
+
const info = (0, inverseDependencyCollector_1.collectInverseDependencies)(filePath, deps);
|
|
24
|
+
if (info) {
|
|
25
|
+
inverseDependenciesMap.set(info.path, info);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function scheduleSave() {
|
|
29
|
+
if (saveTimeout)
|
|
30
|
+
clearTimeout(saveTimeout);
|
|
31
|
+
saveTimeout = setTimeout(saveAll, config_1.SAVE_DELAY);
|
|
32
|
+
}
|
|
33
|
+
function saveAll() {
|
|
34
|
+
if (files.size === 0) {
|
|
35
|
+
console.log('⚠️ 没有收集到文件信息');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const fileInfoArray = Array.from(files.values());
|
|
39
|
+
const totalSize = fileInfoArray.reduce((sum, f) => sum + f.size, 0);
|
|
40
|
+
(0, fileInfoWriter_1.writeFileInfo)(fileInfoArray);
|
|
41
|
+
const treeData = (0, treeBuilder_1.buildTreeStructure)(fileInfoArray, totalSize);
|
|
42
|
+
(0, treeWriter_1.writeTreeAnalysis)(treeData);
|
|
43
|
+
(0, inverseDependencyWriter_1.writeInverseDependencies)(Array.from(inverseDependenciesMap.values()));
|
|
44
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ProcessModuleFilter } from './types';
|
|
2
|
+
export declare const createProcessModuleFilter: (originalProcessModuleFilter?: ProcessModuleFilter) => ProcessModuleFilter;
|
|
3
|
+
export type { FileInfo, FileTypeStats, InverseDependencyInfo, TreeNode, TreeOutputNode, TreeData, MetroModule, ProcessModuleFilter } from './types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createProcessModuleFilter = void 0;
|
|
37
|
+
const fileInfoCollector = __importStar(require("./fileInfoCollector"));
|
|
38
|
+
const createProcessModuleFilter = (originalProcessModuleFilter) => {
|
|
39
|
+
return function (module) {
|
|
40
|
+
if (module && module.path) {
|
|
41
|
+
fileInfoCollector.collectFileInfo(module.path);
|
|
42
|
+
fileInfoCollector.collectInverseDependencies(module.path, Array.from(module.inverseDependencies));
|
|
43
|
+
fileInfoCollector.scheduleSave();
|
|
44
|
+
}
|
|
45
|
+
return originalProcessModuleFilter ? originalProcessModuleFilter(module) : true;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
exports.createProcessModuleFilter = createProcessModuleFilter;
|