/** * Measures the bytes a browser fetches before the Angular app can bootstrap: * `index.html` itself plus every same-origin script, stylesheet and * `modulepreload` chunk it references. This is the J1 ("launch to usable") * counter `renderer.initialBytes` from docs/architecture/performance-journeys.md. * * The number is read from the built output, not estimated from source, so it * is deterministic for a given build and can be ratcheted in CI. * * Usage: * node tools/performance/measure-initial-bytes.mjs [--dist dist/apps/web] * [--json] [--summary dist/performance/journey-summary.json] */ import { existsSync } from 'node:fs'; import { mkdir, readFile, stat, writeFile } from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { parse } from 'parse5'; export const DEFAULT_DIST_DIR = 'dist/apps/web'; export const INITIAL_BYTES_COUNTER = 'renderer.initialBytes'; export const LAUNCH_JOURNEY = 'launch'; const EXTERNAL_URL = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i; const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; /** * A resource only counts when the browser fetches it on the initial path from * the same origin. Manifest, icons and external URLs are not part of the * payload the ratchet guards: icons load lazily, and external hosts are * outside the build's control. */ function classify(tag, attributes) { if (tag === 'script') { return attributes.src ? { kind: 'script', url: attributes.src } : null; } const rel = (attributes.rel ?? '').toLowerCase().split(/\s+/); if (!attributes.href) return null; if (rel.includes('stylesheet')) { return { kind: 'stylesheet', url: attributes.href }; } if (rel.includes('modulepreload')) { return { kind: 'modulepreload', url: attributes.href }; } return null; } /** * Yields the `script` and `link` elements the browser actually creates, in * document order, by parsing `index.html` with the same HTML5 algorithm the * browser uses (parse5, scripting enabled): comments, doctype and bogus * comments are not elements, script/style/noscript/textarea/title bodies are * text, `