Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add include_size_comparison option #82

Merged
merged 2 commits into from
Aug 14, 2024
Merged
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
23 changes: 11 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ jobs:
metafiles: "out/meta.json"
```


### GitHub Action setup for public repositories

If your repository is public and you want to run this action on PRs from forks, you may need to use `pull_request_target` event.
Expand Down Expand Up @@ -154,17 +153,17 @@ Please check the above setup example to use this action with `pull_request_targe

## Action inputs

| Name | Default | Description |
|---------------------------|---------------------------------------|------------------------------------------------------------------------------------------------------------------|
| `metafiles` | - | A required comma-separated list of paths to [esbuild's meta file]([https://esbuild.github.io/api/#metafile]). Glob (`dist/**/meta.json`) is supported. |
| `name` | ${{ github.event.<br>repository.name }} | The name of your project. This will be used in the comment header. |
| `analyze_directory` | `.analyzer` | A path to working directory where bundle analysis are stored. |
| `include_extensions` | `.js,.cjs,.mjs` | A comma-separated list of file extension to be included in the analysis table. |
| `percent_extra_attention` | `20` | If an out file size has increased more than this percent, display a "‼️" to draw extra attention to the change. |
| `show_details` | `true` | If `true`, a collapsed "details" section is rendered. It explains the details of the numbers provided and icons. |
| `show_no_change` | `true` | If `true`, all bundles are shown in the analysis regardless of size change. If `false`, only bundles with size changes are shown. |
| `show_total_changes` | `false` | If `true`, the cumulative number of changes in bundle sizes is rendered. |
| `top_n_largest_paths` | `20` | The number of largest paths (e.g.) `node_modules/foo`) to be collected. If 0 or lower, skipped. |
| Name | Default | Description |
|---------------------------|---------------------------------------------------|------------------------------------------------------------------------------------------------------------------|
| `metafiles` | - | A required comma-separated list of paths to [esbuild's meta file]([https://esbuild.github.io/api/#metafile]). Glob (`dist/**/meta.json`) is supported. |
| `name` | ${{ github.event.<br>repository.name }} | The name of your project. This will be used in the comment header. |
| `analyze_directory` | `.analyzer` | A path to working directory where bundle analysis are stored. |
| `include_extensions` | `.js,.cjs,.mjs` | A comma-separated list of file extension to be included in the analysis table. |
| `percent_extra_attention` | `20` | If an out file size has increased more than this percent, display a "‼️" to draw extra attention to the change. |
| `include_size_comparison` | `added, deleted, increased, decreased, no-change` | A comma-separated list of size comparison items to be displayed.<br>Available filter are `total`, `added`, `deleted`, `increased`, `decreased` and `no-change`.<br>If you are not interested in some of them, you can remove them from the list. |
| `show_details` | `true` | If `true`, a collapsed "details" section is rendered. It explains the details of the numbers provided and icons. |
| `show_no_change` | `true` | [DEPRECATED] Use the `include_size_comparison` list to show/hide `no-change`.<br>If `true`, all bundles are shown in the analysis regardless of size change. If `false`, only bundles with size changes are shown. |
| `top_n_largest_paths` | `20` | The number of largest paths (e.g.) `node_modules/foo`) to be collected. If 0 or lower, skipped. |

## Action outputs

