Build: Bump github/codeql-action from 3.27.0 to 3.27.5 in the github-actions group
[jquery.git] / build / tasks / lib / compareSize.js
blob4cb0b40c50ff5d56a7893bb239d8d66aa35d515e
1 import fs from "node:fs/promises";
2 import { promisify } from "node:util";
3 import zlib from "node:zlib";
4 import { exec as nodeExec } from "node:child_process";
5 import chalk from "chalk";
6 import isCleanWorkingDir from "./isCleanWorkingDir.js";
8 const VERSION = 2;
9 const lastRunBranch = " last run";
11 const gzip = promisify( zlib.gzip );
12 const brotli = promisify( zlib.brotliCompress );
13 const exec = promisify( nodeExec );
15 async function getBranchName() {
16         const { stdout } = await exec( "git rev-parse --abbrev-ref HEAD" );
17         return stdout.trim();
20 async function getCommitHash() {
21         const { stdout } = await exec( "git rev-parse HEAD" );
22         return stdout.trim();
25 function getBranchHeader( branch, commit ) {
26         let branchHeader = branch.trim();
27         if ( commit ) {
28                 branchHeader = chalk.bold( branchHeader ) + chalk.gray( ` @${ commit }` );
29         } else {
30                 branchHeader = chalk.italic( branchHeader );
31         }
32         return branchHeader;
35 async function getCache( loc ) {
36         let cache;
37         try {
38                 const contents = await fs.readFile( loc, "utf8" );
39                 cache = JSON.parse( contents );
40         } catch ( err ) {
41                 return {};
42         }
44         const lastRun = cache[ lastRunBranch ];
45         if ( !lastRun || !lastRun.meta || lastRun.meta.version !== VERSION ) {
46                 console.log( "Compare cache version mismatch. Rewriting..." );
47                 return {};
48         }
49         return cache;
52 function cacheResults( results ) {
53         const files = Object.create( null );
54         results.forEach( function( result ) {
55                 files[ result.filename ] = {
56                         raw: result.raw,
57                         gz: result.gz,
58                         br: result.br
59                 };
60         } );
61         return files;
64 function saveCache( loc, cache ) {
66         // Keep cache readable for manual edits
67         return fs.writeFile( loc, JSON.stringify( cache, null, "  " ) + "\n" );
70 function compareSizes( existing, current, padLength ) {
71         if ( typeof current !== "number" ) {
72                 return chalk.grey( `${ existing }`.padStart( padLength ) );
73         }
74         const delta = current - existing;
75         if ( delta > 0 ) {
76                 return chalk.red( `+${ delta }`.padStart( padLength ) );
77         }
78         return chalk.green( `${ delta }`.padStart( padLength ) );
81 function sortBranches( a, b ) {
82         if ( a === lastRunBranch ) {
83                 return 1;
84         }
85         if ( b === lastRunBranch ) {
86                 return -1;
87         }
88         if ( a < b ) {
89                 return -1;
90         }
91         if ( a > b ) {
92                 return 1;
93         }
94         return 0;
97 export async function compareSize( { cache = ".sizecache.json", files } = {} ) {
98         if ( !files || !files.length ) {
99                 throw new Error( "No files specified" );
100         }
102         const branch = await getBranchName();
103         const commit = await getCommitHash();
104         const sizeCache = await getCache( cache );
106         let rawPadLength = 0;
107         let gzPadLength = 0;
108         let brPadLength = 0;
109         const results = await Promise.all(
110                 files.map( async function( filename ) {
112                         let contents = await fs.readFile( filename, "utf8" );
114                         // Remove the short SHA and .dirty from comparisons.
115                         // The short SHA so commits can be compared against each other
116                         // and .dirty to compare with the existing branch during development.
117                         const sha = /jQuery v\d+.\d+.\d+(?:-[\w\.]+)?(?:\+slim\.|\+)?(\w+(?:\.dirty)?)?/.exec( contents )[ 1 ];
118                         contents = contents.replace( new RegExp( sha, "g" ), "" );
120                         const size = Buffer.byteLength( contents, "utf8" );
121                         const gzippedSize = ( await gzip( contents ) ).length;
122                         const brotlifiedSize = ( await brotli( contents ) ).length;
124                         // Add one to give space for the `+` or `-` in the comparison
125                         rawPadLength = Math.max( rawPadLength, size.toString().length + 1 );
126                         gzPadLength = Math.max( gzPadLength, gzippedSize.toString().length + 1 );
127                         brPadLength = Math.max( brPadLength, brotlifiedSize.toString().length + 1 );
129                         return { filename, raw: size, gz: gzippedSize, br: brotlifiedSize };
130                 } )
131         );
133         const sizeHeader = "raw".padStart( rawPadLength ) +
134                 "gz".padStart( gzPadLength + 1 ) +
135                 "br".padStart( brPadLength + 1 ) +
136                 " Filename";
138         const sizes = results.map( function( result ) {
139                 const rawSize = result.raw.toString().padStart( rawPadLength );
140                 const gzSize = result.gz.toString().padStart( gzPadLength );
141                 const brSize = result.br.toString().padStart( brPadLength );
142                 return `${ rawSize } ${ gzSize } ${ brSize } ${ result.filename }`;
143         } );
145         const comparisons = Object.keys( sizeCache ).sort( sortBranches ).map( function( branch ) {
146                 const meta = sizeCache[ branch ].meta || {};
147                 const commit = meta.commit;
149                 const files = sizeCache[ branch ].files;
150                 const branchSizes = Object.keys( files ).map( function( filename ) {
151                         const branchResult = files[ filename ];
152                         const compareResult = results.find( function( result ) {
153                                 return result.filename === filename;
154                         } ) || {};
156                         const compareRaw = compareSizes( branchResult.raw, compareResult.raw, rawPadLength );
157                         const compareGz = compareSizes( branchResult.gz, compareResult.gz, gzPadLength );
158                         const compareBr = compareSizes( branchResult.br, compareResult.br, brPadLength );
159                         return `${ compareRaw } ${ compareGz } ${ compareBr } ${ filename }`;
160                 } );
162                 return [
163                         "", // New line before each branch
164                         getBranchHeader( branch, commit ),
165                         sizeHeader,
166                         ...branchSizes
167                 ].join( "\n" );
168         } );
170         const output = [
171                 "", // Opening new line
172                 chalk.bold( "Sizes" ),
173                 sizeHeader,
174                 ...sizes,
175                 ...comparisons,
176                 "" // Closing new line
177         ].join( "\n" );
179         console.log( output );
181         // Always save the last run
182         // Save version under last run
183         sizeCache[ lastRunBranch ] = {
184                 meta: { version: VERSION },
185                 files: cacheResults( results )
186         };
188         // Only save cache for the current branch
189         // if the working directory is clean.
190         if ( await isCleanWorkingDir() ) {
191                 sizeCache[ branch ] = {
192                         meta: { commit },
193                         files: cacheResults( results )
194                 };
195                 console.log( `Saved cache for ${ branch }.` );
196         }
198         await saveCache( cache, sizeCache );
200         return results;