Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ tmp/

.bundle-discovery/
/lib/.tmp.js

_serializer/README.md
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ There are two ways to install the package:
#### 1. Install (independent tool)

```bash
yarn add -D react-native-bundle-discovery
yarn add -D react-native-bundle-discovery react-native-bundle-discovery-ui
```

Add to your `metro.config.js`:
Expand Down Expand Up @@ -117,15 +117,15 @@ npx react-native bundle \
Run webserver to view the report:

```bash
npx react-native-bundle-discovery server metro-stats.json [--port <port>]
npx react-native-bundle-discovery-ui server metro-stats.json [--port <port>]
```

##### 4.2 Build the HTML report

Run the following command to generate an HTML report from the JSON file:

```bash
npx react-native-bundle-discovery build metro-stats.json
npx react-native-bundle-discovery-ui build metro-stats.json
```


Expand Down
81 changes: 81 additions & 0 deletions _cli/analyze.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
const path = require("path");
const chalk = require("chalk");
const { prepareReport } = require("./prepare.js");
const recommendations = require("./recommendations/index.js");

function readBuildReport(filePath) {
try {
// Resolve from current working directory to support relative CLI paths.
const resolvedPath = path.resolve(filePath);
const report = require(resolvedPath);
return prepareReport(report);
} catch (error) {
throw new Error(`Failed to read report file: ${filePath}\n${error.message}`);
}
}

function collectRecommendations(report) {
return recommendations
.flatMap((recommendation) => {
const finding = recommendation.check(report);
if (!finding) {
return null;
}

return (Array.isArray(finding) ? finding : [finding]).map(f => {
return {
id: recommendation.id,
title: recommendation.title,
...f,
};
});
})
.filter(Boolean);
}

function printDefaultFormat(filePath, findings) {
const prettyPath = chalk.cyan(filePath);

if (findings.length === 0) {
console.log(`${chalk.green("No optimization recommendations found for")} ${prettyPath}.`);
return;
}

const recommendationLabel = findings.length === 1 ? "recommendation" : "recommendations";
console.log(
`${chalk.bold.green("Found")} ${chalk.bold(findings.length)} ${chalk.green(`${recommendationLabel}:`)}`,
);
console.log(`${chalk.dim("Report:")} ${prettyPath}`);
console.log();

findings.forEach((finding, index) => {
console.log(`${chalk.bold.yellow(`${index + 1}.`)} ${chalk.bold(finding.title)}`);

if (finding.message) {
console.log(` ${chalk.blue("Why:")} ${finding.message}`);
}

if (finding.packages && finding.packages.length > 0) {
console.log(` ${chalk.magenta("Packages:")} ${finding.packages}`);
}

if (finding.docsUrl) {
const docs = Array.isArray(finding.docsUrl) ? finding.docsUrl.join(", ") : finding.docsUrl;
console.log(` ${chalk.cyan("Links:")} ${chalk.underline(docs)}`);
}

if (index < findings.length - 1) {
console.log(chalk.dim(" ----------------------------------------"));
}
});
}

function printAnalyzeReport(filePath) {
const report = readBuildReport(filePath);
const findings = collectRecommendations(report);
printDefaultFormat(filePath, findings);
}

module.exports = {
printAnalyzeReport,
};
88 changes: 88 additions & 0 deletions _cli/bin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env node
const minimist = require("minimist");

function printHelp() {
console.log(`react-native-bundle-discovery-cli

Usage:
react-native-bundle-discovery-cli packages <file> [--sort size|name] [--format json|table|default]
react-native-bundle-discovery-cli analyze <file> [--format json|default]
react-native-bundle-discovery-cli analize <file> [--format json|default]

Commands:
packages <file> Print package list from a Metro bundler stat report
analyze <file> Analyze bundle and print optimization recommendations
analize <file> Alias for analyze

Options:
-h, --help Show help
--sort Sort by size (default, desc) or name (asc)
--format Output format: json, table, or default (default: default)
`);
}

function fail(message) {
console.error(message);
console.error("Use --help to see usage.");
process.exit(1);
}

