/** * 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'; export const DEFAULT_DIST_DIR = 'dist/apps/web'; export const INITIAL_BYTES_COUNTER = 'renderer.initialBytes'; export const LAUNCH_JOURNEY = 'launch'; const TAG_PATTERN = /<(script|link)\b([^>]*)>/gi; const HTML_COMMENT = //g; const INLINE_ELEMENT_BODY = /(<(script|style)\b[^>]*>)[\s\S]*?(<\/\2\s*>)/gi; const ATTRIBUTE_PATTERN = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g; const EXTERNAL_URL = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i; function parseAttributes(raw) { const attributes = {}; for (const match of raw.matchAll(ATTRIBUTE_PATTERN)) { const [, name, doubleQuoted, singleQuoted, bare] = match; attributes[name.toLowerCase()] = doubleQuoted ?? singleQuoted ?? bare ?? ''; } return attributes; } /** * 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; } /** * Removes the parts of the document the browser never treats as markup: HTML * comments (a commented-out `