From 2f288b451640f0f54f15ef4124e1e474b32b15c0 Mon Sep 17 00:00:00 2001 From: Peter Damoc Date: Mon, 15 Aug 2022 19:10:11 +0300 Subject: [PATCH] Add support for summary --- README.md | 6 +- bin/analyse.js | 288 ++++++++++++++++++++++++++++++++++++++++++++++ bin/index.js | 14 +++ package-lock.json | 31 +++-- package.json | 6 +- src/analyse.ts | 101 ++++++++++++---- src/index.ts | 9 +- tsconfig.json | 8 +- yarn.lock | 13 ++- 9 files changed, 429 insertions(+), 47 deletions(-) create mode 100644 bin/analyse.js create mode 100755 bin/index.js diff --git a/README.md b/README.md index 12929d9..62e3b8c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,6 @@ node dist/index.js analyze ## TODO -- Allow giving .js file by options or otherwise by file input (inquirer.js?) -- Show results by package/module/file level -- NPM Publish +- Allow giving .js file by options or otherwise by file input (inquirer.js?) +- Show results by package/module/file level +- NPM Publish diff --git a/bin/analyse.js b/bin/analyse.js new file mode 100644 index 0000000..a8508df --- /dev/null +++ b/bin/analyse.js @@ -0,0 +1,288 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __values = (this && this.__values) || function(o) { + var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; + if (m) return m.call(o); + if (o && typeof o.length === "number") return { + next: function () { + if (o && i >= o.length) o = void 0; + return { value: o && o[i++], done: !o }; + } + }; + throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); +}; +var __read = (this && this.__read) || function (o, n) { + var m = typeof Symbol === "function" && o[Symbol.iterator]; + if (!m) return o; + var i = m.call(o), r, ar = [], e; + try { + while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); + } + catch (error) { e = { error: error }; } + finally { + try { + if (r && !r.done && (m = i["return"])) m.call(i); + } + finally { if (e) throw e.error; } + } + return ar; +}; +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +}; +import * as FS from "fs"; +import * as Console from "console"; +import { promisify } from "util"; +import * as Esprima from "esprima"; +function yieldElmJsDefinitionInfos(script) { + var _a, _b, statement, e_1_1; + var e_1, _c; + return __generator(this, function (_d) { + switch (_d.label) { + case 0: + _d.trys.push([0, 6, 7, 8]); + _a = __values(yieldElmJsStatements(script)), _b = _a.next(); + _d.label = 1; + case 1: + if (!!_b.done) return [3 /*break*/, 5]; + statement = _b.value; + return [5 /*yield**/, __values(yieldFunctionDeclarationInfo(statement))]; + case 2: + _d.sent(); + return [5 /*yield**/, __values(yieldVariableDeclarationInfos(statement))]; + case 3: + _d.sent(); + _d.label = 4; + case 4: + _b = _a.next(); + return [3 /*break*/, 1]; + case 5: return [3 /*break*/, 8]; + case 6: + e_1_1 = _d.sent(); + e_1 = { error: e_1_1 }; + return [3 /*break*/, 8]; + case 7: + try { + if (_b && !_b.done && (_c = _a["return"])) _c.call(_a); + } + finally { if (e_1) throw e_1.error; } + return [7 /*endfinally*/]; + case 8: return [2 /*return*/]; + } + }); +} +function yieldElmJsStatements(script) { + var _a, _b, e, e_2_1; + var e_2, _c; + return __generator(this, function (_d) { + switch (_d.label) { + case 0: + _d.trys.push([0, 5, 6, 7]); + _a = __values(script.body), _b = _a.next(); + _d.label = 1; + case 1: + if (!!_b.done) return [3 /*break*/, 4]; + e = _b.value; + if (!(e.type === "ExpressionStatement" && + e.expression.type === "CallExpression" && + e.expression.callee.type === "FunctionExpression")) return [3 /*break*/, 3]; + return [5 /*yield**/, __values(e.expression.callee.body.body)]; + case 2: + _d.sent(); + _d.label = 3; + case 3: + _b = _a.next(); + return [3 /*break*/, 1]; + case 4: return [3 /*break*/, 7]; + case 5: + e_2_1 = _d.sent(); + e_2 = { error: e_2_1 }; + return [3 /*break*/, 7]; + case 6: + try { + if (_b && !_b.done && (_c = _a["return"])) _c.call(_a); + } + finally { if (e_2) throw e_2.error; } + return [7 /*endfinally*/]; + case 7: return [2 /*return*/]; + } + }); +} +function yieldFunctionDeclarationInfo(statement) { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!(statement.type === "FunctionDeclaration")) return [3 /*break*/, 2]; + return [4 /*yield*/, { + name: statement.id.name, + range: statement.range + }]; + case 1: + _a.sent(); + _a.label = 2; + case 2: return [2 /*return*/]; + } + }); +} +function yieldVariableDeclarationInfos(statement) { + var _a, _b, declaration, e_3_1; + var e_3, _c; + return __generator(this, function (_d) { + switch (_d.label) { + case 0: + if (!(statement.type === "VariableDeclaration")) return [3 /*break*/, 8]; + _d.label = 1; + case 1: + _d.trys.push([1, 6, 7, 8]); + _a = __values(statement.declarations), _b = _a.next(); + _d.label = 2; + case 2: + if (!!_b.done) return [3 /*break*/, 5]; + declaration = _b.value; + if (!(declaration.id.type === "Identifier")) return [3 /*break*/, 4]; + return [4 /*yield*/, { + name: declaration.id.name, + range: declaration.range + }]; + case 3: + _d.sent(); + _d.label = 4; + case 4: + _b = _a.next(); + return [3 /*break*/, 2]; + case 5: return [3 /*break*/, 8]; + case 6: + e_3_1 = _d.sent(); + e_3 = { error: e_3_1 }; + return [3 /*break*/, 8]; + case 7: + try { + if (_b && !_b.done && (_c = _a["return"])) _c.call(_a); + } + finally { if (e_3) throw e_3.error; } + return [7 /*endfinally*/]; + case 8: return [2 /*return*/]; + } + }); +} +function rangeSize(_a) { + var _b = __read(_a, 2), start = _b[0], end = _b[1]; + return end - start; +} +function pct(value, of) { + return "".concat(Number((value / of) * 100).toFixed(3), "%"); +} +function moduleName(name) { + return !name.startsWith("$") + ? "Kernel Code" + : name.slice(0, name.lastIndexOf("$")); +} +function packageName(name) { + return !name.startsWith("$") + ? "Kernel Code" + : name.split("$").slice(1, 3).join("/"); +} +function localName(name) { + return !name.startsWith("$author") + ? "Dependencies" + : name.slice(16, name.lastIndexOf("$")).replaceAll("$", "."); +} +export function analyse(elmOutputJsFilePath, opts) { + return __awaiter(this, void 0, void 0, function () { + var file, parsed, size, infos, summary, entries, rangeSum; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, promisify(FS.readFile)(elmOutputJsFilePath)]; + case 1: + file = _a.sent(); + parsed = Esprima.parseScript(file.toString(), { range: true }); + size = parsed.range; + infos = Array.from(yieldElmJsDefinitionInfos(parsed)); + if (opts.summary != undefined) { + summary = infos.reduce(function (prev, _a) { + var _b; + var name = _a.name, range = _a.range; + var key; + switch (opts.summary) { + case "package": { + key = packageName(name); + break; + } + case "project": { + key = localName(name); + break; + } + default: { + key = moduleName(name); + break; + } + } + prev.set(key, ((_b = prev.get(key)) !== null && _b !== void 0 ? _b : 0) + rangeSize(range)); + return prev; + }, new Map()); + entries = __spreadArray([], __read(summary.entries()), false); + entries.sort(function (a, b) { return b[1] - a[1]; }); + entries.forEach(function (_a) { + var _b = __read(_a, 2), name = _b[0], range = _b[1]; + Console.log("".concat(pct(range, rangeSize(size)), ": ").concat(name)); + }); + } + else { + infos.sort(function (a, b) { return rangeSize(b.range) - rangeSize(a.range); }); + infos.forEach(function (_a) { + var name = _a.name, range = _a.range; + Console.log("".concat(pct(rangeSize(range), rangeSize(size)), ": ").concat(name)); + }); + } + rangeSum = infos + .map(function (_a) { + var range = _a.range; + return rangeSize(range); + }) + .reduce(function (a, b) { return a + b; }, 0); + Console.log("Range sum: ".concat(rangeSum, " total: ").concat(rangeSize(size), ", analized ").concat(pct(rangeSum, rangeSize(size)))); + return [2 /*return*/]; + } + }); + }); +} diff --git a/bin/index.js b/bin/index.js new file mode 100755 index 0000000..ebeb72b --- /dev/null +++ b/bin/index.js @@ -0,0 +1,14 @@ +#!/usr/bin/env node +import { program } from "commander"; +import { analyse } from "./analyse.js"; +program + .name("elmjs-inspector") + .description("Analyse your elm.js file size with this tool.") + .version("1.0.0"); +program + .option("-s, --summary [value]", "Display summary. Possible values: module, package, project") + .argument("", "The file to analyze") + .action(function (filename, opts) { + analyse(filename, opts); +}); +program.parse(); diff --git a/package-lock.json b/package-lock.json index 4a807e9..b380069 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,14 +9,18 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "commander": "^9.2.0", "esprima": "^4.0.1", "node-estree": "^3.1.0" }, + "bin": { + "elmjs-inspector": "src/index.ts" + }, "devDependencies": { "@types/esprima": "^4.0.2", "@types/estree": "^0.0.51", "@types/node": "^13.11.0", - "typescript": "^3.8.3" + "typescript": "^4.7.4" } }, "node_modules/@slimio/is": { @@ -48,6 +52,14 @@ "integrity": "sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ==", "dev": true }, + "node_modules/commander": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", + "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -69,9 +81,9 @@ } }, "node_modules/typescript": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", - "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -109,6 +121,11 @@ "integrity": "sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ==", "dev": true }, + "commander": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", + "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==" + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -123,9 +140,9 @@ } }, "typescript": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", - "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true } } diff --git a/package.json b/package.json index faaeafa..b2b4938 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "main": "src/index.ts", "bin": { - "elmjs-inspector": "src/index.ts" + "elmjs-inspect": "bin/index.js" }, "license": "MIT", "scripts": { @@ -15,11 +15,11 @@ "@types/esprima": "^4.0.2", "@types/estree": "^0.0.51", "@types/node": "^13.11.0", - "typescript": "^3.8.3" + "typescript": "^4.7.4" }, "dependencies": { "commander": "^9.2.0", "esprima": "^4.0.1", "node-estree": "^3.1.0" } -} \ No newline at end of file +} diff --git a/src/analyse.ts b/src/analyse.ts index 96e4fb6..0f48ec2 100644 --- a/src/analyse.ts +++ b/src/analyse.ts @@ -1,8 +1,8 @@ -import * as FS from 'fs'; -import * as Console from 'console'; -import { promisify } from 'util'; -import * as Esprima from 'esprima'; -import * as ESTree from 'estree'; +import * as FS from "fs"; +import * as Console from "console"; +import { promisify } from "util"; +import * as Esprima from "esprima"; +import * as ESTree from "estree"; function* yieldElmJsDefinitionInfos(script: Esprima.Program) { for (const statement of yieldElmJsStatements(script)) { @@ -13,30 +13,32 @@ function* yieldElmJsDefinitionInfos(script: Esprima.Program) { function* yieldElmJsStatements(script: Esprima.Program) { for (const e of script.body) { - if (e.type === 'ExpressionStatement' - && e.expression.type === 'CallExpression' - && e.expression.callee.type === 'FunctionExpression') { + if ( + e.type === "ExpressionStatement" && + e.expression.type === "CallExpression" && + e.expression.callee.type === "FunctionExpression" + ) { yield* e.expression.callee.body.body; } - }; + } } function* yieldFunctionDeclarationInfo(statement: ESTree.Statement) { - if (statement.type === 'FunctionDeclaration') { + if (statement.type === "FunctionDeclaration") { yield { name: statement.id.name, - range: statement.range + range: statement.range, }; } } function* yieldVariableDeclarationInfos(statement: ESTree.Statement) { - if (statement.type === 'VariableDeclaration') { + if (statement.type === "VariableDeclaration") { for (const declaration of statement.declarations) { - if (declaration.id.type === 'Identifier') { + if (declaration.id.type === "Identifier") { yield { name: declaration.id.name, - range: declaration.range + range: declaration.range, }; } } @@ -48,18 +50,73 @@ function rangeSize([start, end]) { } function pct(value: number, of: number) { - return `${Number(value / of * 100).toFixed(3)}%` + return `${Number((value / of) * 100).toFixed(3)}%`; } -export async function analyse(elmOutputJsFilePath: string) { +function moduleName(name: string) { + return !name.startsWith("$") + ? "Kernel Code" + : name.slice(0, name.lastIndexOf("$")); +} + +function packageName(name: string) { + return !name.startsWith("$") + ? "Kernel Code" + : name.split("$").slice(1, 3).join("/"); +} +function localName(name: string) { + return !name.startsWith("$author") + ? "Dependencies" + : name.slice(16, name.lastIndexOf("$")).replaceAll("$", "."); +} + +export async function analyse(elmOutputJsFilePath: string, opts: any) { const file = await promisify(FS.readFile)(elmOutputJsFilePath); const parsed = Esprima.parseScript(file.toString(), { range: true }); const size = parsed.range; const infos = Array.from(yieldElmJsDefinitionInfos(parsed)); - infos.sort((a, b) => rangeSize(b.range) - rangeSize(a.range)); - infos.forEach(({ name, range }) => { - Console.log(`${pct(rangeSize(range), rangeSize(size))}: ${name}`); - }); - const rangeSum = infos.map(({ range }) => rangeSize(range)).reduce((a, b) => a + b, 0); - Console.log(`Range sum: ${rangeSum} total: ${rangeSize(size)}, analyzed ${pct(rangeSum, rangeSize(size))}`); + + if (opts.summary != undefined) { + const summary = infos.reduce((prev, { name, range }) => { + let key; + + switch (opts.summary) { + case "package": { + key = packageName(name); + break; + } + case "project": { + key = localName(name); + break; + } + default: { + key = moduleName(name); + break; + } + } + + prev.set(key, (prev.get(key) ?? 0) + rangeSize(range)); + + return prev; + }, new Map()); + const entries = [...summary.entries()]; + entries.sort((a, b) => b[1] - a[1]); + entries.forEach(([name, range]) => { + Console.log(`${pct(range, rangeSize(size))}: ${name}`); + }); + } else { + infos.sort((a, b) => rangeSize(b.range) - rangeSize(a.range)); + infos.forEach(({ name, range }) => { + Console.log(`${pct(rangeSize(range), rangeSize(size))}: ${name}`); + }); + } + const rangeSum = infos + .map(({ range }) => rangeSize(range)) + .reduce((a, b) => a + b, 0); + Console.log( + `Range sum: ${rangeSum} total: ${rangeSize(size)}, analized ${pct( + rangeSum, + rangeSize(size) + )}` + ); } diff --git a/src/index.ts b/src/index.ts index e689d5e..79dc1a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,10 +9,13 @@ program .version("1.0.0"); program - .command("analyze") + .option( + "-s, --summary [value]", + "Display summary. Possible values: module, package, project" + ) .argument("", "The file to analyze") - .action((filename) => { - analyse(filename); + .action((filename, opts) => { + analyse(filename, opts); }); program.parse(); diff --git a/tsconfig.json b/tsconfig.json index 2006e81..a1b9fdf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,10 +4,8 @@ "module": "ES6", "downlevelIteration": true, "moduleResolution": "node", - "outDir": "dist" + "outDir": "bin", + "lib": ["ES2021.String"] }, - "files": [ - "src/analyse.ts", - "src/index.ts" - ] + "files": ["src/analyse.ts", "src/index.ts"] } diff --git a/yarn.lock b/yarn.lock index b4abf7c..ceea3dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24,6 +24,11 @@ "resolved" "https://registry.npmjs.org/@types/node/-/node-13.11.0.tgz" "version" "13.11.0" +"commander@^9.2.0": + "integrity" "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==" + "resolved" "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz" + "version" "9.4.0" + "esprima@^4.0.1": "integrity" "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" "resolved" "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" @@ -36,7 +41,7 @@ dependencies: "@slimio/is" "^1.5.1" -"typescript@^3.8.3": - "integrity" "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==" - "resolved" "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz" - "version" "3.8.3" +"typescript@^4.7.4": + "integrity" "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==" + "resolved" "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz" + "version" "4.7.4"