const argv = minimist(process.argv.slice(2), {
alias: {
h: "help",
},
boolean: ["help"],
default: {
sort: "size",
format: "default",
},
});

const command = argv._[0];

if (argv.help || !command) {
printHelp();
process.exit(0);
}

if (command === "packages") {
const file = argv._[1];
const sort = argv.sort;
const format = argv.format;
if (!file) {
fail("Missing required argument: <file>");
}
if (sort !== "size" && sort !== "name") {
fail(`Invalid value for --sort: ${sort}. Expected one of: size, name.`);
}
if (format !== "json" && format !== "table" && format !== "default") {
fail(`Invalid value for --format: ${format}. Expected one of: json, table, default.`);
}

try {
const { printPackagesList } = require("./packages.js");
return printPackagesList(file, { sort, format });
} catch (error) {
fail(error.message);
}
}

if (command === "analyze" || command === "analize") {
const file = argv._[1];
const format = argv.format;
if (!file) {
fail("Missing required argument: <file>");
}
if (format !== "json" && format !== "default") {
fail(`Invalid value for --format: ${format}. Expected one of: json, default.`);
}

try {
const { printAnalyzeReport } = require("./analyze.js");
return printAnalyzeReport(file, { format });
} catch (error) {
fail(error.message);
}
}

fail(`Unknown command: ${command}`);
56 changes: 56 additions & 0 deletions _cli/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* @typedef {Object} TransformOptions
* @property {Record<string, *>} customTransformOptions - Кастомные опции трансформации
* @property {boolean} dev - Флаг режима разработки
* @property {boolean} minify - Флаг минификации кода
* @property {string} platform - Целевая платформа (например, "ios", "android")
* @property {string} type - Тип модуля
* @property {string} unstable_transformProfile - Профиль трансформации
*/

/**
* @typedef {Object} PackageInfo
* @property {string} name - Название пакета
* @property {string} absolutePath - Абсолютный путь к пакету
* @property {string} version - Версия пакета
* @property {string} path - Относительный путь к пакету
*/

/**
* @typedef {Object} SourceInfo
* @property {string} code - Исходный код
* @property {number} lineCount - Количество строк в исходном коде
* @property {number} sizeInBytes - Размер исходного кода в байтах
*/

/**
* @typedef {Object} OutputInfo
* @property {string} code - Скомпилированный/сминифицированный код
* @property {number} lineCount - Количество строк в итоговом коде
* @property {number} sizeInBytes - Размер итогового кода в байтах
*/

/**
* @typedef {Object} ModuleInfo
* @property {string} path - Относительный путь к модулю
* @property {SourceInfo} source - Информация об исходном коде
* @property {OutputInfo} output - Информация о собранном коде
* @property {Array<*>} dependencies - Список зависимостей модуля
* @property {string} absolutePath - Абсолютный путь к файлу модуля
* @property {Array<*>} duplicates - Дубликаты модуля
* @property {ModuleInfo[]} dependents - Модули, зависящие от данного (рекурсивный тип)
*/

/**
* @typedef {Object} BuildReport
* @property {number} date - Timestamp даты сборки
* @property {string} entryPoint - Абсолютный путь к точке входа
* @property {TransformOptions} transformOptions - Параметры трансформации
* @property {Record<string, string>} envs - Переменные окружения
* @property {string} rootFolder - Абсолютный путь к корневой папке проекта
* @property {PackageInfo[]} packages - Список используемых пакетов
* @property {ModuleInfo[]} modules - Список обработанных модулей
*/

/** @type {BuildReport} */
const report = require("./tmp/metro-stats.json");
21 changes: 21 additions & 0 deletions _cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "react-native-bundle-discovery-cli",
"version": "2.0.0-rc.1",
"main": "index.js",
"bin": "bin.js",
"repository": "git@github.com:retyui/react-native-bundle-discovery.git",
"author": "David <4661784+retyui@users.noreply.github.com>",
"license": "MIT",
"dependencies": {
"minimist": "^1.2.8",
"chalk": "^4.1.2"
},
"files": [
"index.js",
"bin.js",
"analyze.js",
"packages.js",
"prepare.js",
"recommendations"
]
}
Loading