diff --git a/.changeset/tidy-ravens-report.md b/.changeset/tidy-ravens-report.md new file mode 100644 index 00000000000..83c55e3e713 --- /dev/null +++ b/.changeset/tidy-ravens-report.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +Include exact raw and gzip-compressed Worker bundle sizes in structured `deploy` and `version-upload` output. diff --git a/packages/deploy-helpers/src/deploy/deploy.ts b/packages/deploy-helpers/src/deploy/deploy.ts index f43d1d15009..8dfa5cc6d73 100644 --- a/packages/deploy-helpers/src/deploy/deploy.ts +++ b/packages/deploy-helpers/src/deploy/deploy.ts @@ -22,7 +22,11 @@ import { syncAssets, } from "./helpers/assets"; import { getBindings } from "./helpers/binding-utils"; -import { printBundleSize } from "./helpers/bundle-reporter"; +import { + getSize, + printBundleSize, + type BundleSize, +} from "./helpers/bundle-reporter"; import { confirmLatestDeploymentOverwrite } from "./helpers/confirm-latest-deployment-overwrite"; import { createWorkerUploadForm } from "./helpers/create-worker-upload-form"; import { deployWfpUserWorker } from "./helpers/deploy-wfp"; @@ -144,6 +148,7 @@ export default async function deploy( workerTag: string | null; assetUploadStats?: AssetUploadStats; targets?: string[]; + bundleSize?: BundleSize; }> { const { entry, compatibilityDate, compatibilityFlags, keepVars, accountId } = props; @@ -340,10 +345,8 @@ export default async function deploy( 0 ); - await printBundleSize( - { name: path.basename(resolvedEntryPointPath), content: content }, - modules - ); + const bundleSize = await getSize([...modules, { content }]); + printBundleSize(bundleSize); // We can use the new versions/deployments APIs if we: // * are uploading a worker that already exists @@ -719,7 +722,7 @@ export default async function deploy( if (isDryRun) { logger.log(`--dry-run: exiting now.`); - return { versionId, workerTag }; + return { versionId, workerTag, bundleSize }; } const uploadMs = Date.now() - start; @@ -742,7 +745,7 @@ export default async function deploy( // Early exit for WfP since it doesn't need the below code if (props.dispatchNamespace !== undefined) { deployWfpUserWorker(props.dispatchNamespace, versionId); - return { versionId, workerTag, assetUploadStats }; + return { versionId, workerTag, assetUploadStats, bundleSize }; } assert(accountId); // deploy triggers @@ -765,5 +768,6 @@ export default async function deploy( workerTag, assetUploadStats, targets: targets ?? [], + bundleSize, }; } diff --git a/packages/deploy-helpers/src/deploy/helpers/bundle-reporter.ts b/packages/deploy-helpers/src/deploy/helpers/bundle-reporter.ts index ab891b3a916..06b443c3bc1 100644 --- a/packages/deploy-helpers/src/deploy/helpers/bundle-reporter.ts +++ b/packages/deploy-helpers/src/deploy/helpers/bundle-reporter.ts @@ -9,7 +9,14 @@ const ONE_KIB_BYTES = 1024; // See https://developers.cloudflare.com/workers/platform/limits/#worker-size const MAX_GZIP_SIZE_BYTES = 3 * ONE_KIB_BYTES * ONE_KIB_BYTES; -async function getSize(modules: Pick[]) { +export interface BundleSize { + size: number; + gzipSize: number; +} + +export async function getSize( + modules: Pick[] +): Promise { const gzipSize = gzipSync( await new Blob(modules.map((file) => file.content)).arrayBuffer() ).byteLength; @@ -18,15 +25,7 @@ async function getSize(modules: Pick[]) { return { size: aggregateSize, gzipSize }; } -export async function printBundleSize( - main: { - name: string; - content: string; - }, - modules: CfModule[] -) { - const { size, gzipSize } = await getSize([...modules, main]); - +export function printBundleSize({ size, gzipSize }: BundleSize) { const bundleReport = `${(size / ONE_KIB_BYTES).toFixed(2)} KiB / gzip: ${( gzipSize / ONE_KIB_BYTES ).toFixed(2)} KiB`; diff --git a/packages/deploy-helpers/src/deploy/versions-upload.ts b/packages/deploy-helpers/src/deploy/versions-upload.ts index ebce780fd4a..f8cd5f801f8 100644 --- a/packages/deploy-helpers/src/deploy/versions-upload.ts +++ b/packages/deploy-helpers/src/deploy/versions-upload.ts @@ -15,7 +15,11 @@ import { getWorkersDevSubdomain } from "../triggers/subdomain"; import { resolveAssetOptions, syncAssets } from "./helpers/assets"; import { renderBindingDependsOnExportError } from "./helpers/binding-depends-on-export"; import { getBindings } from "./helpers/binding-utils"; -import { printBundleSize } from "./helpers/bundle-reporter"; +import { + getSize, + printBundleSize, + type BundleSize, +} from "./helpers/bundle-reporter"; import { createWorkerUploadForm } from "./helpers/create-worker-upload-form"; import { applyServiceAndEnvironmentTags, @@ -63,6 +67,7 @@ export default async function versionsUpload( assetUploadStats?: AssetUploadStats; versionPreviewUrl?: string | undefined; versionPreviewAliasUrl?: string | undefined; + bundleSize?: BundleSize; }> { const { entry, compatibilityDate, compatibilityFlags, keepVars, accountId } = props; @@ -199,10 +204,8 @@ export default async function versionsUpload( : undefined, }; - await printBundleSize( - { name: path.basename(resolvedEntryPointPath), content: content }, - modules - ); + const bundleSize = await getSize([...modules, { content }]); + printBundleSize(bundleSize); let workerBundle: FormData; @@ -382,7 +385,7 @@ export default async function versionsUpload( if (props.dryRun) { logger.log(`--dry-run: exiting now.`); - return { versionId, workerTag }; + return { versionId, workerTag, bundleSize }; } assert(accountId); @@ -433,5 +436,6 @@ Changes to triggers (routes, custom domains, cron schedules, etc) must be applie assetUploadStats: assetsUploadResult?.assetUploadStats, versionPreviewUrl, versionPreviewAliasUrl, + bundleSize, }; } diff --git a/packages/wrangler/src/__tests__/deploy/build.test.ts b/packages/wrangler/src/__tests__/deploy/build.test.ts index b0b54e0173e..99654a8641f 100644 --- a/packages/wrangler/src/__tests__/deploy/build.test.ts +++ b/packages/wrangler/src/__tests__/deploy/build.test.ts @@ -11,7 +11,10 @@ import { import * as esbuild from "esbuild"; import { http, HttpResponse } from "msw"; import { afterEach, beforeEach, describe, it, test, vi } from "vitest"; -import { printBundleSize } from "../../deployment-bundle/bundle-reporter"; +import { + getSize, + printBundleSize, +} from "../../deployment-bundle/bundle-reporter"; import { clearOutputFilePath } from "../../output"; import { diagnoseScriptSizeError } from "../../utils/friendly-validator-errors"; import { mockAccountId, mockApiToken } from "../helpers/mock-account-id"; @@ -1117,17 +1120,19 @@ export default { fetch() { return new Response(foo); } }` // keeping these as unit tests to try and keep them snappy, as they often deal with // big files that would take a while to deal with in a full wrangler test - test("should print the bundle size", async ({ expect }) => { + test("should calculate the bundle size", async ({ expect }) => { const bigModule = Buffer.alloc(10_000_000); randomFillSync(bigModule); - await printBundleSize({ name: "index.js", content: "" }, [ - { - name: "index.js", - filePath: undefined, - content: bigModule, - type: "buffer", - }, - ]); + const bundleSize = await getSize([{ content: bigModule }]); + + expect(bundleSize).toEqual({ + size: 10_000_000, + gzipSize: expect.any(Number), + }); + }); + + test("should print the bundle size", ({ expect }) => { + printBundleSize({ size: 10_000_000, gzipSize: 10_000_000 }); expect(std).toMatchInlineSnapshot(` { diff --git a/packages/wrangler/src/__tests__/deploy/core.test.ts b/packages/wrangler/src/__tests__/deploy/core.test.ts index 75c5a6c9326..326b40f1f54 100644 --- a/packages/wrangler/src/__tests__/deploy/core.test.ts +++ b/packages/wrangler/src/__tests__/deploy/core.test.ts @@ -1957,7 +1957,13 @@ describe("deploy", () => { .map((line) => JSON.parse(line)) as OutputEntry[]; expect(outputEntries).toContainEqual( - expect.objectContaining({ type: "deploy" }) + expect.objectContaining({ + type: "deploy", + bundle_size: { + raw_bytes: expect.any(Number), + gzip_bytes: expect.any(Number), + }, + }) ); const autoconfigOutputEntry = outputEntries.find( diff --git a/packages/wrangler/src/deploy/index.ts b/packages/wrangler/src/deploy/index.ts index 5820d2f539a..44877d13a07 100644 --- a/packages/wrangler/src/deploy/index.ts +++ b/packages/wrangler/src/deploy/index.ts @@ -20,7 +20,7 @@ import { import { experimentalNewConfigArg } from "../experimental-config/cli-flag"; import { logger } from "../logger"; import * as metrics from "../metrics"; -import { writeOutput } from "../output"; +import { formatBundleSizeOutput, writeOutput } from "../output"; import { syncWorkersSite } from "../sites"; import { detectAgent } from "../utils/detect-agent"; import { getScriptName } from "../utils/getScriptName"; @@ -195,14 +195,20 @@ export async function runDeployCommandHandler( const buildResult = await buildWorker(buildProps, config); - const { sourceMapSize, versionId, workerTag, assetUploadStats, targets } = - await deploy(props, config, buildResult, { - syncWorkersSite, - getNormalizedContainerOptions, - buildContainer, - deployContainers, - analyseBundle, - }); + const { + sourceMapSize, + versionId, + workerTag, + assetUploadStats, + targets, + bundleSize, + } = await deploy(props, config, buildResult, { + syncWorkersSite, + getNormalizedContainerOptions, + buildContainer, + deployContainers, + analyseBundle, + }); writeOutput({ type: "deploy", @@ -213,6 +219,7 @@ export async function runDeployCommandHandler( targets, wrangler_environment: args.env, worker_name_overridden: workerNameOverridden, + bundle_size: formatBundleSizeOutput(bundleSize), }); metrics.sendMetricsEvent( diff --git a/packages/wrangler/src/deployment-bundle/bundle-reporter.ts b/packages/wrangler/src/deployment-bundle/bundle-reporter.ts index e578708e722..52fad20a583 100644 --- a/packages/wrangler/src/deployment-bundle/bundle-reporter.ts +++ b/packages/wrangler/src/deployment-bundle/bundle-reporter.ts @@ -1 +1 @@ -export { printBundleSize } from "@cloudflare/deploy-helpers"; +export { getSize, printBundleSize } from "@cloudflare/deploy-helpers"; diff --git a/packages/wrangler/src/dev/remote.ts b/packages/wrangler/src/dev/remote.ts index 07016837a1f..741c9931548 100644 --- a/packages/wrangler/src/dev/remote.ts +++ b/packages/wrangler/src/dev/remote.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { syncAssets } from "@cloudflare/deploy-helpers"; import { APIError, UserError } from "@cloudflare/workers-utils"; import { isAuthenticationError } from "../core/handle-errors"; -import { printBundleSize } from "../deployment-bundle/bundle-reporter"; +import { getSize, printBundleSize } from "../deployment-bundle/bundle-reporter"; import { getBundleType } from "../deployment-bundle/bundle-type"; import { withSourceURLs } from "../deployment-bundle/source-url"; import { getInferredHost } from "../dev"; @@ -164,13 +164,7 @@ export async function createRemoteWorkerInit(props: { ); // TODO: For Dev we could show the reporter message in the interactive box. - void printBundleSize( - { - name: path.basename(props.bundle.path), - content, - }, - props.modules - ); + void getSize([...props.modules, { content }]).then(printBundleSize); const workersSitesAssets = await syncWorkersSite( props.complianceConfig, diff --git a/packages/wrangler/src/output.ts b/packages/wrangler/src/output.ts index 65d845d6154..4c6cf9008af 100644 --- a/packages/wrangler/src/output.ts +++ b/packages/wrangler/src/output.ts @@ -7,6 +7,7 @@ import { } from "@cloudflare/workers-utils"; import { ensureDirectoryExistsSync } from "./utils/filesystem"; import type { AutoConfigSummary } from "@cloudflare/autoconfig"; +import type { BundleSize } from "@cloudflare/deploy-helpers"; /** * Write an entry to the output file. @@ -33,6 +34,15 @@ export function clearOutputFilePath() { outputFilePath = undefined; } +export function formatBundleSizeOutput(bundleSize: BundleSize | undefined) { + return bundleSize + ? { + raw_bytes: bundleSize.size, + gzip_bytes: bundleSize.gzipSize, + } + : undefined; +} + let outputFilePath: string | null | undefined = undefined; function getOutputFilePath() { const outputFilePathFromEnv = getOutputFilePathFromEnv(); @@ -63,6 +73,13 @@ interface OutputEntryBase { type: T; } +interface OutputEntryBundleSize { + /** The uncompressed size of the Worker bundle. */ + raw_bytes: number; + /** The gzip-compressed size of the Worker bundle. */ + gzip_bytes: number; +} + /** * All the different types of entry you can output. */ @@ -101,6 +118,8 @@ interface OutputEntryDeployment extends OutputEntryBase<"deploy"> { worker_name_overridden: boolean; /** wrangler environment used */ wrangler_environment: string | undefined; + /** Exact Worker bundle sizes in bytes. */ + bundle_size?: OutputEntryBundleSize; } interface OutputEntryPreview extends OutputEntryBase<"preview"> { @@ -177,6 +196,8 @@ interface OutputEntryVersionUpload extends OutputEntryBase<"version-upload"> { worker_name_overridden: boolean; /** wrangler environment used */ wrangler_environment: string | undefined; + /** Exact Worker bundle sizes in bytes. */ + bundle_size?: OutputEntryBundleSize; } interface OutputEntryVersionDeployment extends OutputEntryBase<"version-deploy"> { diff --git a/packages/wrangler/src/versions/upload.ts b/packages/wrangler/src/versions/upload.ts index 5bd4f187790..f230127e1f3 100644 --- a/packages/wrangler/src/versions/upload.ts +++ b/packages/wrangler/src/versions/upload.ts @@ -15,7 +15,7 @@ import { } from "../deployment-bundle/merge-config-args"; import { experimentalNewConfigArg } from "../experimental-config/cli-flag"; import * as metrics from "../metrics"; -import { writeOutput } from "../output"; +import { formatBundleSizeOutput, writeOutput } from "../output"; import { getScriptName } from "../utils/getScriptName"; export const versionsUploadCommand = createCommand({ @@ -70,6 +70,7 @@ export const versionsUploadCommand = createCommand({ assetUploadStats: uploadStats, versionPreviewUrl, versionPreviewAliasUrl, + bundleSize, } = await versionsUpload(props, config, buildResult, { analyseBundle: analyseBundle, }); @@ -85,6 +86,7 @@ export const versionsUploadCommand = createCommand({ preview_alias_url: versionPreviewAliasUrl, wrangler_environment: args.env, worker_name_overridden: workerNameOverridden, + bundle_size: formatBundleSizeOutput(bundleSize), }); } finally { metrics.sendMetricsEvent(