diff --git a/package-lock.json b/package-lock.json index 277a60789d..6c90d8cd65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@testing-library/react": "^13.3.0", + "@types/cli-table": "^0.3.4", "@types/jmespath": "^0.15.2", "@types/node": "^18.0.0", "@types/request": "^2.48.7", @@ -30,6 +31,7 @@ "async": "ably-forks/async#requirejs", "aws-sdk": "^2.1413.0", "chai": "^4.2.0", + "cli-table": "^0.3.11", "cors": "^2.8.5", "esbuild": "^0.18.10", "esbuild-plugin-umd-wrapper": "^1.0.7", @@ -1436,6 +1438,12 @@ "@types/chai": "*" } }, + "node_modules/@types/cli-table": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@types/cli-table/-/cli-table-0.3.4.tgz", + "integrity": "sha512-GsALrTL69mlwbAw/MHF1IPTadSLZQnsxe7a80G8l4inN/iEXCOcVeT/S7aRc6hbhqzL9qZ314kHPDQnQ3ev+HA==", + "dev": true + }, "node_modules/@types/eslint": { "version": "8.44.8", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", @@ -2798,6 +2806,27 @@ "node": ">=6.0" } }, + "node_modules/cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dev": true, + "dependencies": { + "colors": "1.0.3" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/cli-table/node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/cli-table3": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", @@ -11719,6 +11748,12 @@ "@types/chai": "*" } }, + "@types/cli-table": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@types/cli-table/-/cli-table-0.3.4.tgz", + "integrity": "sha512-GsALrTL69mlwbAw/MHF1IPTadSLZQnsxe7a80G8l4inN/iEXCOcVeT/S7aRc6hbhqzL9qZ314kHPDQnQ3ev+HA==", + "dev": true + }, "@types/eslint": { "version": "8.44.8", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", @@ -12757,6 +12792,23 @@ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, + "cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dev": true, + "requires": { + "colors": "1.0.3" + }, + "dependencies": { + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true + } + } + }, "cli-table3": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", diff --git a/package.json b/package.json index 760795238e..ae880a90d5 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@testing-library/react": "^13.3.0", + "@types/cli-table": "^0.3.4", "@types/jmespath": "^0.15.2", "@types/node": "^18.0.0", "@types/request": "^2.48.7", @@ -62,6 +63,7 @@ "async": "ably-forks/async#requirejs", "aws-sdk": "^2.1413.0", "chai": "^4.2.0", + "cli-table": "^0.3.11", "cors": "^2.8.5", "esbuild": "^0.18.10", "esbuild-plugin-umd-wrapper": "^1.0.7", @@ -138,7 +140,7 @@ "format": "prettier --write --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s docs/chrome-mv3.md grunt", "format:check": "prettier --check --ignore-path .gitignore --ignore-path .prettierignore src test ably.d.ts modules.d.ts webpack.config.js Gruntfile.js scripts/*.[jt]s grunt", "sourcemap": "source-map-explorer build/ably.min.js", - "modulereport": "tsc --noEmit scripts/moduleReport.ts && esr scripts/moduleReport.ts", + "modulereport": "tsc --noEmit --esModuleInterop scripts/moduleReport.ts && esr scripts/moduleReport.ts", "docs": "typedoc" } } diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index bcf17b5a17..a00e3edc2d 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -3,6 +3,7 @@ import * as path from 'path'; import { explore } from 'source-map-explorer'; import { promisify } from 'util'; import { gzip } from 'zlib'; +import Table from 'cli-table'; // The maximum size we allow for a minimal useful Realtime bundle (i.e. one that can subscribe to a channel) const minimalUsefulRealtimeBundleSizeThresholdsKiB = { raw: 94, gzip: 29 }; @@ -53,8 +54,14 @@ interface ByteSizes { gzipEncodedByteSize: number; } -function formatByteSizes(sizes: ByteSizes) { - return `raw: ${formatBytes(sizes.rawByteSize)}; gzip: ${formatBytes(sizes.gzipEncodedByteSize)}`; +interface TableRow { + description: string; + sizes: ByteSizes; +} + +interface Output { + tableRows: TableRow[]; + errors: Error[]; } // Uses esbuild to create a bundle containing the named exports from 'ably/modules' @@ -107,46 +114,49 @@ async function runSourceMapExplorer(bundleInfo: BundleInfo) { }); } -async function printAndCheckModuleSizes() { - const errors: Error[] = []; +async function calculateAndCheckModuleSizes(): Promise { + const output: Output = { tableRows: [], errors: [] }; for (const baseClient of ['BaseRest', 'BaseRealtime']) { const baseClientSizes = await getImportSizes([baseClient]); - // First display the size of the base client - console.log(`${baseClient}: ${formatByteSizes(baseClientSizes)}`); + // First output the size of the base client + output.tableRows.push({ description: baseClient, sizes: baseClientSizes }); - // Then display the size of each export together with the base client + // Then output the size of each export together with the base client for (const exportName of [...moduleNames, ...Object.values(functions).map((functionData) => functionData.name)]) { const sizes = await getImportSizes([baseClient, exportName]); - console.log(`${baseClient} + ${exportName}: ${formatByteSizes(sizes)}`); + output.tableRows.push({ description: `${baseClient} + ${exportName}`, sizes }); if (!(baseClientSizes.rawByteSize < sizes.rawByteSize) && !(baseClient === 'BaseRest' && exportName === 'Rest')) { // Emit an error if adding the module does not increase the bundle size // (this means that the module is not being tree-shaken correctly). - errors.push(new Error(`Adding ${exportName} to ${baseClient} does not increase the bundle size.`)); + output.errors.push(new Error(`Adding ${exportName} to ${baseClient} does not increase the bundle size.`)); } } } - return errors; + return output; } -async function printAndCheckFunctionSizes() { - const errors: Error[] = []; +async function calculateAndCheckFunctionSizes(): Promise { + const output: Output = { tableRows: [], errors: [] }; for (const functionData of functions) { const { name: functionName, transitiveImports } = functionData; - // First display the size of the function + // First output the size of the function const standaloneSizes = await getImportSizes([functionName]); - console.log(`${functionName}: ${formatByteSizes(standaloneSizes)}`); + output.tableRows.push({ description: functionName, sizes: standaloneSizes }); - // Then display the size of the function together with the modules we expect + // Then output the size of the function together with the modules we expect // it to transitively import if (transitiveImports.length > 0) { const withTransitiveImportsSizes = await getImportSizes([functionName, ...transitiveImports]); - console.log(`${functionName} + ${transitiveImports.join(' + ')}: ${formatByteSizes(withTransitiveImportsSizes)}`); + output.tableRows.push({ + description: `${functionName} + ${transitiveImports.join(' + ')}`, + sizes: withTransitiveImportsSizes, + }); if (withTransitiveImportsSizes.rawByteSize > standaloneSizes.rawByteSize) { // Emit an error if the bundle size is increased by adding the modules @@ -154,7 +164,7 @@ async function printAndCheckFunctionSizes() { // This seemed like a useful sense check, but it might need tweaking in // the future if we make future optimisations that mean that the // standalone functions don’t necessarily import the whole module. - errors.push( + output.errors.push( new Error( `Adding ${transitiveImports.join(' + ')} to ${functionName} unexpectedly increases the bundle size.` ) @@ -163,19 +173,19 @@ async function printAndCheckFunctionSizes() { } } - return errors; + return output; } -async function printAndCheckMinimalUsefulRealtimeBundleSize() { - const errors: Error[] = []; +async function calculateAndCheckMinimalUsefulRealtimeBundleSize(): Promise { + const output: Output = { tableRows: [], errors: [] }; const exports = ['BaseRealtime', 'FetchRequest', 'WebSocketTransport']; const sizes = await getImportSizes(exports); - console.log(`Minimal useful Realtime (${exports.join(' + ')}): ${formatByteSizes(sizes)}`); + output.tableRows.push({ description: `Minimal useful Realtime (${exports.join(' + ')})`, sizes }); if (sizes.rawByteSize > minimalUsefulRealtimeBundleSizeThresholdsKiB.raw * 1024) { - errors.push( + output.errors.push( new Error( `Minimal raw useful Realtime bundle is ${formatBytes( sizes.rawByteSize @@ -185,7 +195,7 @@ async function printAndCheckMinimalUsefulRealtimeBundleSize() { } if (sizes.gzipEncodedByteSize > minimalUsefulRealtimeBundleSizeThresholdsKiB.gzip * 1024) { - errors.push( + output.errors.push( new Error( `Minimal gzipped useful Realtime bundle is ${formatBytes( sizes.gzipEncodedByteSize @@ -194,7 +204,7 @@ async function printAndCheckMinimalUsefulRealtimeBundleSize() { ); } - return errors; + return output; } // Performs a sense check that there are no unexpected files making a large contribution to the BaseRealtime bundle size. @@ -282,15 +292,32 @@ async function checkBaseRealtimeFiles() { } (async function run() { - const errors: Error[] = []; - - errors.push(...(await printAndCheckMinimalUsefulRealtimeBundleSize())); - errors.push(...(await printAndCheckModuleSizes())); - errors.push(...(await printAndCheckFunctionSizes())); - errors.push(...(await checkBaseRealtimeFiles())); + const output = ( + await Promise.all([ + calculateAndCheckMinimalUsefulRealtimeBundleSize(), + calculateAndCheckModuleSizes(), + calculateAndCheckFunctionSizes(), + ]) + ).reduce((accum, current) => ({ + tableRows: [...accum.tableRows, ...current.tableRows], + errors: [...accum.errors, ...current.errors], + })); + + output.errors.push(...(await checkBaseRealtimeFiles())); + + const table = new Table({ + style: { head: ['green'] }, + head: ['Modules', 'Size (raw, KiB)', 'Size (gzipped, KiB)'], + rows: output.tableRows.map((row) => [ + row.description, + formatBytes(row.sizes.rawByteSize), + formatBytes(row.sizes.gzipEncodedByteSize), + ]), + }); + console.log(table.toString()); - if (errors.length > 0) { - for (const error of errors) { + if (output.errors.length > 0) { + for (const error of output.errors) { console.log(error.message); } process.exit(1); diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 0000000000..b6955c086e --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + } +}