Ctrl K
ComunidadGuías…

scripts/worker-closure.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env node
/**
 * Fail when the built Worker's static closure grows too much.
 *
 * The static closure is every chunk the Worker entry imports without
 * `import()`, followed transitively. A Durable Object activation that starts
 * an isolate loads all of it before its constructor runs, at roughly 0.1 s per
 * MiB, so this is a latency budget; the upload budget in verify-worker-size.mjs
 * is a platform limit.
 *
 * Two checks. A change may add GROWTH_ALLOWANCE_BYTES over its merge base
 * (scripts/lib/budget-growth.mjs), measured by building that commit in a
 * temporary export with this checkout's node_modules. So a dependency upgrade
 * counts against the ceiling only. A base that does not build fails the
 * check with the build's output; when it cannot build with these
 * dependencies, CHICKPEA_BUDGET_UNBUILDABLE_BASE=1 checks the ceiling alone.
 * The ceiling backstops the total: set it to the measured size plus 64 KiB
 * once per merge train, and lower it when a change moves code behind
 * `import()`.
 *
 * Reads the build in dist-cf, which must be of this checkout. Its builds run
 * inside verify:regression's host reservation; standalone, wrap it in
 * `npm run verify:host --`.
 */
import { existsSync, readFileSync, rmSync, statSync, symlinkSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import path from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
import { builtWorkerConfigPath, staleBuildReason } from './lib/built-worker-config.mjs';
import { compareSizes, exportCommit, formatGrowth, growthFailure, growthLine, growthPlan, short } from './lib/budget-growth.mjs';

const MIB = 1024 * 1024;
export const WORKER_STATIC_CLOSURE_CEILING_BYTES = Math.round(7.75 * MIB);
export const UNBUILDABLE_BASE_ENV = 'CHICKPEA_BUDGET_UNBUILDABLE_BASE';
const ENTRY = 'virtual:cloudflare/worker-entry';
const BASE_REPORT = 'budget-modules.json';

/**
 * The entry's static chunks, largest first, from the build's Vite manifest,
 * and the modules in them by rendered length when `report` records each
 * module's chunk.
 */
export function measureStaticClosure(directory, report = 'bundle-modules.json') {
  const manifest = JSON.parse(readFileSync(path.join(directory, '.vite', 'manifest.json'), 'utf8'));
  if (!manifest[ENTRY]) throw new Error(`The Vite manifest in ${directory} has no ${ENTRY} entry.`);
  const seen = new Set();
  const pending = [ENTRY];
  for (let key = pending.pop(); key !== undefined; key = pending.pop()) {
    if (seen.has(key)) continue;
    seen.add(key);
    pending.push(...(manifest[key].imports ?? []));
  }
  const chunks = [...seen]
    .map((key) => ({ file: manifest[key].file, bytes: statSync(path.join(directory, manifest[key].file)).size }))
    .sort((a, b) => b.bytes - a.bytes);
  return { chunks, bytes: chunks.reduce((sum, chunk) => sum + chunk.bytes, 0), modules: closureModules(directory, report, chunks) };
}

function closureModules(directory, report, chunks) {
  const file = path.join(directory, report);
  if (!existsSync(file)) return undefined;
  const entries = JSON.parse(readFileSync(file, 'utf8'));
  if (entries.some((entry) => entry.chunk === undefined)) return undefined;
  const files = new Set(chunks.map((chunk) => chunk.file));
  const modules = new Map();
  for (const { id, renderedLength, chunk } of entries) {
    if (files.has(chunk)) modules.set(id, (modules.get(id) ?? 0) + renderedLength);
  }
  return modules;
}

/** Builds `commit` in a temporary export with this checkout's dependencies and measures its closure. */
export function measureCommit(projectRoot, commit) {
  const directory = exportCommit(projectRoot, commit);
  try {
    symlinkSync(path.join(projectRoot, 'node_modules'), path.join(directory, 'node_modules'), 'dir');
    const built = spawnSync(process.execPath, [path.join(projectRoot, 'scripts/lib/build-worker-report.mjs'), BASE_REPORT], {
      cwd: directory, encoding: 'utf8', maxBuffer: 64 * MIB,
    });
    if (built.status !== 0) {
      const lockfile = spawnSync('git', ['-C', projectRoot, 'diff', '--quiet', commit, '--', 'package-lock.json']).status === 0
        ? 'the same as' : 'not';
      throw new Error(`Building ${short(commit)} to measure its closure failed. Its package-lock.json is ${lockfile} ` +
        `this checkout's, whose node_modules it built with.\n${built.stdout}${built.stderr}`);
    }
    return measureStaticClosure(path.dirname(builtWorkerConfigPath(directory)), BASE_REPORT);
  } finally {
    rmSync(directory, { recursive: true, force: true });
  }
}

const mib = (bytes) => `${(bytes / MIB).toFixed(2)} MiB`;
const rowsBetween = (before, after) => before.modules && after.modules ? compareSizes(before.modules, after.modules).rows : undefined;

/**
 * Why the closure fails its budgets, as messages: each change over its
 * allowance, each change that could not be measured unless
 * `unbuildableBaseAllowed`, and the ceiling. `plan` is growthPlan's.
 * `at(commit)` measures a commit, or the working tree when it is undefined,
 * and throws when it cannot. Each change's growth goes to `log`.
 */
export function closureFailures({
  head, plan, at, ceiling = WORKER_STATIC_CLOSURE_CEILING_BYTES, unbuildableBaseAllowed = false, log = console.log,
}) {
  const failures = [];
  if (plan.skip) log(plan.skip);
  if (plan.error) failures.push(plan.error);
  if (plan.changes) log(`The merge base with ${plan.ref} is ${short(plan.base)}.`);
  for (const change of plan.changes ?? []) {
    let before;
    let after;
    try {
      before = at(change.from);
      after = at(change.to);
    } catch (error) {
      if (unbuildableBaseAllowed) {
        log(`Growth in ${change.label} is not checked, as ${UNBUILDABLE_BASE_ENV} is set; the ceiling still is. ${error.message}`);
      } else {
        failures.push(`Growth in ${change.label} could not be measured. ${error.message}\n` +
          `When that commit cannot build with this checkout's dependencies, as after a dependency upgrade, ` +
          `set ${UNBUILDABLE_BASE_ENV}=1 to check the ceiling alone.`);
      }
      continue;
    }
    const growth = after.bytes - before.bytes;
    log(growthLine(change, growth));
    if (growth > change.allowance) {
      failures.push(growthFailure({ what: 'The Worker static closure', change, ref: plan.ref, growth, rows: rowsBetween(before, after) }));
    }
  }
  if (head.bytes > ceiling) {
    let sinceBase;
    try {
      sinceBase = plan.base === undefined ? undefined : rowsBetween(at(plan.base), head);
    } catch {
      // The base's build failure is reported above.
    }
    failures.push(
      `The static closure is ${mib(head.bytes - ceiling)} over its ceiling.` +
      (sinceBase ? `\nModules that grew since the merge base:\n${formatGrowth(sinceBase)}` : '') +
      '\nLoad the new code with import() where it is used. Once per merge train, the ceiling in ' +
      'scripts/worker-closure.mjs may be set to the measured size plus 64 KiB.',
    );
  }
  return failures;
}

if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
  const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
  const stale = staleBuildReason(projectRoot);
  if (stale) {
    console.error(stale);
    process.exit(2);
  }
  const head = measureStaticClosure(path.dirname(builtWorkerConfigPath(projectRoot)));
  console.log(`Worker static closure: ${head.chunks.length} chunks, ${mib(head.bytes)} (ceiling ${mib(WORKER_STATIC_CLOSURE_CEILING_BYTES)})`);
  for (const chunk of head.chunks.slice(0, 8)) console.log(`  ${mib(chunk.bytes).padStart(10)}  ${chunk.file}`);

  // Each commit builds once, and a build that failed is not retried.
  const measured = new Map();
  const at = (commit) => {
    if (commit === undefined) return head;
    if (!measured.has(commit)) {
      try {
        measured.set(commit, { closure: measureCommit(projectRoot, commit) });
      } catch (error) {
        measured.set(commit, { error });
      }
    }
    const { closure, error } = measured.get(commit);
    if (error) throw error;
    return closure;
  };
  const failures = closureFailures({
    head, plan: growthPlan(projectRoot), at, unbuildableBaseAllowed: process.env[UNBUILDABLE_BASE_ENV] === '1',
  });
  for (const failure of failures) console.error(`\n${failure}`);
  if (failures.length > 0) process.exit(1);
}