@node-cli/bundlesize 4.3.0 → 4.5.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 CHANGED
@@ -14,6 +14,39 @@
14
14
 
15
15
  A configuration file must be provided via the `-c` parameter.
16
16
 
17
+ ### JSON Schema Support
18
+
19
+ For IDE autocompletion and validation, you can add a `$schema` property to your configuration file:
20
+
21
+ ```js
22
+ export default {
23
+ $schema:
24
+ "https://cdn.jsdelivr.net/npm/@node-cli/bundlesize/schemas/bundlesize.config.schema.json",
25
+ sizes: [
26
+ {
27
+ path: "dist/bundle.js",
28
+ limit: "10 kB"
29
+ }
30
+ ]
31
+ };
32
+ ```
33
+
34
+ This provides:
35
+
36
+ - Autocompletion for configuration options
37
+ - Inline documentation for each property
38
+ - Validation of configuration values
39
+
40
+ **Note**: For a specific version of the schema, use:
41
+
42
+ ```
43
+ https://cdn.jsdelivr.net/npm/@node-cli/bundlesize@VERSION/schemas/bundlesize.config.schema.json
44
+ ```
45
+
46
+ ### Configuration Options
47
+
48
+ ### Configuration Options
49
+
17
50
  For the size option, it must export an object named "size" which is an array of objects with the following properties:
18
51
 
19
52
  - `path`: the path to the file to check
@@ -26,6 +59,7 @@ For the report option, it must export an object named "report" which is an objec
26
59
  - `previous`: the previous path to the report to compare against (required, string)
27
60
  - `current`: the current path to the report to compare against (required, string)
28
61
  - `columns`: the columns to display (optional, array of objects)
62
+ - `threshold`: the minimum gzip size change in bytes to consider as a change (optional, number, defaults to 0). Changes below this threshold are treated as no change.
29
63
 
30
64
  ## Examples
31
65
 
@@ -184,6 +218,22 @@ export default {
184
218
  };
185
219
  ```
186
220
 
221
+ #### Simple report with custom threshold
222
+
223
+ By default, all gzip size changes are reported. You can set a threshold to ignore small changes:
224
+
225
+ ```js
226
+ export default {
227
+ report: {
228
+ threshold: 10, // ignore changes smaller than 10 bytes
229
+ prev: "stats/previous.json",
230
+ current: "stats/current.json"
231
+ }
232
+ };
233
+ ```
234
+
235
+ **Note on threshold with subgroups**: When using subgroup headers, the threshold is applied consistently. If individual file changes are below the threshold (and thus treated as zero), the sub-bundle total will also reflect this. For example, if file A changes by +3 bytes and file B changes by +4 bytes with a threshold of 5, both individual changes are ignored, and the sub-bundle will show no change (even though the raw total would be +7 bytes).
236
+
187
237
  #### Report with subgroup headers (multiple tables)
188
238
 
189
239
  You can interleave header objects inside the `sizes` array to break the report output into multiple markdown tables. Each header object must contain a `header` property (any markdown heading is allowed). A sub-bundle total line will be printed after each group along with its diff to previous stats (if available), followed by the overall bundle size and status at the end.
@@ -5,3 +5,4 @@ export declare const defaultFlags: {
5
5
  type: string;
6
6
  force: boolean;
7
7
  };
8
+ export declare const DEFAULT_THRESHOLD = 0;
package/dist/defaults.js CHANGED
@@ -5,5 +5,6 @@
5
5
  type: "size",
6
6
  force: false
7
7
  };
8
+ export const DEFAULT_THRESHOLD = 0;
8
9
 
9
10
  //# sourceMappingURL=defaults.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/defaults.ts"],"sourcesContent":["/* istanbul ignore file */\n\nexport const defaultFlags = {\n\tboring: false,\n\tconfiguration: \"\",\n\tsilent: false,\n\ttype: \"size\",\n\tforce: false,\n};\n"],"names":["defaultFlags","boring","configuration","silent","type","force"],"mappings":"AAAA,wBAAwB,GAExB,OAAO,MAAMA,eAAe;IAC3BC,QAAQ;IACRC,eAAe;IACfC,QAAQ;IACRC,MAAM;IACNC,OAAO;AACR,EAAE"}
1
+ {"version":3,"sources":["../src/defaults.ts"],"sourcesContent":["/* istanbul ignore file */\n\nexport const defaultFlags = {\n\tboring: false,\n\tconfiguration: \"\",\n\tsilent: false,\n\ttype: \"size\",\n\tforce: false,\n};\n\nexport const DEFAULT_THRESHOLD = 0;\n"],"names":["defaultFlags","boring","configuration","silent","type","force","DEFAULT_THRESHOLD"],"mappings":"AAAA,wBAAwB,GAExB,OAAO,MAAMA,eAAe;IAC3BC,QAAQ;IACRC,eAAe;IACfC,QAAQ;IACRC,MAAM;IACNC,OAAO;AACR,EAAE;AAEF,OAAO,MAAMC,oBAAoB,EAAE"}
@@ -1,5 +1,6 @@
1
1
  import { basename, dirname, join } from "node:path";
2
2
  import bytes from "bytes";
3
+ import { DEFAULT_THRESHOLD } from "./defaults.js";
3
4
  import { addMDrow, formatFooter, getMostRecentVersion, getOutputFile, percentFormatter, readJSONFile, validateConfigurationFile } from "./utilities.js";
4
5
  const isHeaderEntry = (entry)=>"header" in entry;
5
6
  export const reportStats = async ({ flags })=>{
@@ -44,6 +45,7 @@ export const reportStats = async ({ flags })=>{
44
45
  let limitReached = false;
45
46
  let overallDiff = 0;
46
47
  let totalGzipSize = 0;
48
+ const threshold = configuration.report.threshold ?? DEFAULT_THRESHOLD;
47
49
  const headerString = configuration.report.header || "## Bundle Size";
48
50
  const footerFormatter = configuration.report.footer || formatFooter;
49
51
  const columns = configuration.report.columns || [
@@ -103,13 +105,15 @@ export const reportStats = async ({ flags })=>{
103
105
  }
104
106
  }
105
107
  let subGroupAccumulatedSize = 0;
108
+ let subGroupAccumulatedDiff = 0;
106
109
  let subGroupAccumulatedPrevSize = 0;
107
110
  let hasSubGroup = false;
108
111
  const flushSubGroup = ()=>{
109
112
  if (!hasSubGroup) {
110
113
  return;
111
114
  }
112
- const diff = subGroupAccumulatedSize - subGroupAccumulatedPrevSize;
115
+ // Use the accumulated thresholded diff directly
116
+ const diff = subGroupAccumulatedDiff;
113
117
  let diffString = "";
114
118
  if (diff !== 0 && subGroupAccumulatedPrevSize !== 0) {
115
119
  const sign = diff > 0 ? "+" : "-";
@@ -123,6 +127,7 @@ export const reportStats = async ({ flags })=>{
123
127
  })}${diffString}`, "");
