Skip to content

Commit

Permalink
Perform a check of which files contribute to BaseRealtime bundle size
Browse files Browse the repository at this point in the history
To give us further confidence that something unexpected won’t sneak in.

Part of #1497.
  • Loading branch information
lawrence-forooghian committed Nov 17, 2023
1 parent f6c9c64 commit 31d2e2d
Showing 1 changed file with 124 additions and 4 deletions.
128 changes: 124 additions & 4 deletions scripts/moduleReport.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import esbuild from 'esbuild';
import path from 'path';
import { explore } from 'source-map-explorer';

// List of all modules accepted in ModulesMap
const moduleNames = [
Expand Down Expand Up @@ -35,8 +37,14 @@ function formatBytes(bytes: number) {
return `${formatted} KiB`;
}

// Gets the bundled size in bytes of an array of named exports from 'ably/modules'
function getImportSize(modules: string[]) {
interface BundleInfo {
byteSize: number;
code: Uint8Array;
sourceMap: Uint8Array;
}

// Uses esbuild to create a bundle containing the named exports from 'ably/modules'
function getBundleInfo(modules: string[]): BundleInfo {
const outfile = modules.join('');
const result = esbuild.buildSync({
stdin: {
Expand All @@ -48,9 +56,36 @@ function getImportSize(modules: string[]) {
bundle: true,
outfile,
write: false,
sourcemap: 'external',
});

return result.metafile.outputs[outfile].bytes;
const pathHasBase = (component: string) => {
return (outputFile: esbuild.OutputFile) => {
return path.parse(outputFile.path).base === component;
};
};

const codeOutputFile = result.outputFiles.find(pathHasBase(outfile))!;
const sourceMapOutputFile = result.outputFiles.find(pathHasBase(`${outfile}.map`))!;

return {
byteSize: result.metafile.outputs[outfile].bytes,
code: codeOutputFile.contents,
sourceMap: sourceMapOutputFile.contents,
};
}

// Gets the bundled size in bytes of an array of named exports from 'ably/modules'
function getImportSize(modules: string[]) {
const bundleInfo = getBundleInfo(modules);
return bundleInfo.byteSize;
}

async function runSourceMapExplorer(bundleInfo: BundleInfo) {
return explore({
code: Buffer.from(bundleInfo.code),
map: Buffer.from(bundleInfo.sourceMap),
});
}

function printAndCheckModuleSizes() {
Expand Down Expand Up @@ -112,11 +147,96 @@ function printAndCheckFunctionSizes() {
return errors;
}

(function run() {
// Performs a sense check that there are no unexpected files making a large contribution to the BaseRealtime bundle size.
async function checkBaseRealtimeFiles() {
const baseRealtimeBundleInfo = getBundleInfo(['BaseRealtime']);
const exploreResult = await runSourceMapExplorer(baseRealtimeBundleInfo);

const files = exploreResult.bundles[0].files;
delete files['[sourceMappingURL]'];
delete files['[unmapped]'];
delete files['[EOLs]'];

const thresholdBytes = 100;
const filesAboveThreshold = Object.entries(files).filter((file) => file[1].size >= thresholdBytes);

// These are the files that are allowed to contribute >= `threshold` bytes to the BaseRealtime bundle.
//
// The threshold is chosen pretty arbitrarily. There are some files (e.g. presencemessage.ts) whose bulk should not be included in the BaseRealtime bundle, but which make a small contribution to the bundle (probably because we make use of one exported constant or something; I haven’t looked into it).
const allowedFiles = new Set([
'src/common/constants/HttpStatusCodes.ts',
'src/common/constants/TransportName.ts',
'src/common/constants/XHRStates.ts',
'src/common/lib/client/auth.ts',
'src/common/lib/client/baseclient.ts',
'src/common/lib/client/baserealtime.ts',
'src/common/lib/client/channelstatechange.ts',
'src/common/lib/client/connection.ts',
'src/common/lib/client/connectionstatechange.ts',
'src/common/lib/client/realtimechannel.ts',
'src/common/lib/transport/connectionerrors.ts',
'src/common/lib/transport/connectionmanager.ts',
'src/common/lib/transport/messagequeue.ts',
'src/common/lib/transport/protocol.ts',
'src/common/lib/transport/transport.ts',
'src/common/lib/types/devicedetails.ts',
'src/common/lib/types/errorinfo.ts',
'src/common/lib/types/message.ts',
'src/common/lib/types/protocolmessage.ts',
'src/common/lib/types/pushchannelsubscription.ts', // TODO why? https://github.com/ably/ably-js/issues/1506
'src/common/lib/util/defaults.ts',
'src/common/lib/util/eventemitter.ts',
'src/common/lib/util/logger.ts',
'src/common/lib/util/multicaster.ts',
'src/common/lib/util/utils.ts',
'src/platform/web/config.ts',
'src/platform/web/lib/http/http.ts',
'src/platform/web/lib/util/bufferutils.ts',
'src/platform/web/lib/util/defaults.ts',
'src/platform/web/lib/util/hmac-sha256.ts',
'src/platform/web/lib/util/webstorage.ts',
'src/platform/web/modules.ts',
]);

const errors: Error[] = [];

// Check that no files other than those allowed above make a large contribution to the bundle size
for (const file of filesAboveThreshold) {
if (!allowedFiles.has(file[0])) {
errors.push(
new Error(
`Unexpected file ${file[0]}, contributes ${file[1].size}B to BaseRealtime, more than allowed ${thresholdBytes}B`
)
);
}
}

// Check that there are no stale entries in the allowed list
for (const allowedFile of Array.from(allowedFiles)) {
const file = files[allowedFile];

if (file) {
if (file.size < thresholdBytes) {
errors.push(
new Error(
`File ${allowedFile} contributes ${file.size}B, which is less than the expected minimum of ${thresholdBytes}B. Remove it from the \`allowedFiles\` list.`
)
);
}
} else {
errors.push(new Error(`File ${allowedFile} is referenced in \`allowedFiles\` but does not contribute to bundle`));
}
}

return errors;
}

(async function run() {
const errors: Error[] = [];

errors.push(...printAndCheckModuleSizes());
errors.push(...printAndCheckFunctionSizes());
errors.push(...(await checkBaseRealtimeFiles()));

if (errors.length > 0) {
for (const error of errors) {
Expand Down

0 comments on commit 31d2e2d

Please sign in to comment.