From 4b0832fb785a004cf2a4defc4f663aabb3f7ef24 Mon Sep 17 00:00:00 2001 From: George Fu Date: Mon, 8 Sep 2025 12:57:19 -0400 Subject: [PATCH] chore(scripts): report bundler sizes for assorted app configurations --- .gitignore | 1 + benchmark/bundlers/report.md | 14 ++ package.json | 1 + .../applications/lib-dynamodb-aggregate.ts | 2 + .../applications/multiple-sdk-clients.ts | 3 + .../private-multiple-clients-micg.ts | 6 + .../private-multiple-clients-schema.ts | 6 + .../private-restjson-micg-aggregate.ts | 1 + .../private-restjson-micg-single-command.ts | 1 + .../private-restjson-schema-aggregate.ts | 1 + .../private-restjson-schema-single-command.ts | 1 + .../applications/sdk-ec2-aggregate.ts | 1 + .../bundlers/applications/sdk-s3-aggregate.ts | 1 + .../applications/sdk-s3-single-command.ts | 1 + .../applications/sdk-sagemaker-aggregate.ts | 1 + .../runner/BundlerSizeBenchmarker.mjs | 148 ++++++++++++++++++ tests/bundlers/runner/ReportMarkdown.mjs | 31 ++++ tests/bundlers/runner/run.mjs | 28 ++++ 18 files changed, 248 insertions(+) create mode 100644 benchmark/bundlers/report.md create mode 100644 tests/bundlers/applications/lib-dynamodb-aggregate.ts create mode 100644 tests/bundlers/applications/multiple-sdk-clients.ts create mode 100644 tests/bundlers/applications/private-multiple-clients-micg.ts create mode 100644 tests/bundlers/applications/private-multiple-clients-schema.ts create mode 100644 tests/bundlers/applications/private-restjson-micg-aggregate.ts create mode 100644 tests/bundlers/applications/private-restjson-micg-single-command.ts create mode 100644 tests/bundlers/applications/private-restjson-schema-aggregate.ts create mode 100644 tests/bundlers/applications/private-restjson-schema-single-command.ts create mode 100644 tests/bundlers/applications/sdk-ec2-aggregate.ts create mode 100644 tests/bundlers/applications/sdk-s3-aggregate.ts create mode 100644 tests/bundlers/applications/sdk-s3-single-command.ts create mode 100644 tests/bundlers/applications/sdk-sagemaker-aggregate.ts create mode 100644 tests/bundlers/runner/BundlerSizeBenchmarker.mjs create mode 100644 tests/bundlers/runner/ReportMarkdown.mjs create mode 100644 tests/bundlers/runner/run.mjs diff --git a/.gitignore b/.gitignore index 10607a93d2d2f..104af9a157eff 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ package-lock.json !serviceModels/logs dist tests/bundlers/dist-min +tests/bundlers/dist-vite .idea/ *.iml diff --git a/benchmark/bundlers/report.md b/benchmark/bundlers/report.md new file mode 100644 index 0000000000000..de27f168c2806 --- /dev/null +++ b/benchmark/bundlers/report.md @@ -0,0 +1,14 @@ +| Application | SDK Version | browser:Webpack | browser:Rollup | browser:EsBuild | +| :---------------------------------------- | :---------- | :-------------- | :------------- | :-------------- | +| lib-dynamodb-aggregate.ts | 3.883.0 | 211 kb | 204 kb | 232 kb | +| multiple-sdk-clients.ts | 3.883.0 | 498 kb | 488 kb | 534 kb | +| private-multiple-clients-micg.ts | 3.883.0 | 435 kb | 429 kb | 479 kb | +| private-multiple-clients-schema.ts | 3.883.0 | 376 kb | 378 kb | 423 kb | +| private-restjson-micg-aggregate.ts | 3.883.0 | 228 kb | 223 kb | 248 kb | +| private-restjson-micg-single-command.ts | 3.883.0 | 125 kb | 120 kb | 140 kb | +| private-restjson-schema-aggregate.ts | 3.883.0 | 213 kb | 210 kb | 233 kb | +| private-restjson-schema-single-command.ts | 3.883.0 | 146 kb | 142 kb | 164 kb | +| sdk-ec2-aggregate.ts | 3.883.0 | 1.159 mb | 1.105 mb | 1.152 mb | +| sdk-s3-aggregate.ts | 3.883.0 | 376 kb | 367 kb | 399 kb | +| sdk-s3-single-command.ts | 3.883.0 | 222 kb | 216 kb | 242 kb | +| sdk-sagemaker-aggregate.ts | 3.883.0 | 462 kb | 445 kb | 482 kb | diff --git a/package.json b/package.json index 2e9a251251e01..a85f11894b648 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "scripts": { "bootstrap": "yarn", "bootstrap:ci": "yarn install --frozen-lockfile", + "benchmark:bundlers": "node ./tests/bundlers/runner/run.mjs", "build:all": "node ./scripts/turbo build", "build:ci": "node ./scripts/turbo build", "build:clients:generic": "node ./scripts/turbo build -F=@aws-sdk/aws-echo-service", diff --git a/tests/bundlers/applications/lib-dynamodb-aggregate.ts b/tests/bundlers/applications/lib-dynamodb-aggregate.ts new file mode 100644 index 0000000000000..e2e21b22f0fe6 --- /dev/null +++ b/tests/bundlers/applications/lib-dynamodb-aggregate.ts @@ -0,0 +1,2 @@ +export { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"; +export { DynamoDB } from "@aws-sdk/client-dynamodb"; diff --git a/tests/bundlers/applications/multiple-sdk-clients.ts b/tests/bundlers/applications/multiple-sdk-clients.ts new file mode 100644 index 0000000000000..c30f3ba6d6763 --- /dev/null +++ b/tests/bundlers/applications/multiple-sdk-clients.ts @@ -0,0 +1,3 @@ +export { S3 } from "@aws-sdk/client-s3"; +export { DynamoDB } from "@aws-sdk/client-dynamodb"; +export { SQS } from "@aws-sdk/client-sqs"; diff --git a/tests/bundlers/applications/private-multiple-clients-micg.ts b/tests/bundlers/applications/private-multiple-clients-micg.ts new file mode 100644 index 0000000000000..97a935c64649a --- /dev/null +++ b/tests/bundlers/applications/private-multiple-clients-micg.ts @@ -0,0 +1,6 @@ +export { RestJsonProtocol } from "@aws-sdk/aws-protocoltests-restjson"; +export { RestXmlProtocol } from "@aws-sdk/aws-protocoltests-restxml"; +export { JsonProtocol } from "@aws-sdk/aws-protocoltests-json"; +export { JSONRPC10 } from "@aws-sdk/aws-protocoltests-json-10"; +export { RpcV2Protocol } from "@aws-sdk/aws-protocoltests-smithy-rpcv2-cbor"; +export { QueryProtocol } from "@aws-sdk/aws-protocoltests-query"; diff --git a/tests/bundlers/applications/private-multiple-clients-schema.ts b/tests/bundlers/applications/private-multiple-clients-schema.ts new file mode 100644 index 0000000000000..03e92ca0d26fb --- /dev/null +++ b/tests/bundlers/applications/private-multiple-clients-schema.ts @@ -0,0 +1,6 @@ +export { RestJsonProtocol } from "@aws-sdk/aws-protocoltests-restjson-schema"; +export { RestXmlProtocol } from "@aws-sdk/aws-protocoltests-restxml-schema"; +export { JsonProtocol } from "@aws-sdk/aws-protocoltests-json-schema"; +export { JSONRPC10 } from "@aws-sdk/aws-protocoltests-json-10-schema"; +export { RpcV2Protocol } from "@aws-sdk/aws-protocoltests-smithy-rpcv2-cbor-schema"; +export { QueryProtocol } from "@aws-sdk/aws-protocoltests-query-schema"; diff --git a/tests/bundlers/applications/private-restjson-micg-aggregate.ts b/tests/bundlers/applications/private-restjson-micg-aggregate.ts new file mode 100644 index 0000000000000..f5e2a576bed06 --- /dev/null +++ b/tests/bundlers/applications/private-restjson-micg-aggregate.ts @@ -0,0 +1 @@ +export { RestJsonProtocol } from "@aws-sdk/aws-protocoltests-restjson"; diff --git a/tests/bundlers/applications/private-restjson-micg-single-command.ts b/tests/bundlers/applications/private-restjson-micg-single-command.ts new file mode 100644 index 0000000000000..f16d4aa188091 --- /dev/null +++ b/tests/bundlers/applications/private-restjson-micg-single-command.ts @@ -0,0 +1 @@ +export { HttpStringPayloadCommand, RestJsonProtocolClient } from "@aws-sdk/aws-protocoltests-restjson"; diff --git a/tests/bundlers/applications/private-restjson-schema-aggregate.ts b/tests/bundlers/applications/private-restjson-schema-aggregate.ts new file mode 100644 index 0000000000000..c5dc9773abf1d --- /dev/null +++ b/tests/bundlers/applications/private-restjson-schema-aggregate.ts @@ -0,0 +1 @@ +export { RestJsonProtocol } from "@aws-sdk/aws-protocoltests-restjson-schema"; diff --git a/tests/bundlers/applications/private-restjson-schema-single-command.ts b/tests/bundlers/applications/private-restjson-schema-single-command.ts new file mode 100644 index 0000000000000..27a2d0ac738dc --- /dev/null +++ b/tests/bundlers/applications/private-restjson-schema-single-command.ts @@ -0,0 +1 @@ +export { HttpStringPayloadCommand, RestJsonProtocolClient } from "@aws-sdk/aws-protocoltests-restjson-schema"; diff --git a/tests/bundlers/applications/sdk-ec2-aggregate.ts b/tests/bundlers/applications/sdk-ec2-aggregate.ts new file mode 100644 index 0000000000000..c48a7c979126f --- /dev/null +++ b/tests/bundlers/applications/sdk-ec2-aggregate.ts @@ -0,0 +1 @@ +export { EC2 } from "@aws-sdk/client-ec2"; diff --git a/tests/bundlers/applications/sdk-s3-aggregate.ts b/tests/bundlers/applications/sdk-s3-aggregate.ts new file mode 100644 index 0000000000000..33f15361d37f1 --- /dev/null +++ b/tests/bundlers/applications/sdk-s3-aggregate.ts @@ -0,0 +1 @@ +export { S3 } from "@aws-sdk/client-s3"; diff --git a/tests/bundlers/applications/sdk-s3-single-command.ts b/tests/bundlers/applications/sdk-s3-single-command.ts new file mode 100644 index 0000000000000..a0a27de75b699 --- /dev/null +++ b/tests/bundlers/applications/sdk-s3-single-command.ts @@ -0,0 +1 @@ +export { GetObjectCommand, S3Client } from "@aws-sdk/client-s3"; diff --git a/tests/bundlers/applications/sdk-sagemaker-aggregate.ts b/tests/bundlers/applications/sdk-sagemaker-aggregate.ts new file mode 100644 index 0000000000000..4ceeecc167cde --- /dev/null +++ b/tests/bundlers/applications/sdk-sagemaker-aggregate.ts @@ -0,0 +1 @@ +export { SageMaker } from "@aws-sdk/client-sagemaker"; diff --git a/tests/bundlers/runner/BundlerSizeBenchmarker.mjs b/tests/bundlers/runner/BundlerSizeBenchmarker.mjs new file mode 100644 index 0000000000000..fa73d9c12576a --- /dev/null +++ b/tests/bundlers/runner/BundlerSizeBenchmarker.mjs @@ -0,0 +1,148 @@ +import path from "node:path"; +import fs from "node:fs"; +import webpack from "webpack"; +import { build } from "vite"; +import esbuild from "esbuild"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +/** + * Gives file stats for a selection of bundlers and application types. + */ +export class BundlerSizeBenchmarker { + constructor({ application }) { + this.application = application; + } + + /** + * Create a Webpack bundle. + * @returns {Promise<{app:string,size:string,bundler:string}>} + */ + async webpack() { + const outfile = path.resolve(__dirname, "..", "dist", `webpack-dist-${this.application}.js`); + + const config = { + mode: "production", + entry: path.resolve(__dirname, "..", "applications", this.application), + target: "web", + output: { + path: path.dirname(outfile), + filename: path.basename(outfile), + library: "dist", + }, + optimization: { + minimize: true, + splitChunks: false, + runtimeChunk: false, + sideEffects: true, + usedExports: true, + }, + stats: { + optimizationBailout: false, + }, + }; + return new Promise((resolve) => { + webpack(config, (err, stats) => { + if (err) { + console.error(err); + } + const stat = fs.statSync(outfile); + resolve(this.report(stat, "webpack")); + }); + }); + } + + /** + * Create a Rollup bundle. + * @returns {Promise<{app:string,size:string,bundler:string}>} + */ + async rollup() { + const inputFile = path.resolve(__dirname, "..", "applications", this.application); + const outfile = path.resolve(__dirname, "..", "dist-vite", `vite-${this.application}.umd.js`); + + await build({ + logLevel: "silent", + build: { + outDir: "./dist-vite", + lib: { + entry: inputFile, + name: "dist", + fileName: `vite-${this.application}`, + }, + rollupOptions: { + input: { + input: inputFile, + }, + external: [], + output: { + dir: path.dirname(outfile), + inlineDynamicImports: true, + }, + }, + }, + }); + + const stat = fs.statSync(outfile); + return this.report(stat, "rollup"); + } + + /** + * Create a Esbuild bundle. + * @returns {Promise<{app:string,size:string,bundler:string}>} + */ + async esbuild() { + const entryPoint = path.resolve(__dirname, "..", "applications", this.application); + const outfile = path.resolve(__dirname, "..", "dist", `esbuild-dist-${this.application}.js`); + + await esbuild.build({ + entryPoints: [entryPoint], + bundle: true, + minify: true, + outfile: outfile, + format: "esm", + target: "es2015", + }); + + const stat = fs.statSync(outfile); + return this.report(stat, "esbuild"); + } + + async all() { + const stats = await Promise.all([this.rollup(), this.webpack(), this.esbuild()]); + const data = { + app: this.application, + }; + for (const stat of stats) { + data[stat.bundler] = stat.size; + } + return data; + } + + /** + * @param {fs.Stats} stat + * @param {string} bundler + * @returns {{app, size: string}} + */ + report(stat, bundler) { + return { + app: this.application, + size: byteSize(stat.size), + bundler: bundler, + }; + } +} + +/** + * @param {number} num - of bytes. + * @returns {string} bytes in readable format. + */ +function byteSize(num) { + if (num > 1024 ** 2) { + return ((((num / 1024 ** 2) * 1000) | 0) / 1000).toLocaleString() + " mb"; + } + if (num > 1024) { + return ((num / 1024) | 0).toLocaleString() + " kb"; + } + return num.toLocaleString() + " b"; +} diff --git a/tests/bundlers/runner/ReportMarkdown.mjs b/tests/bundlers/runner/ReportMarkdown.mjs new file mode 100644 index 0000000000000..3c735038e7dbc --- /dev/null +++ b/tests/bundlers/runner/ReportMarkdown.mjs @@ -0,0 +1,31 @@ +import { fileURLToPath } from "url"; +import path from "node:path"; +import fs from "node:fs"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const lerna = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "..", "lerna.json"), "utf-8")); + +/** + * Converts stat objects from {BundlerSizeBenchmarker} to markdown report. + */ +export class ReportMarkdown { + constructor() { + this.rows = []; + } + + push(stat) { + this.rows.push(stat); + } + + toMarkdown() { + return `| Application | SDK Version | browser:Webpack | browser:Rollup | browser:EsBuild | +| :------ | :------ | :----------- | :------ | :----- | +${this.rows + .map((row) => { + const { app, webpack, rollup, esbuild } = row; + return `|${app}|${lerna.version}|${webpack}|${rollup}|${esbuild}|`; + }) + .join("\n")}`; + } +} diff --git a/tests/bundlers/runner/run.mjs b/tests/bundlers/runner/run.mjs new file mode 100644 index 0000000000000..c0c3eed4b8ab4 --- /dev/null +++ b/tests/bundlers/runner/run.mjs @@ -0,0 +1,28 @@ +import fs from "node:fs"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; +import prettier from "prettier"; + +import { ReportMarkdown } from "./ReportMarkdown.mjs"; +import { BundlerSizeBenchmarker } from "./BundlerSizeBenchmarker.mjs"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const applicationFolder = path.join(__dirname, "..", "applications"); +const report = new ReportMarkdown(); + +for (const app of fs.readdirSync(applicationFolder)) { + const benchmarker = new BundlerSizeBenchmarker({ application: app }); + + const stat = await benchmarker.all(); + console.log(stat); + report.push(stat); +} + +const reportMd = path.join(__dirname, "..", "..", "..", "benchmark", "bundlers", "report.md"); + +const formatted = prettier.format(report.toMarkdown(), { + filepath: reportMd, +}); + +fs.writeFileSync(reportMd, formatted);