Skip to content

Commit

Permalink
Format modulereport output as a table
Browse files Browse the repository at this point in the history
A bit easier to read.
  • Loading branch information
lawrence-forooghian committed Jan 24, 2024
1 parent 7a0e379 commit 71caf05
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 33 deletions.
52 changes: 52 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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"
}
}
91 changes: 59 additions & 32 deletions scripts/moduleReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -107,54 +114,57 @@ async function runSourceMapExplorer(bundleInfo: BundleInfo) {
});
}

async function printAndCheckModuleSizes() {
const errors: Error[] = [];
async function calculateAndCheckModuleSizes(): Promise<Output> {
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<Output> {
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
// that we expect this function to have transitively imported anyway.
// 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.`
)
Expand All @@ -163,19 +173,19 @@ async function printAndCheckFunctionSizes() {
}
}

return errors;
return output;
}

async function printAndCheckMinimalUsefulRealtimeBundleSize() {
const errors: Error[] = [];
async function calculateAndCheckMinimalUsefulRealtimeBundleSize(): Promise<Output> {
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
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions scripts/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"esModuleInterop": true,
}
}

0 comments on commit 71caf05

Please sign in to comment.