From 4a1a4d53bf4b86ff0e7ad95710eb3bc8d4fc9ddc Mon Sep 17 00:00:00 2001 From: George Fu Date: Fri, 3 Oct 2025 13:27:08 -0400 Subject: [PATCH] test: create bundler testing workspace --- .gitignore | 2 +- Makefile | 2 +- testbed/bundlers/Makefile | 29 +-- .../bundlers/applications/NormalizedSchema.ts | 1 + .../applications/abstract-protocols.ts | 1 + .../applications/cbor-client-aggregate.ts | 1 + testbed/bundlers/applications/cbor-client.ts | 1 + .../bundlers/applications/cbor-protocol.ts | 1 + .../bundlers/applications/inactive/.gitkeep | 0 testbed/bundlers/bundlers.spec.mjs | 31 --- testbed/bundlers/esbuild.mjs | 19 -- .../runner/BundlerSizeBenchmarker.mjs | 189 ++++++++++++++++++ testbed/bundlers/runner/run.mjs | 19 ++ testbed/bundlers/source.ts | 1 - testbed/bundlers/vite.config.ts | 30 --- testbed/bundlers/vite.min.config.ts | 30 --- testbed/bundlers/webpack.config.js | 25 --- testbed/bundlers/webpack.min.config.js | 17 -- 18 files changed, 221 insertions(+), 178 deletions(-) create mode 100644 testbed/bundlers/applications/NormalizedSchema.ts create mode 100644 testbed/bundlers/applications/abstract-protocols.ts create mode 100644 testbed/bundlers/applications/cbor-client-aggregate.ts create mode 100644 testbed/bundlers/applications/cbor-client.ts create mode 100644 testbed/bundlers/applications/cbor-protocol.ts create mode 100644 testbed/bundlers/applications/inactive/.gitkeep delete mode 100644 testbed/bundlers/bundlers.spec.mjs delete mode 100644 testbed/bundlers/esbuild.mjs create mode 100644 testbed/bundlers/runner/BundlerSizeBenchmarker.mjs create mode 100644 testbed/bundlers/runner/run.mjs delete mode 100644 testbed/bundlers/source.ts delete mode 100644 testbed/bundlers/vite.config.ts delete mode 100644 testbed/bundlers/vite.min.config.ts delete mode 100644 testbed/bundlers/webpack.config.js delete mode 100644 testbed/bundlers/webpack.min.config.js diff --git a/.gitignore b/.gitignore index 8c9c78512c8..289a5510e7e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,7 @@ smithy-typescript-ssdk-codegen-test-utils/bin/ smithy-typescript-codegen-test/example-weather-customizations/bin/ testbed/bundlers/dist -testbed/bundlers/dist-min +testbed/bundlers/dist-* **/node_modules/ **/*.tsbuildinfo **/*.d.ts diff --git a/Makefile b/Makefile index 70a196d9c79..597e3fb4714 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ test-browser: yarn g:vitest run -c vitest.config.browser.mts test-bundlers: - (cd ./testbed/bundlers && make build test) + (cd ./testbed/bundlers && make run) # typecheck for test code. test-types: diff --git a/testbed/bundlers/Makefile b/testbed/bundlers/Makefile index 8b1fca5c115..85cc92509a6 100644 --- a/testbed/bundlers/Makefile +++ b/testbed/bundlers/Makefile @@ -1,24 +1,7 @@ -.PHONY: test build vite webpack +.PHONY: run -# asserts that bundles contain expected content. -test: - node bundlers.spec.mjs - du -sh ./dist-min/* - -# create bundles -build: - rm -rf ./dist/* - rm -rf ./dist-min/* - make vite webpack - -# note: vite deletes files in the build folders and must run first. -vite: - npx vite build --config vite.config.ts - npx vite build --config vite.min.config.ts - -webpack: - npx webpack - npx webpack -c webpack.min.config.js - -esbuild: - exit 1 \ No newline at end of file +run: + mkdir -p dist-vite + mkdir -p dist-esbuild + mkdir -p dist-webpack + node ./runner/run.mjs \ No newline at end of file diff --git a/testbed/bundlers/applications/NormalizedSchema.ts b/testbed/bundlers/applications/NormalizedSchema.ts new file mode 100644 index 00000000000..4cbc0eabf4c --- /dev/null +++ b/testbed/bundlers/applications/NormalizedSchema.ts @@ -0,0 +1 @@ +export { NormalizedSchema } from "@smithy/core/schema"; diff --git a/testbed/bundlers/applications/abstract-protocols.ts b/testbed/bundlers/applications/abstract-protocols.ts new file mode 100644 index 00000000000..1d5fb6fc81e --- /dev/null +++ b/testbed/bundlers/applications/abstract-protocols.ts @@ -0,0 +1 @@ +export { HttpBindingProtocol, RpcProtocol } from "@smithy/core/protocols"; diff --git a/testbed/bundlers/applications/cbor-client-aggregate.ts b/testbed/bundlers/applications/cbor-client-aggregate.ts new file mode 100644 index 00000000000..d3655087d4d --- /dev/null +++ b/testbed/bundlers/applications/cbor-client-aggregate.ts @@ -0,0 +1 @@ +export { RpcV2Protocol } from "@smithy/smithy-rpcv2-cbor-schema"; diff --git a/testbed/bundlers/applications/cbor-client.ts b/testbed/bundlers/applications/cbor-client.ts new file mode 100644 index 00000000000..e25ef00479b --- /dev/null +++ b/testbed/bundlers/applications/cbor-client.ts @@ -0,0 +1 @@ +export { RpcV2ProtocolClient } from "@smithy/smithy-rpcv2-cbor-schema"; diff --git a/testbed/bundlers/applications/cbor-protocol.ts b/testbed/bundlers/applications/cbor-protocol.ts new file mode 100644 index 00000000000..1720b066bce --- /dev/null +++ b/testbed/bundlers/applications/cbor-protocol.ts @@ -0,0 +1 @@ +export { SmithyRpcV2CborProtocol } from "@smithy/core/cbor"; diff --git a/testbed/bundlers/applications/inactive/.gitkeep b/testbed/bundlers/applications/inactive/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/testbed/bundlers/bundlers.spec.mjs b/testbed/bundlers/bundlers.spec.mjs deleted file mode 100644 index 7026db66d25..00000000000 --- a/testbed/bundlers/bundlers.spec.mjs +++ /dev/null @@ -1,31 +0,0 @@ -import assert from "node:assert"; - -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -const webpackDist = { - name: "webpack", - content: fs.readFileSync(path.join(__dirname, "dist", "webpack-dist.js"), "utf-8"), -}; -const viteDist = { - name: "vite", - content: fs.readFileSync(path.join(__dirname, "dist", "vite-dist.js"), "utf-8"), -}; - -for (const { content: fileContents, name } of [webpackDist, viteDist]) { - console.log("================", name, "================"); - - const contentSize = fileContents.replaceAll(/\s+/g, "").length; - const callsToClassBuilder = fileContents.match(/\.classBuilder\(\)/g) || []; - - const serializers = fileContents.match(/(var|const) se_/g) || []; - const operationSchemas = fileContents.match(/ op\(/g) || []; - const structSchemas = fileContents.match(/ struct\(/g) || []; - - console.log("serializers", serializers.length); - console.log("operationSchemas", operationSchemas.length); - console.log("structSchemas", structSchemas.length); -} diff --git a/testbed/bundlers/esbuild.mjs b/testbed/bundlers/esbuild.mjs deleted file mode 100644 index 1457844e1d6..00000000000 --- a/testbed/bundlers/esbuild.mjs +++ /dev/null @@ -1,19 +0,0 @@ -import esbuild from "esbuild"; - -const buildOptions = { - platform: "browser", - target: ["es2020"], - bundle: true, - format: "esm", - mainFields: ["module", "browser", "main"], - allowOverwrite: true, - entryPoints: ["./source.ts"], - supported: { - "dynamic-import": true, - }, - outfile: "./dist/esbuild-dist.js", - keepNames: true, - external: [], -}; - -await esbuild.build(buildOptions); diff --git a/testbed/bundlers/runner/BundlerSizeBenchmarker.mjs b/testbed/bundlers/runner/BundlerSizeBenchmarker.mjs new file mode 100644 index 00000000000..f0caab60c44 --- /dev/null +++ b/testbed/bundlers/runner/BundlerSizeBenchmarker.mjs @@ -0,0 +1,189 @@ +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", `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.cjs`); + + await build({ + logLevel: "silent", + build: { + outDir: "./dist-vite", + lib: { + entry: inputFile, + name: "dist", + fileName: `vite-${this.application}`, + }, + minify: true, + emptyOutDir: false, + rollupOptions: { + input: { + input: inputFile, + }, + external: [], + output: { + dir: path.dirname(outfile), + inlineDynamicImports: true, + }, + }, + }, + }); + + const stat = fs.statSync(outfile); + return this.report(stat, "rollup"); + } + + async rollupNoMinification() { + const inputFile = path.resolve(__dirname, "..", "applications", this.application); + const outfile = path.resolve(__dirname, "..", "dist-rollup", `rollup-${this.application}.umd.cjs`); + + await build({ + logLevel: "silent", + build: { + outDir: "./dist-rollup", + lib: { + entry: inputFile, + name: "dist", + fileName: `rollup-${this.application}`, + }, + minify: false, + emptyOutDir: false, + rollupOptions: { + input: { + input: inputFile, + }, + external: [], + output: { + dir: path.dirname(outfile), + inlineDynamicImports: true, + }, + }, + }, + }); + + const stat = fs.statSync(outfile); + return this.report(stat, "rollup_no_minification"); + } + + /** + * 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", `esbuild-dist-${this.application}.js`); + + await esbuild.build({ + entryPoints: [entryPoint], + platform: "browser", + bundle: true, + minify: true, + mainFields: ["browser", "module", "main"], + outfile: outfile, + format: "esm", + target: "es2022", + }); + + const stat = fs.statSync(outfile); + return this.report(stat, "esbuild"); + } + + async all() { + const stats = await Promise.all([ + this.rollup(), // + this.rollupNoMinification(), + 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/testbed/bundlers/runner/run.mjs b/testbed/bundlers/runner/run.mjs new file mode 100644 index 00000000000..4950ea73d09 --- /dev/null +++ b/testbed/bundlers/runner/run.mjs @@ -0,0 +1,19 @@ +import fs from "node:fs"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; + +import { BundlerSizeBenchmarker } from "./BundlerSizeBenchmarker.mjs"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const applicationFolder = path.join(__dirname, "..", "applications"); + +for (const app of fs.readdirSync(applicationFolder)) { + if (fs.lstatSync(path.join(applicationFolder, app)).isDirectory()) { + continue; + } + const benchmarker = new BundlerSizeBenchmarker({ application: app }); + + const stat = await benchmarker.all(); + console.log(stat); +} diff --git a/testbed/bundlers/source.ts b/testbed/bundlers/source.ts deleted file mode 100644 index 9929198a817..00000000000 --- a/testbed/bundlers/source.ts +++ /dev/null @@ -1 +0,0 @@ -export { NormalizedSchema, op, struct, error, list, map, sim } from "@smithy/core/schema"; diff --git a/testbed/bundlers/vite.config.ts b/testbed/bundlers/vite.config.ts deleted file mode 100644 index 32fb8e65248..00000000000 --- a/testbed/bundlers/vite.config.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { defineConfig } from "vite"; -import * as path from "node:path"; - -export default defineConfig({ - build: { - outDir: "./dist", - lib: { - entry: path.join(__dirname, "source.ts"), - name: "dist", - // the proper extensions will be added - fileName: "vite-dist", - }, - rollupOptions: { - // make sure to externalize deps that shouldn't be bundled - // into your library - external: [], - output: { - // Provide global variables to use in the UMD build - // for externalized deps - globals: {}, - // to get an easier aggregate accounting of bundle contents - inlineDynamicImports: true, - }, - }, - minify: false, - terserOptions: { - mangle: false, - }, - }, -}); diff --git a/testbed/bundlers/vite.min.config.ts b/testbed/bundlers/vite.min.config.ts deleted file mode 100644 index 81ee4da53b9..00000000000 --- a/testbed/bundlers/vite.min.config.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { defineConfig } from "vite"; -import * as path from "node:path"; - -export default defineConfig({ - build: { - outDir: "./dist-min", - lib: { - entry: path.join(__dirname, "source.ts"), - name: "dist-min", - // the proper extensions will be added - fileName: "vite-dist.min", - }, - rollupOptions: { - // make sure to externalize deps that shouldn't be bundled - // into your library - external: [], - output: { - // Provide global variables to use in the UMD build - // for externalized deps - globals: {}, - // to get an easier aggregate accounting of bundle contents - inlineDynamicImports: true, - }, - }, - minify: true, - terserOptions: { - mangle: true, - }, - }, -}); diff --git a/testbed/bundlers/webpack.config.js b/testbed/bundlers/webpack.config.js deleted file mode 100644 index 143743fbb89..00000000000 --- a/testbed/bundlers/webpack.config.js +++ /dev/null @@ -1,25 +0,0 @@ -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -export default { - mode: "production", - entry: "./source.ts", - target: "web", - output: { - path: path.resolve(__dirname, "dist"), - filename: "webpack-dist.js", - library: "dist", - }, - optimization: { - minimize: false, - splitChunks: false, - runtimeChunk: false, - sideEffects: true, - usedExports: true, - }, - stats: { - optimizationBailout: false, - }, -}; diff --git a/testbed/bundlers/webpack.min.config.js b/testbed/bundlers/webpack.min.config.js deleted file mode 100644 index 98e4f0d7fd0..00000000000 --- a/testbed/bundlers/webpack.min.config.js +++ /dev/null @@ -1,17 +0,0 @@ -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -export default { - mode: "production", - entry: "./source.ts", - target: "web", - output: { - path: path.resolve(__dirname, "dist-min"), - filename: "webpack-dist.min.js", - library: "dist", - }, - optimization: {}, - stats: {}, -};