124
128
  }
125
129
  subGroupAccumulatedSize = 0;
130
+ subGroupAccumulatedDiff = 0;
126
131
  subGroupAccumulatedPrevSize = 0;
127
132
  hasSubGroup = false;
128
133
  };
@@ -147,10 +152,16 @@ export const reportStats = async ({ flags })=>{
147
152
  }
148
153
  let diffString = "";
149
154
  let previousFileSizeGzip = 0;
155
+ let thresholdedDiff = 0;
150
156
  if (previousStats && previousVersion) {
151
157
  const previousFileStats = previousStats.data[previousVersion] && previousStats.data[previousVersion][key];
152
158
  previousFileSizeGzip = previousFileStats?.fileSizeGzip || 0;
153
- const diff = item.fileSizeGzip - previousFileSizeGzip;
159
+ let diff = item.fileSizeGzip - previousFileSizeGzip;
160
+ // Apply threshold: if the absolute diff is below threshold, treat as no change
161
+ if (Math.abs(diff) < threshold) {
162
+ diff = 0;
163
+ }
164
+ thresholdedDiff = diff;
154
165
  overallDiff += diff;
155
166
  diffString = diff === 0 || diff === item.fileSizeGzip ? "" : ` (${diff > 0 ? "+" : "-"}${bytes(Math.abs(diff), {
156
167
  unitSeparator: " "
@@ -158,6 +169,7 @@ export const reportStats = async ({ flags })=>{
158
169
  }
159
170
  totalGzipSize += item.fileSizeGzip;
160
171
  subGroupAccumulatedSize += item.fileSizeGzip;
172
+ subGroupAccumulatedDiff += thresholdedDiff;
161
173
  subGroupAccumulatedPrevSize += previousFileSizeGzip;
162
174
  hasSubGroup = true;
163
175
  rowsMD.push(addMDrow({
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/reportStats.ts"],"sourcesContent":["import { basename, dirname, join } from \"node:path\";\nimport bytes from \"bytes\";\nimport type { FooterProperties } from \"./utilities.js\";\nimport {\n\taddMDrow,\n\tformatFooter,\n\tgetMostRecentVersion,\n\tgetOutputFile,\n\tpercentFormatter,\n\treadJSONFile,\n\tvalidateConfigurationFile,\n} from \"./utilities.js\";\n\ntype ReportConfiguration = {\n\tcurrent: string;\n\tprevious: string;\n\theader?: string;\n\tfooter?: (arguments_: FooterProperties) => string;\n\tcolumns?: { [key: string]: string }[];\n};\n\ninterface HeaderSizeEntry {\n\theader: string;\n}\ninterface FileSizeEntry {\n\tpath: string;\n\tlimit: string;\n\talias?: string;\n}\ntype SizesConfiguration = Array<HeaderSizeEntry | FileSizeEntry>;\n\nconst isHeaderEntry = (\n\tentry: HeaderSizeEntry | FileSizeEntry,\n): entry is HeaderSizeEntry => \"header\" in entry;\n\ntype ReportCompare = {\n\tdata: string;\n\texitCode: number;\n\texitMessage: string;\n\toutputFile: string;\n};\n\nexport const reportStats = async ({ flags }): Promise<ReportCompare> => {\n\tconst result: ReportCompare = {\n\t\texitCode: 0,\n\t\texitMessage: \"\",\n\t\toutputFile: \"\",\n\t\tdata: \"\",\n\t};\n\n\tlet previousStats, previousVersion: string;\n\tconst isValidConfigResult = validateConfigurationFile(flags.configuration);\n\tif (isValidConfigResult.exitMessage !== \"\") {\n\t\treturn { ...result, ...isValidConfigResult };\n\t}\n\n\tconst configurationFile = isValidConfigResult.data;\n\tconst outputFile = getOutputFile(flags.output);\n\tresult.outputFile = outputFile;\n\n\tconst configuration: {\n\t\treport: ReportConfiguration;\n\t\tsizes?: SizesConfiguration;\n\t} = await import(configurationFile).then((m) => m.default);\n\n\tconst currentStats = readJSONFile(\n\t\tjoin(dirname(configurationFile), configuration.report.current),\n\t);\n\tif (currentStats.exitMessage !== \"\") {\n\t\treturn { ...result, ...currentStats };\n\t}\n\n\ttry {\n\t\tpreviousStats = readJSONFile(\n\t\t\tjoin(dirname(configurationFile), configuration.report.previous),\n\t\t);\n\t\tif (previousStats.exitMessage !== \"\") {\n\t\t\treturn { ...result, ...previousStats };\n\t\t}\n\t\tpreviousVersion = getMostRecentVersion(previousStats.data);\n\t} catch {\n\t\t// no previous stats available.\n\t}\n\n\tconst currentVersion = getMostRecentVersion(currentStats.data);\n\n\tlet limitReached = false;\n\tlet overallDiff = 0;\n\tlet totalGzipSize = 0;\n\n\tconst headerString = configuration.report.header || \"## Bundle Size\";\n\tconst footerFormatter = configuration.report.footer || formatFooter;\n\tconst columns = configuration.report.columns || [\n\t\t{ status: \"Status\" },\n\t\t{ file: \"File\" },\n\t\t{ size: \"Size (Gzip)\" },\n\t\t{ limits: \"Limits\" },\n\t];\n\n\tconst rowsMD: string[] = [];\n\tconst hasGroupHeaders = Boolean(configuration.sizes?.some(isHeaderEntry));\n\tif (!hasGroupHeaders) {\n\t\trowsMD.push(addMDrow({ type: \"header\", columns }));\n\t}\n\n\t// Build ordered keys based on configuration.sizes if present.\n\tconst orderedKeys: Array<{\n\t\ttype: \"header\" | \"item\";\n\t\tvalue: string;\n\t\theader?: string;\n\t}> = [];\n\tif (configuration.sizes && Array.isArray(configuration.sizes)) {\n\t\tfor (const entry of configuration.sizes) {\n\t\t\tif (isHeaderEntry(entry)) {\n\t\t\t\torderedKeys.push({ type: \"header\", value: \"\", header: entry.header });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// entry is FileSizeEntry here.\n\t\t\tif (entry.alias && currentStats.data[currentVersion][entry.alias]) {\n\t\t\t\torderedKeys.push({ type: \"item\", value: entry.alias });\n\t\t\t} else if (currentStats.data[currentVersion][entry.path]) {\n\t\t\t\torderedKeys.push({ type: \"item\", value: entry.path });\n\t\t\t}\n\t\t}\n\t}\n\n\tif (orderedKeys.length === 0) {\n\t\tfor (const key of Object.keys(currentStats.data[currentVersion])) {\n\t\t\torderedKeys.push({ type: \"item\", value: key });\n\t\t}\n\t}\n\n\tlet subGroupAccumulatedSize = 0;\n\tlet subGroupAccumulatedPrevSize = 0;\n\tlet hasSubGroup = false;\n\tconst flushSubGroup = () => {\n\t\tif (!hasSubGroup) {\n\t\t\treturn;\n\t\t}\n\t\tconst diff = subGroupAccumulatedSize - subGroupAccumulatedPrevSize;\n\t\tlet diffString = \"\";\n\t\tif (diff !== 0 && subGroupAccumulatedPrevSize !== 0) {\n\t\t\tconst sign = diff > 0 ? \"+\" : \"-\";\n\t\t\tdiffString = ` (${sign}${bytes(Math.abs(diff), { unitSeparator: \" \" })} ${percentFormatter(\n\t\t\t\tdiff / subGroupAccumulatedPrevSize,\n\t\t\t)})`;\n\t\t}\n\t\tif (hasGroupHeaders) {\n\t\t\trowsMD.push(\n\t\t\t\t`\\nSub-bundle size: ${bytes(subGroupAccumulatedSize, {\n\t\t\t\t\tunitSeparator: \" \",\n\t\t\t\t})}${diffString}`,\n\t\t\t\t\"\",\n\t\t\t);\n\t\t}\n\t\tsubGroupAccumulatedSize = 0;\n\t\tsubGroupAccumulatedPrevSize = 0;\n\t\thasSubGroup = false;\n\t};\n\n\tfor (const entry of orderedKeys) {\n\t\tif (entry.type === \"header\") {\n\t\t\tflushSubGroup();\n\t\t\trowsMD.push(\"\", entry.header as string, \"\");\n\t\t\trowsMD.push(addMDrow({ type: \"header\", columns }));\n\t\t\tcontinue;\n\t\t}\n\t\tconst key = entry.value;\n\t\tconst item = currentStats.data[currentVersion][key];\n\t\tif (!item) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst name = basename(key);\n\t\tif (!item.passed) {\n\t\t\tlimitReached = true;\n\t\t}\n\n\t\tlet diffString = \"\";\n\t\tlet previousFileSizeGzip = 0;\n\t\tif (previousStats && previousVersion) {\n\t\t\tconst previousFileStats =\n\t\t\t\tpreviousStats.data[previousVersion] &&\n\t\t\t\tpreviousStats.data[previousVersion][key];\n\t\t\tpreviousFileSizeGzip = previousFileStats?.fileSizeGzip || 0;\n\t\t\tconst diff = item.fileSizeGzip - previousFileSizeGzip;\n\t\t\toverallDiff += diff;\n\t\t\tdiffString =\n\t\t\t\tdiff === 0 || diff === item.fileSizeGzip\n\t\t\t\t\t? \"\"\n\t\t\t\t\t: ` (${diff > 0 ? \"+\" : \"-\"}${bytes(Math.abs(diff), { unitSeparator: \" \" })} ${percentFormatter(\n\t\t\t\t\t\t\tpreviousFileSizeGzip === 0 ? 0 : diff / previousFileSizeGzip,\n\t\t\t\t\t\t)})`;\n\t\t}\n\n\t\ttotalGzipSize += item.fileSizeGzip;\n\t\tsubGroupAccumulatedSize += item.fileSizeGzip;\n\t\tsubGroupAccumulatedPrevSize += previousFileSizeGzip;\n\t\thasSubGroup = true;\n\n\t\trowsMD.push(\n\t\t\taddMDrow({\n\t\t\t\ttype: \"row\",\n\t\t\t\tpassed: item.passed,\n\t\t\t\tname: name,\n\t\t\t\tsize: item.fileSizeGzip,\n\t\t\t\tdiff: diffString,\n\t\t\t\tlimit: item.limit,\n\t\t\t\tcolumns,\n\t\t\t}),\n\t\t);\n\t}\n\n\tflushSubGroup();\n\n\tconst template = `\n${headerString}\n${rowsMD.join(\"\\n\")}\n\n${footerFormatter({ limitReached, overallDiff, totalGzipSize })}\n`;\n\n\tif (limitReached) {\n\t\tresult.exitCode = 1;\n\t}\n\tresult.data = template;\n\treturn result;\n};\n"],"names":["basename","dirname","join","bytes","addMDrow","formatFooter","getMostRecentVersion","getOutputFile","percentFormatter","readJSONFile","validateConfigurationFile","isHeaderEntry","entry","reportStats","flags","result","exitCode","exitMessage","outputFile","data","previousStats","previousVersion","isValidConfigResult","configuration","configurationFile","output","then","m","default","currentStats","report","current","previous","currentVersion","limitReached","overallDiff","totalGzipSize","headerString","header","footerFormatter","footer","columns","status","file","size","limits","rowsMD","hasGroupHeaders","Boolean","sizes","some","push","type","orderedKeys","Array","isArray","value","alias","path","length","key","Object","keys","subGroupAccumulatedSize","subGroupAccumulatedPrevSize","hasSubGroup","flushSubGroup","diff","diffString","sign","Math","abs","unitSeparator","item","name","passed","previousFileSizeGzip","previousFileStats","fileSizeGzip","limit","template"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,OAAO,EAAEC,IAAI,QAAQ,YAAY;AACpD,OAAOC,WAAW,QAAQ;AAE1B,SACCC,QAAQ,EACRC,YAAY,EACZC,oBAAoB,EACpBC,aAAa,EACbC,gBAAgB,EAChBC,YAAY,EACZC,yBAAyB,QACnB,iBAAiB;AAoBxB,MAAMC,gBAAgB,CACrBC,QAC8B,YAAYA;AAS3C,OAAO,MAAMC,cAAc,OAAO,EAAEC,KAAK,EAAE;IAC1C,MAAMC,SAAwB;QAC7BC,UAAU;QACVC,aAAa;QACbC,YAAY;QACZC,MAAM;IACP;IAEA,IAAIC,eAAeC;IACnB,MAAMC,sBAAsBZ,0BAA0BI,MAAMS,aAAa;IACzE,IAAID,oBAAoBL,WAAW,KAAK,IAAI;QAC3C,OAAO;YAAE,GAAGF,MAAM;YAAE,GAAGO,mBAAmB;QAAC;IAC5C;IAEA,MAAME,oBAAoBF,oBAAoBH,IAAI;IAClD,MAAMD,aAAaX,cAAcO,MAAMW,MAAM;IAC7CV,OAAOG,UAAU,GAAGA;IAEpB,MAAMK,gBAGF,MAAM,MAAM,CAACC,mBAAmBE,IAAI,CAAC,CAACC,IAAMA,EAAEC,OAAO;IAEzD,MAAMC,eAAepB,aACpBP,KAAKD,QAAQuB,oBAAoBD,cAAcO,MAAM,CAACC,OAAO;IAE9D,IAAIF,aAAaZ,WAAW,KAAK,IAAI;QACpC,OAAO;YAAE,GAAGF,MAAM;YAAE,GAAGc,YAAY;QAAC;IACrC;IAEA,IAAI;QACHT,gBAAgBX,aACfP,KAAKD,QAAQuB,oBAAoBD,cAAcO,MAAM,CAACE,QAAQ;QAE/D,IAAIZ,cAAcH,WAAW,KAAK,IAAI;YACrC,OAAO;gBAAE,GAAGF,MAAM;gBAAE,GAAGK,aAAa;YAAC;QACtC;QACAC,kBAAkBf,qBAAqBc,cAAcD,IAAI;IAC1D,EAAE,OAAM;IACP,+BAA+B;IAChC;IAEA,MAAMc,iBAAiB3B,qBAAqBuB,aAAaV,IAAI;IAE7D,IAAIe,eAAe;IACnB,IAAIC,cAAc;IAClB,IAAIC,gBAAgB;IAEpB,MAAMC,eAAed,cAAcO,MAAM,CAACQ,MAAM,IAAI;IACpD,MAAMC,kBAAkBhB,cAAcO,MAAM,CAACU,MAAM,IAAInC;IACvD,MAAMoC,UAAUlB,cAAcO,MAAM,CAACW,OAAO,IAAI;QAC/C;YAAEC,QAAQ;QAAS;QACnB;YAAEC,MAAM;QAAO;QACf;YAAEC,MAAM;QAAc;QACtB;YAAEC,QAAQ;QAAS;KACnB;IAED,MAAMC,SAAmB,EAAE;IAC3B,MAAMC,kBAAkBC,QAAQzB,cAAc0B,KAAK,EAAEC,KAAKvC;IAC1D,IAAI,CAACoC,iBAAiB;QACrBD,OAAOK,IAAI,CAAC/C,SAAS;YAAEgD,MAAM;YAAUX;QAAQ;IAChD;IAEA,8DAA8D;IAC9D,MAAMY,cAID,EAAE;IACP,IAAI9B,cAAc0B,KAAK,IAAIK,MAAMC,OAAO,CAAChC,cAAc0B,KAAK,GAAG;QAC9D,KAAK,MAAMrC,SAASW,cAAc0B,KAAK,CAAE;YACxC,IAAItC,cAAcC,QAAQ;gBACzByC,YAAYF,IAAI,CAAC;oBAAEC,MAAM;oBAAUI,OAAO;oBAAIlB,QAAQ1B,MAAM0B,MAAM;gBAAC;gBACnE;YACD;YACA,+BAA+B;YAC/B,IAAI1B,MAAM6C,KAAK,IAAI5B,aAAaV,IAAI,CAACc,eAAe,CAACrB,MAAM6C,KAAK,CAAC,EAAE;gBAClEJ,YAAYF,IAAI,CAAC;oBAAEC,MAAM;oBAAQI,OAAO5C,MAAM6C,KAAK;gBAAC;YACrD,OAAO,IAAI5B,aAAaV,IAAI,CAACc,eAAe,CAACrB,MAAM8C,IAAI,CAAC,EAAE;gBACzDL,YAAYF,IAAI,CAAC;oBAAEC,MAAM;oBAAQI,OAAO5C,MAAM8C,IAAI;gBAAC;YACpD;QACD;IACD;IAEA,IAAIL,YAAYM,MAAM,KAAK,GAAG;QAC7B,KAAK,MAAMC,OAAOC,OAAOC,IAAI,CAACjC,aAAaV,IAAI,CAACc,eAAe,EAAG;YACjEoB,YAAYF,IAAI,CAAC;gBAAEC,MAAM;gBAAQI,OAAOI;YAAI;QAC7C;IACD;IAEA,IAAIG,0BAA0B;IAC9B,IAAIC,8BAA8B;IAClC,IAAIC,cAAc;IAClB,MAAMC,gBAAgB;QACrB,IAAI,CAACD,aAAa;YACjB;QACD;QACA,MAAME,OAAOJ,0BAA0BC;QACvC,IAAII,aAAa;QACjB,IAAID,SAAS,KAAKH,gCAAgC,GAAG;YACpD,MAAMK,OAAOF,OAAO,IAAI,MAAM;YAC9BC,aAAa,CAAC,EAAE,EAAEC,OAAOlE,MAAMmE,KAAKC,GAAG,CAACJ,OAAO;gBAAEK,eAAe;YAAI,GAAG,CAAC,EAAEhE,iBACzE2D,OAAOH,6BACN,CAAC,CAAC;QACL;QACA,IAAIjB,iBAAiB;YACpBD,OAAOK,IAAI,CACV,CAAC,mBAAmB,EAAEhD,MAAM4D,yBAAyB;gBACpDS,eAAe;YAChB,KAAKJ,YAAY,EACjB;QAEF;QACAL,0BAA0B;QAC1BC,8BAA8B;QAC9BC,cAAc;IACf;IAEA,KAAK,MAAMrD,SAASyC,YAAa;QAChC,IAAIzC,MAAMwC,IAAI,KAAK,UAAU;YAC5Bc;YACApB,OAAOK,IAAI,CAAC,IAAIvC,MAAM0B,MAAM,EAAY;YACxCQ,OAAOK,IAAI,CAAC/C,SAAS;gBAAEgD,MAAM;gBAAUX;YAAQ;YAC/C;QACD;QACA,MAAMmB,MAAMhD,MAAM4C,KAAK;QACvB,MAAMiB,OAAO5C,aAAaV,IAAI,CAACc,eAAe,CAAC2B,IAAI;QACnD,IAAI,CAACa,MAAM;YACV;QACD;QACA,MAAMC,OAAO1E,SAAS4D;QACtB,IAAI,CAACa,KAAKE,MAAM,EAAE;YACjBzC,eAAe;QAChB;QAEA,IAAIkC,aAAa;QACjB,IAAIQ,uBAAuB;QAC3B,IAAIxD,iBAAiBC,iBAAiB;YACrC,MAAMwD,oBACLzD,cAAcD,IAAI,CAACE,gBAAgB,IACnCD,cAAcD,IAAI,CAACE,gBAAgB,CAACuC,IAAI;YACzCgB,uBAAuBC,mBAAmBC,gBAAgB;YAC1D,MAAMX,OAAOM,KAAKK,YAAY,GAAGF;YACjCzC,eAAegC;YACfC,aACCD,SAAS,KAAKA,SAASM,KAAKK,YAAY,GACrC,KACA,CAAC,EAAE,EAAEX,OAAO,IAAI,MAAM,MAAMhE,MAAMmE,KAAKC,GAAG,CAACJ,OAAO;gBAAEK,eAAe;YAAI,GAAG,CAAC,EAAEhE,iBAC7EoE,yBAAyB,IAAI,IAAIT,OAAOS,sBACvC,CAAC,CAAC;QACR;QAEAxC,iBAAiBqC,KAAKK,YAAY;QAClCf,2BAA2BU,KAAKK,YAAY;QAC5Cd,+BAA+BY;QAC/BX,cAAc;QAEdnB,OAAOK,IAAI,CACV/C,SAAS;YACRgD,MAAM;YACNuB,QAAQF,KAAKE,MAAM;YACnBD,MAAMA;YACN9B,MAAM6B,KAAKK,YAAY;YACvBX,MAAMC;YACNW,OAAON,KAAKM,KAAK;YACjBtC;QACD;IAEF;IAEAyB;IAEA,MAAMc,WAAW,CAAC;AACnB,EAAE3C,aAAa;AACf,EAAES,OAAO5C,IAAI,CAAC,MAAM;;AAEpB,EAAEqC,gBAAgB;QAAEL;QAAcC;QAAaC;IAAc,GAAG;AAChE,CAAC;IAEA,IAAIF,cAAc;QACjBnB,OAAOC,QAAQ,GAAG;IACnB;IACAD,OAAOI,IAAI,GAAG6D;IACd,OAAOjE;AACR,EAAE"}
1
+ {"version":3,"sources":["../src/reportStats.ts"],"sourcesContent":["import { basename, dirname, join } from \"node:path\";\nimport bytes from \"bytes\";\nimport { DEFAULT_THRESHOLD } from \"./defaults.js\";\nimport type { FooterProperties } from \"./utilities.js\";\nimport {\n\taddMDrow,\n\tformatFooter,\n\tgetMostRecentVersion,\n\tgetOutputFile,\n\tpercentFormatter,\n\treadJSONFile,\n\tvalidateConfigurationFile,\n} from \"./utilities.js\";\n\ntype ReportConfiguration = {\n\tcurrent: string;\n\tprevious: string;\n\theader?: string;\n\tfooter?: (arguments_: FooterProperties) => string;\n\tcolumns?: { [key: string]: string }[];\n\tthreshold?: number;\n};\n\ninterface HeaderSizeEntry {\n\theader: string;\n}\ninterface FileSizeEntry {\n\tpath: string;\n\tlimit: string;\n\talias?: string;\n}\ntype SizesConfiguration = Array<HeaderSizeEntry | FileSizeEntry>;\n\nconst isHeaderEntry = (\n\tentry: HeaderSizeEntry | FileSizeEntry,\n): entry is HeaderSizeEntry => \"header\" in entry;\n\ntype ReportCompare = {\n\tdata: string;\n\texitCode: number;\n\texitMessage: string;\n\toutputFile: string;\n};\n\nexport const reportStats = async ({ flags }): Promise<ReportCompare> => {\n\tconst result: ReportCompare = {\n\t\texitCode: 0,\n\t\texitMessage: \"\",\n\t\toutputFile: \"\",\n\t\tdata: \"\",\n\t};\n\n\tlet previousStats, previousVersion: string;\n\tconst isValidConfigResult = validateConfigurationFile(flags.configuration);\n\tif (isValidConfigResult.exitMessage !== \"\") {\n\t\treturn { ...result, ...isValidConfigResult };\n\t}\n\n\tconst configurationFile = isValidConfigResult.data;\n\tconst outputFile = getOutputFile(flags.output);\n\tresult.outputFile = outputFile;\n\n\tconst configuration: {\n\t\treport: ReportConfiguration;\n\t\tsizes?: SizesConfiguration;\n\t} = await import(configurationFile).then((m) => m.default);\n\n\tconst currentStats = readJSONFile(\n\t\tjoin(dirname(configurationFile), configuration.report.current),\n\t);\n\tif (currentStats.exitMessage !== \"\") {\n\t\treturn { ...result, ...currentStats };\n\t}\n\n\ttry {\n\t\tpreviousStats = readJSONFile(\n\t\t\tjoin(dirname(configurationFile), configuration.report.previous),\n\t\t);\n\t\tif (previousStats.exitMessage !== \"\") {\n\t\t\treturn { ...result, ...previousStats };\n\t\t}\n\t\tpreviousVersion = getMostRecentVersion(previousStats.data);\n\t} catch {\n\t\t// no previous stats available.\n\t}\n\n\tconst currentVersion = getMostRecentVersion(currentStats.data);\n\n\tlet limitReached = false;\n\tlet overallDiff = 0;\n\tlet totalGzipSize = 0;\n\n\tconst threshold = configuration.report.threshold ?? DEFAULT_THRESHOLD;\n\tconst headerString = configuration.report.header || \"## Bundle Size\";\n\tconst footerFormatter = configuration.report.footer || formatFooter;\n\tconst columns = configuration.report.columns || [\n\t\t{ status: \"Status\" },\n\t\t{ file: \"File\" },\n\t\t{ size: \"Size (Gzip)\" },\n\t\t{ limits: \"Limits\" },\n\t];\n\n\tconst rowsMD: string[] = [];\n\tconst hasGroupHeaders = Boolean(configuration.sizes?.some(isHeaderEntry));\n\tif (!hasGroupHeaders) {\n\t\trowsMD.push(addMDrow({ type: \"header\", columns }));\n\t}\n\n\t// Build ordered keys based on configuration.sizes if present.\n\tconst orderedKeys: Array<{\n\t\ttype: \"header\" | \"item\";\n\t\tvalue: string;\n\t\theader?: string;\n\t}> = [];\n\tif (configuration.sizes && Array.isArray(configuration.sizes)) {\n\t\tfor (const entry of configuration.sizes) {\n\t\t\tif (isHeaderEntry(entry)) {\n\t\t\t\torderedKeys.push({ type: \"header\", value: \"\", header: entry.header });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// entry is FileSizeEntry here.\n\t\t\tif (entry.alias && currentStats.data[currentVersion][entry.alias]) {\n\t\t\t\torderedKeys.push({ type: \"item\", value: entry.alias });\n\t\t\t} else if (currentStats.data[currentVersion][entry.path]) {\n\t\t\t\torderedKeys.push({ type: \"item\", value: entry.path });\n\t\t\t}\n\t\t}\n\t}\n\n\tif (orderedKeys.length === 0) {\n\t\tfor (const key of Object.keys(currentStats.data[currentVersion])) {\n\t\t\torderedKeys.push({ type: \"item\", value: key });\n\t\t}\n\t}\n\n\tlet subGroupAccumulatedSize = 0;\n\tlet subGroupAccumulatedDiff = 0;\n\tlet subGroupAccumulatedPrevSize = 0;\n\tlet hasSubGroup = false;\n\tconst flushSubGroup = () => {\n\t\tif (!hasSubGroup) {\n\t\t\treturn;\n\t\t}\n\t\t// Use the accumulated thresholded diff directly\n\t\tconst diff = subGroupAccumulatedDiff;\n\t\tlet diffString = \"\";\n\t\tif (diff !== 0 && subGroupAccumulatedPrevSize !== 0) {\n\t\t\tconst sign = diff > 0 ? \"+\" : \"-\";\n\t\t\tdiffString = ` (${sign}${bytes(Math.abs(diff), { unitSeparator: \" \" })} ${percentFormatter(\n\t\t\t\tdiff / subGroupAccumulatedPrevSize,\n\t\t\t)})`;\n\t\t}\n\t\tif (hasGroupHeaders) {\n\t\t\trowsMD.push(\n\t\t\t\t`\\nSub-bundle size: ${bytes(subGroupAccumulatedSize, {\n\t\t\t\t\tunitSeparator: \" \",\n\t\t\t\t})}${diffString}`,\n\t\t\t\t\"\",\n\t\t\t);\n\t\t}\n\t\tsubGroupAccumulatedSize = 0;\n\t\tsubGroupAccumulatedDiff = 0;\n\t\tsubGroupAccumulatedPrevSize = 0;\n\t\thasSubGroup = false;\n\t};\n\n\tfor (const entry of orderedKeys) {\n\t\tif (entry.type === \"header\") {\n\t\t\tflushSubGroup();\n\t\t\trowsMD.push(\"\", entry.header as string, \"\");\n\t\t\trowsMD.push(addMDrow({ type: \"header\", columns }));\n\t\t\tcontinue;\n\t\t}\n\t\tconst key = entry.value;\n\t\tconst item = currentStats.data[currentVersion][key];\n\t\tif (!item) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst name = basename(key);\n\t\tif (!item.passed) {\n\t\t\tlimitReached = true;\n\t\t}\n\n\t\tlet diffString = \"\";\n\t\tlet previousFileSizeGzip = 0;\n\t\tlet thresholdedDiff = 0;\n\t\tif (previousStats && previousVersion) {\n\t\t\tconst previousFileStats =\n\t\t\t\tpreviousStats.data[previousVersion] &&\n\t\t\t\tpreviousStats.data[previousVersion][key];\n\t\t\tpreviousFileSizeGzip = previousFileStats?.fileSizeGzip || 0;\n\t\t\tlet diff = item.fileSizeGzip - previousFileSizeGzip;\n\t\t\t// Apply threshold: if the absolute diff is below threshold, treat as no change\n\t\t\tif (Math.abs(diff) < threshold) {\n\t\t\t\tdiff = 0;\n\t\t\t}\n\t\t\tthresholdedDiff = diff;\n\t\t\toverallDiff += diff;\n\t\t\tdiffString =\n\t\t\t\tdiff === 0 || diff === item.fileSizeGzip\n\t\t\t\t\t? \"\"\n\t\t\t\t\t: ` (${diff > 0 ? \"+\" : \"-\"}${bytes(Math.abs(diff), { unitSeparator: \" \" })} ${percentFormatter(\n\t\t\t\t\t\t\tpreviousFileSizeGzip === 0 ? 0 : diff / previousFileSizeGzip,\n\t\t\t\t\t\t)})`;\n\t\t}\n\n\t\ttotalGzipSize += item.fileSizeGzip;\n\t\tsubGroupAccumulatedSize += item.fileSizeGzip;\n\t\tsubGroupAccumulatedDiff += thresholdedDiff;\n\t\tsubGroupAccumulatedPrevSize += previousFileSizeGzip;\n\t\thasSubGroup = true;\n\n\t\trowsMD.push(\n\t\t\taddMDrow({\n\t\t\t\ttype: \"row\",\n\t\t\t\tpassed: item.passed,\n\t\t\t\tname: name,\n\t\t\t\tsize: item.fileSizeGzip,\n\t\t\t\tdiff: diffString,\n\t\t\t\tlimit: item.limit,\n\t\t\t\tcolumns,\n\t\t\t}),\n\t\t);\n\t}\n\n\tflushSubGroup();\n\n\tconst template = `\n${headerString}\n${rowsMD.join(\"\\n\")}\n\n${footerFormatter({ limitReached, overallDiff, totalGzipSize })}\n`;\n\n\tif (limitReached) {\n\t\tresult.exitCode = 1;\n\t}\n\tresult.data = template;\n\treturn result;\n};\n"],"names":["basename","dirname","join","bytes","DEFAULT_THRESHOLD","addMDrow","formatFooter","getMostRecentVersion","getOutputFile","percentFormatter","readJSONFile","validateConfigurationFile","isHeaderEntry","entry","reportStats","flags","result","exitCode","exitMessage","outputFile","data","previousStats","previousVersion","isValidConfigResult","configuration","configurationFile","output","then","m","default","currentStats","report","current","previous","currentVersion","limitReached","overallDiff","totalGzipSize","threshold","headerString","header","footerFormatter","footer","columns","status","file","size","limits","rowsMD","hasGroupHeaders","Boolean","sizes","some","push","type","orderedKeys","Array","isArray","value","alias","path","length","key","Object","keys","subGroupAccumulatedSize","subGroupAccumulatedDiff","subGroupAccumulatedPrevSize","hasSubGroup","flushSubGroup","diff","diffString","sign","Math","abs","unitSeparator","item","name","passed","previousFileSizeGzip","thresholdedDiff","previousFileStats","fileSizeGzip","limit","template"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-cli/bundlesize",
3
- "version": "4.3.0",
3
+ "version": "4.5.0",
4
4
  "license": "MIT",
5
5
  "author": "Arno Versini",
6
6
  "description": "Simple CLI tool that checks file(s) size and report if limits have been reached",
@@ -10,6 +10,7 @@
10
10
  "bin": "dist/bundlesize.js",
11
11
  "files": [
12
12
  "dist",
13
+ "schemas",
13
14
  "README.md"
14
15
  ],
15
16
  "node": ">=18",
@@ -45,5 +46,5 @@
45
46
  "@vitest/coverage-v8": "3.2.4",
46
47
  "vitest": "3.2.4"
47
48
  },
48
- "gitHead": "a1b287c23dd32b40d0b5fa5439241bebe4b3f5cd"
49
+ "gitHead": "b2f3d976bb47479d29242c68e36dcc85df41f7b9"
49
50
  }
@@ -0,0 +1,151 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://cdn.jsdelivr.net/npm/@node-cli/bundlesize/schemas/bundlesize.config.schema.json",
4
+ "title": "Bundlesize Configuration",
5
+ "description": "Configuration schema for @node-cli/bundlesize - a CLI tool that checks file(s) size and reports if limits have been reached",
6
+ "type": "object",
7
+ "properties": {
8
+ "$schema": {
9
+ "type": "string",
10
+ "description": "Path to the JSON Schema for this configuration file"
11
+ },
12
+ "sizes": {
13
+ "type": "array",
14
+ "description": "Array of file size entries to check",
15
+ "items": {
16
+ "oneOf": [
17
+ {
18
+ "$ref": "#/$defs/SizeEntry"
19
+ },
20
+ {
21
+ "$ref": "#/$defs/HeaderEntry"
22
+ }
23
+ ]
24
+ }
25
+ },
26
+ "report": {
27
+ "$ref": "#/$defs/ReportConfiguration"
28
+ }
29
+ },
30
+ "additionalProperties": false,
31
+ "$defs": {
32
+ "SizeEntry": {
33
+ "type": "object",
34
+ "description": "A file size entry to check",
35
+ "properties": {
36
+ "path": {
37
+ "type": "string",
38
+ "description": "Path to the file to check. Supports glob patterns and special placeholders: <hash> for content hashes, <semver> for semantic versions"
39
+ },
40
+ "limit": {
41
+ "type": "string",
42
+ "description": "Maximum allowed size for the file (gzipped). Examples: '1.5 kB', '500 B', '2 MB'",
43
+ "pattern": "^[0-9]+(\\.[0-9]+)?\\s*(B|kB|KB|MB|GB|TB|PB)$"
44
+ },
45
+ "alias": {
46
+ "type": "string",
47
+ "description": "Optional alias for the file in reports. Useful when using dynamic path patterns"
48
+ }
49
+ },
50
+ "required": ["path", "limit"],
51
+ "additionalProperties": false
52
+ },
53
+ "HeaderEntry": {
54
+ "type": "object",
55
+ "description": "A header entry for grouping files in reports",
56
+ "properties": {
57
+ "header": {
58
+ "type": "string",
59
+ "description": "Header text for a group of files in the report (e.g., '### Main Bundle')"
60
+ }
61
+ },
62
+ "required": ["header"],
63
+ "additionalProperties": false
64
+ },
65
+ "ColumnDefinition": {
66
+ "type": "object",
67
+ "description": "A column definition for the report table",
68
+ "minProperties": 1,
69
+ "maxProperties": 1,
70
+ "additionalProperties": {
71
+ "type": "string",
72
+ "description": "Column header text"
73
+ }
74
+ },
75
+ "ReportConfiguration": {
76
+ "type": "object",
77
+ "description": "Configuration for generating size comparison reports",
78
+ "properties": {
79
+ "current": {
80
+ "type": "string",
81
+ "description": "Path to the current stats JSON file (relative to config file)"
82
+ },
83
+ "previous": {
84
+ "type": "string",
85
+ "description": "Path to the previous stats JSON file for comparison (relative to config file)"
86
+ },
87
+ "header": {
88
+ "type": "string",
89
+ "description": "Custom header for the report. Defaults to '## Bundle Size'"
90
+ },
91
+ "threshold": {
92
+ "type": "number",
93
+ "description": "Minimum byte difference to report as a change. Differences below this threshold are treated as no change. Defaults to 0",
94
+ "minimum": 0
95
+ },
96
+ "columns": {
97
+ "type": "array",
98
+ "description": "Custom column definitions for the report table. Default columns: status, file, size, limits",
99
+ "items": {
100
+ "$ref": "#/$defs/ColumnDefinition"
101
+ },
102
+ "examples": [
103
+ [
104
+ { "status": "Status" },
105
+ { "file": "File" },
106
+ { "size": "Size (Gzip)" },
107
+ { "limits": "Limits" }
108
+ ]
109
+ ]
110
+ }
111
+ },
112
+ "required": ["current", "previous"],
113
+ "additionalProperties": true
114
+ }
115
+ },
116
+ "examples": [
117
+ {
118
+ "sizes": [
119
+ {
120
+ "path": "dist/index.js",
121
+ "limit": "10 kB"
122
+ },
123
+ {
124
+ "path": "dist/vendor.<hash>.js",
125
+ "limit": "50 kB",
126
+ "alias": "vendor"
127
+ }
128
+ ]
129
+ },
130
+ {
131
+ "sizes": [
132
+ { "header": "### Core" },
133
+ {
134
+ "path": "dist/core.js",
135
+ "limit": "5 kB"
136
+ },
137
+ { "header": "### Plugins" },
138
+ {
139
+ "path": "dist/plugins/*.js",
140
+ "limit": "2 kB"
141
+ }
142
+ ],
143
+ "report": {
144
+ "current": "stats/current.json",
145
+ "previous": "stats/previous.json",
146
+ "header": "## Bundle Size Report",
147
+ "threshold": 100
148
+ }
149
+ }
150
+ ]
151
+ }