Expand Down
9 changes: 7 additions & 2 deletions __tests__/no-base.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@ describe("examples w/o base analysis", () => {
analyzerDirectory: ".analyzer",
percentExtraAttention: 20,
includeExtensions: [".js", ".mjs", ".cjs"],
includeSizeComparison: new Set([
"added",
"deleted",
"increased",
"decreased",
"no-change",
]),
metafiles: ["out/**/meta.json"],
name: "test",
showDetails: false,
showNoChange: true,
topNLargestPaths: 10,
showTotalChanges: false,
};

beforeEach(() => {
Expand Down
22 changes: 17 additions & 5 deletions __tests__/with-base.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { execSync } from "node:child_process";
import path from "node:path";

import { run } from "../src/index";
import type { Input } from "../src/types";
import type { Input, SizeComparisonFilter } from "../src/types";
import {
getExampleDirectories,
readAnalysisComment,
Expand All @@ -26,16 +26,28 @@ describe("examples w/ base analysis", () => {
: ["out/meta.json"];

const isEven = index % 2 === 0;

const includeSizeComparison = new Set<SizeComparisonFilter>([
"total",
"added",
"deleted",
"increased",
"decreased",
"no-change",
]);
if (!isEven) {
includeSizeComparison.delete("total");
includeSizeComparison.delete("no-change");
}
const input: Input = {
analyzerDirectory: ".analyzer",
percentExtraAttention: 20,
includeExtensions: [".js", ".mjs", ".cjs"],
metafiles,
name: "test",
showDetails: false,
showNoChange: isEven,
topNLargestPaths: 10,
showTotalChanges: isEven,
includeSizeComparison,
};

beforeEach(() => {
Expand All @@ -60,10 +72,10 @@ describe("examples w/ base analysis", () => {
expect(comment).toMatch(/✅ {2}-\d+/);
expect(comment).toMatch(/✅ {2}No change/i);
if (isEven) {
expect(comment).not.toMatch(/\d bundles with no change are hidden./i);
expect(comment).not.toMatch(/\d bundles are hidden./i);
expect(comment).toMatch(/\(Total\) \| - \|.+⚠️/i);
} else {
expect(comment).toMatch(/\d bundles with no change are hidden./i);
expect(comment).toMatch(/\d bundles are hidden./i);
expect(comment).not.toMatch(/\(Total\) \| - \|.+⚠️/i);
}
expect(comment).toMatch(/🆕 Added/i);
Expand Down
10 changes: 7 additions & 3 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,13 @@ inputs:
description: |
If an out file size has increased more than this percent, display a "‼️" to draw attention
to the change.
show_total_changes:
include_size_comparison:
required: false
default: "false"
default: "added, deleted, increased, decreased, no-change"
description: |
If `true`, the cumulative number of changes in bundle sizes is rendered.
A comma-separated list of size comparison items to be displayed.
Available filter are `total`, `added`, `deleted`, `increased`, `decreased` and `no-change`.
If you are not interested in some of them, you can remove them from the list.
show_details:
required: false
default: "true"
Expand All @@ -62,6 +64,8 @@ inputs:
show_no_change:
required: false
default: "true"
deprecationMessage: |
Use the `include_size_comparison` list to show/hide `no-change`.
description: |
If `true`, all bundles are shown in the analysis regardless of size change. If `false`, only bundles with size changes are shown.
top_n_largest_paths:
Expand Down
30 changes: 15 additions & 15 deletions dist/index.mjs

Large diffs are not rendered by default.

67 changes: 43 additions & 24 deletions src/compare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import * as console from "node:console";
import fs from "node:fs";
import path from "node:path";
import { findMetafiles } from "./report";
import type { CompareResult, Input, Report, TreeMapNode } from "./types";
import type {
CompareResult,
Input,
Report,
SizeComparisonFilter,
TreeMapNode,
} from "./types";
import { loadAnalysisJson, loadMetaFile } from "./utils";

export function compare(input: Input): void {
Expand Down Expand Up @@ -63,19 +69,23 @@ This analysis was generated by [esbuild-bundle-analyzer](https://github.com/exoe
...currentStats,
baseBytes: baseStats.bytes,
tree,
remark: Math.sign(diff) ? "increased" : "decreased",
remark:
diff === 0 ? "no-change" : Math.sign(diff) ? "increased" : "decreased",
};
});
console.log("Comparison done.", comparison);

if (hasAnyChange) {
output += markdownTable(
comparison,
input.includeSizeComparison,
input.percentExtraAttention,
);
output += hiddenTable(
comparison,
input.includeSizeComparison,
input.percentExtraAttention,
input.showTotalChanges,
input.showNoChange,
);
output += noChangesTable(comparison, input.showNoChange);
output += fileSizeTable(comparison, input.topNLargestPaths);
output += detail(input);
} else {
Expand Down Expand Up @@ -205,6 +215,7 @@ function buildFileTree(input: Input) {
}

const spacer = " ";

function filesize(bytes: number): string {
const sign = bytes < 0 ? "-" : "";
const n = Math.abs(bytes);
Expand All @@ -228,9 +239,8 @@ const shouldShowBundle = (d: CompareResult, showNoChange: boolean) =>

function markdownTable(
data: Array<CompareResult>,
sizeComparisonFilters: Set<SizeComparisonFilter>,
redThreshold: number,
showTotalChanges: boolean,
showNoChange: boolean,
): string {
const totalRow = data.reduce(
(acc, d) => {
Expand All @@ -252,9 +262,13 @@ function markdownTable(
);
totalRow.remark =
totalRow.bytes > totalRow.baseBytes ? "increased" : "decreased";
const totalRows: Array<CompareResult> = showTotalChanges ? [totalRow] : [];
const totalRows: Array<CompareResult> = sizeComparisonFilters.has("total")
? [totalRow]
: [];

const individualRows = data.filter((d) => shouldShowBundle(d, showNoChange));
const individualRows = data.filter((d) =>
shouldShowBundle(d, sizeComparisonFilters.has(d.remark)),
);
const rows = [...totalRows, ...individualRows]
.map((d) => {
return `${d.metafile} | ${d.outfile} | ${renderSize(d)} | ${renderNote(
Expand All @@ -270,24 +284,28 @@ Meta File | Out File | Size (raw) | Note
${rows}`;
}

function noChangesTable(
function hiddenTable(
data: Array<CompareResult>,
showNoChange: boolean,
includeSizeComparison: Set<SizeComparisonFilter>,
redThreshold: number,
): string {
const noChangeBundles = data.filter(
(d) => !shouldShowBundle(d, showNoChange),
const hiddenBundles = data.filter(
(d) => !includeSizeComparison.has(d.remark),
);
const rows = noChangeBundles
const rows = hiddenBundles
.map((d) => {
return `${d.metafile} | ${d.outfile} | ${renderSize(d)} | ✅ No change\n`;
return `${d.metafile} | ${d.outfile} | ${renderSize(d)} | ${renderNote(
d,
redThreshold,
)}\n`;
})
.join("");
if (noChangeBundles.length === 0) {
if (hiddenBundles.length === 0) {
return "";
}
return `
<details>
<summary>${noChangeBundles.length} bundles with no change are hidden.</summary>
<summary>${hiddenBundles.length} bundles are hidden since not listed in include_size_comparison.</summary>

Meta File | Out File | Size (raw) | Note
----------|----------|-----------:|------
Expand Down Expand Up @@ -384,6 +402,7 @@ function renderBar(percent: number, bytes: number): string {
// Block progression is 1/8 = 0.125
const blocks = ["", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"];
const progression = 1 / (blocks.length - 1);

function progress(value: number, length = 25, vmin = 0.0, vmax = 1.0) {
const v = value * length;
const integerPart = Math.floor(v);
Expand All @@ -405,14 +424,14 @@ function renderNote(d: CompareResult, redThreshold: number): string {
if (d.remark === "added") {
return "🆕 Added";
}
const diff = d.bytes - d.baseBytes;
if (diff !== 0) {
const percentChange = (diff / d.baseBytes) * 100;
return `${renderStatusIndicator(percentChange, redThreshold)}${filesize(
diff,
)} (${sign(percentChange)}${percentChange.toFixed(1)}%)`;
if (d.remark === "no-change") {
return "✅ No change";
}
return "✅ No change";
const diff = d.bytes - d.baseBytes;
const percentChange = (diff / d.baseBytes) * 100;
return `${renderStatusIndicator(percentChange, redThreshold)}${filesize(
diff,
)} (${sign(percentChange)}${percentChange.toFixed(1)}%)`;
}

function sign(num: number): string {
Expand Down
40 changes: 37 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import console from "node:console";
import { pathToFileURL } from "node:url";
import { compare } from "./compare";
import { report } from "./report";
import type { Input } from "./types";
import type { Input, SizeComparisonFilter } from "./types";
import { getBooleanInput, getNumberInput, getSingleInput } from "./utils";

function getInput(): Input {
Expand All @@ -13,15 +14,48 @@ function getInput(): Input {
if (!name) {
throw new Error("name is not specified");
}
const filters = new Set<SizeComparisonFilter>(
(
getSingleInput("include_size_comparison") ||
"added, deleted, increased, decreased, no-change"
)
.split(",")
.map((s) => {
switch (s.trim()) {
case "added":
case "deleted":
case "increased":
case "decreased":
case "total":
case "no-change":
return s.trim() as SizeComparisonFilter;
default:
throw new Error(`Unknown size comparison filter: ${s}`);
}
}),
);
const rawShowNoChange = getSingleInput("show_no_change");
if (rawShowNoChange !== "") {
if (getBooleanInput("show_no_change", "true")) {
filters.delete("no-change");
console.log(
"`show_no_change: true` is deprecated. Instead, remove `no-change` from the `include_size_comparison` list.",
);
} else {
filters.add("no-change");
console.log(
"`show_no_change: false` is deprecated. Instead, add `no-change` to the `include_size_comparison` list.",
);
}
}
return {
percentExtraAttention: getNumberInput("percent_extra_attention", 20),
showDetails: getBooleanInput("show_details", "true"),
showNoChange: getBooleanInput("show_no_change", "true"),
showTotalChanges: getBooleanInput("show_total_changes", "false"),
topNLargestPaths: getNumberInput("top_n_largest_paths", 20),
includeExtensions: (
getSingleInput("include_extensions") || ".js,.mjs,.cjs"
).split(","),
includeSizeComparison: filters,
name,
analyzerDirectory: getSingleInput("analyze_directory") || ".analyzer",
metafiles: rawMetafiles.split(","),
Expand Down
7 changes: 4 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,21 @@ export interface CompareResult {
outfile: string;
bytes: number;
baseBytes: number;
remark: "added" | "deleted" | "increased" | "decreased";
remark: "added" | "deleted" | "increased" | "decreased" | "no-change";
tree: TreeMapNode | undefined;
}

export type SizeComparisonFilter = CompareResult["remark"] | "total";

export interface Input {
name: string;
metafiles: Array<string>;
includeExtensions: Array<string>;
includeSizeComparison: Set<SizeComparisonFilter>;
topNLargestPaths: number;
analyzerDirectory: string;
percentExtraAttention: number;
showDetails: boolean;
showNoChange: boolean;
showTotalChanges: boolean;
}

export interface TreeMapNode {
Expand Down