diff --git a/fixtures/lockfile-resolution-benchmark/README.md b/fixtures/lockfile-resolution-benchmark/README.md new file mode 100644 index 0000000000..3c001e5883 --- /dev/null +++ b/fixtures/lockfile-resolution-benchmark/README.md @@ -0,0 +1,15 @@ +# Lockfile resolution benchmark + +This fixture compares dry-run deploy performance between the workspace Wrangler +and the prerelease from PR +[#14703](https://github.com/cloudflare/workers-sdk/pull/14703). + +Its `test:ci` script warms both CLIs, runs five alternating trials, and prints a +single timing summary. The fixture test suite runs automatically on Linux, +macOS, and Windows in the main CI workflow. + +Run it locally from the repository root with: + +```sh +pnpm test:ci --filter @fixture/lockfile-resolution-benchmark +``` diff --git a/fixtures/lockfile-resolution-benchmark/benchmark.mjs b/fixtures/lockfile-resolution-benchmark/benchmark.mjs new file mode 100644 index 0000000000..e2c651d1b3 --- /dev/null +++ b/fixtures/lockfile-resolution-benchmark/benchmark.mjs @@ -0,0 +1,93 @@ +import { spawn } from "node:child_process"; +import path from "node:path"; +import { performance } from "node:perf_hooks"; +import { fileURLToPath } from "node:url"; + +const projectPath = path.dirname(fileURLToPath(import.meta.url)); +const trials = 5; +const binaries = { + stable: path.join(projectPath, "node_modules/wrangler/bin/wrangler.js"), + pr: path.join(projectPath, "node_modules/wrangler-pr/bin/wrangler.js"), +}; + +await runWrangler("stable"); +await runWrangler("pr"); + +const results = { stable: [], pr: [] }; +for (let trial = 0; trial < trials; trial++) { + const order = trial % 2 === 0 ? ["stable", "pr"] : ["pr", "stable"]; + for (const variant of order) { + results[variant].push(await runWrangler(variant)); + } +} + +const stable = summarize(results.stable); +const pr = summarize(results.pr); +const pairedDeltas = results.pr.map( + (duration, index) => duration - results.stable[index] +); +const delta = summarize(pairedDeltas); + +console.log(` +Lockfile resolution benchmark (${trials} warmed dry-run deploys) + + median mean min max +Wrangler stable ${format(stable.median)} ${format(stable.mean)} ${format(stable.min)} ${format(stable.max)} +PR #14703 ${format(pr.median)} ${format(pr.mean)} ${format(pr.min)} ${format(pr.max)} + +Median paired overhead: ${format(delta.median)} +Median slowdown: ${((pr.median / stable.median - 1) * 100).toFixed(1)}% +`); + +async function runWrangler(variant) { + const startedAt = performance.now(); + await new Promise((resolve, reject) => { + const child = spawn( + process.execPath, + [binaries[variant], "deploy", "--dry-run"], + { + cwd: projectPath, + env: { + ...process.env, + WRANGLER_SEND_METRICS: "false", + WRANGLER_WRITE_LOGS: "false", + }, + stdio: ["ignore", "pipe", "pipe"], + } + ); + + let stdout = ""; + let stderr = ""; + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (chunk) => (stdout += chunk)); + child.stderr.on("data", (chunk) => (stderr += chunk)); + child.on("error", reject); + child.on("exit", (code, signal) => { + if (code === 0) { + resolve(); + return; + } + reject( + new Error( + `${variant} Wrangler failed (${signal ?? code})\n${stdout}\n${stderr}` + ) + ); + }); + }); + return performance.now() - startedAt; +} + +function summarize(values) { + const sorted = values.toSorted((a, b) => a - b); + return { + min: sorted[0], + median: sorted[Math.floor(sorted.length / 2)], + mean: values.reduce((sum, value) => sum + value, 0) / values.length, + max: sorted[sorted.length - 1], + }; +} + +function format(milliseconds) { + return `${milliseconds.toFixed(1).padStart(7)} ms`; +} diff --git a/fixtures/lockfile-resolution-benchmark/package.json b/fixtures/lockfile-resolution-benchmark/package.json new file mode 100644 index 0000000000..dccd3ff42e --- /dev/null +++ b/fixtures/lockfile-resolution-benchmark/package.json @@ -0,0 +1,17 @@ +{ + "name": "@fixture/lockfile-resolution-benchmark", + "private": true, + "scripts": { + "test:ci": "node benchmark.mjs" + }, + "dependencies": { + "jsonc-parser": "3.2.0" + }, + "devDependencies": { + "wrangler": "workspace:*", + "wrangler-pr": "catalog:lockfile-benchmark" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/fixtures/lockfile-resolution-benchmark/src/index.js b/fixtures/lockfile-resolution-benchmark/src/index.js new file mode 100644 index 0000000000..116f375a36 --- /dev/null +++ b/fixtures/lockfile-resolution-benchmark/src/index.js @@ -0,0 +1,7 @@ +import { parse } from "jsonc-parser"; + +export default { + fetch() { + return new Response(parse('"Hello World!"')); + }, +}; diff --git a/fixtures/lockfile-resolution-benchmark/turbo.json b/fixtures/lockfile-resolution-benchmark/turbo.json new file mode 100644 index 0000000000..15212d2569 --- /dev/null +++ b/fixtures/lockfile-resolution-benchmark/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "test:ci": { + "dependsOn": ["wrangler#build"] + } + } +} diff --git a/fixtures/lockfile-resolution-benchmark/wrangler.jsonc b/fixtures/lockfile-resolution-benchmark/wrangler.jsonc new file mode 100644 index 0000000000..c44e8194a9 --- /dev/null +++ b/fixtures/lockfile-resolution-benchmark/wrangler.jsonc @@ -0,0 +1,6 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "lockfile-resolution-benchmark", + "main": "src/index.js", + "compatibility_date": "2026-07-20", +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d9a05df14f..8bb6014215 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -93,6 +93,10 @@ catalogs: zod: specifier: 4.4.3 version: 4.4.3 + lockfile-benchmark: + wrangler-pr: + specifier: https://pkg.pr.new/wrangler@14703 + version: 4.112.0 overrides: '@types/react-dom@18>@types/react': ^18 @@ -560,6 +564,19 @@ importers: fixtures/legacy-site-app: {} + fixtures/lockfile-resolution-benchmark: + dependencies: + jsonc-parser: + specifier: 3.2.0 + version: 3.2.0 + devDependencies: + wrangler: + specifier: workspace:* + version: link:../../packages/wrangler + wrangler-pr: + specifier: catalog:lockfile-benchmark + version: wrangler@https://pkg.pr.new/wrangler@14703(@cloudflare/workers-types@5.20260714.1) + fixtures/miniflare-node-test: devDependencies: '@cloudflare/workers-tsconfig': @@ -5465,6 +5482,11 @@ packages: resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} engines: {node: '>=18.0.0'} + '@cloudflare/kv-asset-handler@https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/kv-asset-handler@be782c6': + resolution: {tarball: https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/kv-asset-handler@be782c6} + version: 0.5.0 + engines: {node: '>=22.0.0'} + '@cloudflare/playwright@1.0.0': resolution: {integrity: sha512-Z5LFFPJl1HxiI5+qJr+C3eoP8Lt5cv1xFesAWQG4ywL/Szx3b6S6ovJgOrPPZuZ6K9P5AFyqYEqFT3GjfVm/yA==} @@ -5519,6 +5541,16 @@ packages: workerd: optional: true + '@cloudflare/unenv-preset@https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/unenv-preset@be782c6': + resolution: {tarball: https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/unenv-preset@be782c6} + version: 2.16.1 + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: '>1.20260305.0 <2.0.0-0' + peerDependenciesMeta: + workerd: + optional: true + '@cloudflare/util-en-garde@8.0.10': resolution: {integrity: sha512-qdCFf90hoZzT4o4xEmxOKUf9+bEJNGh4ANnRYApo6BMyVnHoHEHAQ3nWmGSHBmo+W9hOk2Ik7r1oHLbI0O/RRg==} @@ -12647,6 +12679,12 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + miniflare@https://pkg.pr.new/cloudflare/workers-sdk/miniflare@be782c6: + resolution: {tarball: https://pkg.pr.new/cloudflare/workers-sdk/miniflare@be782c6} + version: 4.20260714.0 + engines: {node: '>=22.0.0'} + hasBin: true + minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} @@ -15532,6 +15570,17 @@ packages: '@cloudflare/workers-types': optional: true + wrangler@https://pkg.pr.new/wrangler@14703: + resolution: {tarball: https://pkg.pr.new/wrangler@14703} + version: 4.112.0 + engines: {node: '>=22.0.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^5.20260714.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -17052,6 +17101,8 @@ snapshots: '@cloudflare/kv-asset-handler@0.4.2': {} + '@cloudflare/kv-asset-handler@https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/kv-asset-handler@be782c6': {} + '@cloudflare/playwright@1.0.0': {} '@cloudflare/puppeteer@1.0.4': @@ -17151,6 +17202,12 @@ snapshots: optionalDependencies: workerd: 1.20260317.1 + '@cloudflare/unenv-preset@https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/unenv-preset@be782c6(unenv@2.0.0-rc.24)(workerd@1.20260714.1)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20260714.1 + '@cloudflare/util-en-garde@8.0.10': dependencies: fp-ts: 2.13.1 @@ -23910,6 +23967,18 @@ snapshots: - bufferutil - utf-8-validate + miniflare@https://pkg.pr.new/cloudflare/workers-sdk/miniflare@be782c6: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.34.5 + undici: 7.28.0 + workerd: 1.20260714.1 + ws: 8.21.0 + youch: 4.1.0-beta.10(patch_hash=3e73fc48581841f22ea1d2cf5a3560bde280fdba04940253073fb121a70a9269) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + minimatch@10.2.5: dependencies: brace-expansion: 5.0.5 @@ -27293,6 +27362,23 @@ snapshots: - bufferutil - utf-8-validate + wrangler@https://pkg.pr.new/wrangler@14703(@cloudflare/workers-types@5.20260714.1): + dependencies: + '@cloudflare/kv-asset-handler': https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/kv-asset-handler@be782c6 + '@cloudflare/unenv-preset': https://pkg.pr.new/cloudflare/workers-sdk/@cloudflare/unenv-preset@be782c6(unenv@2.0.0-rc.24)(workerd@1.20260714.1) + blake3-wasm: 2.1.5 + esbuild: 0.28.1 + miniflare: https://pkg.pr.new/cloudflare/workers-sdk/miniflare@be782c6 + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260714.1 + optionalDependencies: + '@cloudflare/workers-types': 5.20260714.1 + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8f737c7f18..4acb8d5d91 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -98,6 +98,10 @@ patchedDependencies: # Catalog # ────────────────────────────────────────────────────────────────────────────── +catalogs: + lockfile-benchmark: + wrangler-pr: "https://pkg.pr.new/wrangler@14703" + catalog: chalk: "5.3.0" command-exists: "1.2.9"