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.
Files changed (54) hide show
  1. package/README.md +80 -0
  2. package/dist/builders/treeBuilder.d.ts +2 -0
  3. package/dist/builders/treeBuilder.js +47 -0
  4. package/dist/collectors/fileCollector.d.ts +2 -0
  5. package/dist/collectors/fileCollector.js +27 -0
  6. package/dist/collectors/inverseDependencyCollector.d.ts +2 -0
  7. package/dist/collectors/inverseDependencyCollector.js +29 -0
  8. package/dist/config.d.ts +12 -0
  9. package/dist/config.js +15 -0
  10. package/dist/fileInfoCollector.d.ts +4 -0
  11. package/dist/fileInfoCollector.js +44 -0
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.js +48 -0
  14. package/dist/templates/analyse.html +1082 -0
  15. package/dist/templates/analyse.pug +321 -0
  16. package/dist/templates/analyseTemplate.d.ts +3 -0
  17. package/dist/templates/analyseTemplate.js +41 -0
  18. package/dist/templates/icons-inline.js +27 -0
  19. package/dist/templates/lucideIcons.d.ts +2 -0
  20. package/dist/templates/lucideIcons.js +28 -0
  21. package/dist/templates/tailwindcss.js +83 -0
  22. package/dist/types.d.ts +41 -0
  23. package/dist/types.js +2 -0
  24. package/dist/utils/envSetup.d.ts +1 -0
  25. package/dist/utils/envSetup.js +21 -0
  26. package/dist/utils/fileUtils.d.ts +3 -0
  27. package/dist/utils/fileUtils.js +27 -0
  28. package/dist/writers/fileInfoWriter.d.ts +12 -0
  29. package/dist/writers/fileInfoWriter.js +42 -0
  30. package/dist/writers/inverseDependencyWriter.d.ts +2 -0
  31. package/dist/writers/inverseDependencyWriter.js +48 -0
  32. package/dist/writers/treeWriter.d.ts +2 -0
  33. package/dist/writers/treeWriter.js +26 -0
  34. package/index.js +1 -0
  35. package/package.json +24 -0
  36. package/scripts/compile-pug.js +17 -0
  37. package/src/builders/treeBuilder.ts +57 -0
  38. package/src/collectors/fileCollector.ts +24 -0
  39. package/src/collectors/inverseDependencyCollector.ts +32 -0
  40. package/src/config.ts +14 -0
  41. package/src/fileInfoCollector.ts +50 -0
  42. package/src/index.ts +15 -0
  43. package/src/templates/analyse-app.js +988 -0
  44. package/src/templates/analyse.pug +321 -0
  45. package/src/templates/analyseTemplate.ts +45 -0
  46. package/src/templates/icons-inline.js +27 -0
  47. package/src/templates/tailwindcss.js +83 -0
  48. package/src/types.ts +43 -0
  49. package/src/utils/envSetup.ts +18 -0
  50. package/src/utils/fileUtils.ts +21 -0
  51. package/src/writers/fileInfoWriter.ts +44 -0
  52. package/src/writers/inverseDependencyWriter.ts +50 -0
  53. package/src/writers/treeWriter.ts +24 -0
  54. 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,2 @@
1
+ import { FileInfo, TreeData } from '../types';
2
+ export declare function buildTreeStructure(fileInfoArray: FileInfo[], totalSize: number): TreeData;
@@ -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,2 @@
1
+ import { FileInfo } from '../types';
2
+ export declare function collectFileInfo(filePath: string): FileInfo | null;
@@ -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,2 @@
1
+ import { InverseDependencyInfo } from '../types';
2
+ export declare function collectInverseDependencies(filePath: string, inverseDependencies: string[]): InverseDependencyInfo | null;
@@ -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
+ }
@@ -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
+ }
@@ -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;