Skip to content

Freeze the remaining exported constants that double as default arguments - #19

Merged
Hugoer merged 4 commits into
mainfrom
fix/freeze-remaining-exported-constants
Sep 3, 2026
Merged

Freeze the remaining exported constants that double as default arguments#19
Hugoer merged 4 commits into
mainfrom
fix/freeze-remaining-exported-constants

Conversation

@Hugoer

@Hugoer Hugoer commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Closes #18.

Freezes the five exported constants that double as default arguments, applying the same pattern — and the same lesson — as the CrUX pair in #17.

What was exposed

Each of these is exported from a public subpath and used as a default parameter value, so a consumer mutating one changed behaviour for every later call in the process:

Constant Subpath Was the default for
DEFAULT_PSI_STRATEGIES psi runPsi, runPsiBatch, runPsiAuditBatch
PSI_STRATEGIES psi strategy validation
DEFAULT_SKIP_AUDITS lab, root buildLighthouseConfig
CHROME_FLAGS lab, root every Chrome launch in lab.js and links.js
LAB_CATEGORIES profiles --category validation

DEFAULT_PSI_CATEGORIES is internal rather than exported, but is a default argument in the same way, so it is frozen alongside them.

CHROME_FLAGS is the sharpest of the five. The others cost quota — an extra request per URL against 25,000/day. Appending a flag to CHROME_FLAGS changes how every subsequent audit launches the browser, which silently invalidates scores.

Freezing alone would have repeated #17's mistake

DEFAULT_CRUX_FORM_FACTORS was frozen in #17 but declared string[], and string is not assignable to CruxFormFactor — so the constant could never be passed back into the option it was the default for. It was frozen and unusable at the same time, and the freeze only changed the error code from TS2322 to TS4104.

So each constant here gets both halves: an element type (readonly PsiStrategy[], not readonly string[]), and the options that receive it widened to accept a readonly array.

The chrome-launcher question the issue flagged

Answered: chrome-launcher types chromeFlags as a mutable Array<string>, so a frozen CHROME_FLAGS cannot be passed to it directly. The three call sites in lab.js and links.js now spread, the constant's comment says consumers must do the same, and type-tests asserts that spread keeps compiling.

That is a real ergonomic cost on the most likely use of the export, and it is the one judgement call in this PR. I took it because the alternative is an array that every audit launch reads and any consumer can quietly edit.

Verification

Every assertion mutation-tested — six reverts, six failures:

un-freeze PSI_STRATEGIES            -> caught
un-freeze DEFAULT_PSI_STRATEGIES    -> caught
narrow PsiBatchOptions.strategies   -> caught
un-freeze DEFAULT_SKIP_AUDITS       -> caught
un-freeze LAB_CATEGORIES            -> caught
un-freeze CHROME_FLAGS              -> caught
  • npm run lint — 0 problems.
  • npm test — 565 passing, unchanged.
  • npm run generate-types — committed, no drift.
  • npm run check-types — passes.
  • Real browser launches, since the chromeFlags spread is a runtime change: the links command wrote output, and lab-audit, lab-save-runs, psi-audit and psi-save examples all pass.
  • README needed no change — none of these constants appear in it.

Deliberately out of scope

PROFILES, NETWORK_PRESETS and DEVICE_PRESETS in lib/profiles.js are exported objects read by resolveProfileSettings, so PROFILES.low.network = 'wifi' would change every later audit — the same hazard class. They need a deep freeze, since Object.freeze would leave the nested preset objects writable, and that has different type implications. Filed separately rather than smuggled in here.

PSI_STRATEGIES and DEFAULT_PSI_STRATEGIES are exported from web-perf-cli/psi and are also the
default `strategies` for runPsi, runPsiBatch and runPsiAuditBatch. A consumer calling push on
DEFAULT_PSI_STRATEGIES would have added a strategy to every later call in the process — an
extra API request per URL against the 25,000/day quota. DEFAULT_PSI_CATEGORIES is internal but
is a default argument in the same way, so it is frozen alongside them.

Each carries its element type (`readonly PsiStrategy[]`), not bare `string[]`, so the exported
constant can still be passed to the option it is the default for. That was the mistake in the
CrUX freeze: declared `string[]`, the constant could never be passed back into its own option,
and freezing only changed the error code.

The strategies options accept a readonly array for the same reason. The two batch option
objects are promoted to named typedefs (PsiBatchOptions, PsiWriteBatchOptions) because the
inline forms went past the line limit once widened.
Both are exported from web-perf-cli/lab and the package root. DEFAULT_SKIP_AUDITS is the
fallback inside buildLighthouseConfig, so mutating it silently skips an extra audit in every
later run. CHROME_FLAGS is the sharper one: it is handed to every Chrome launch in lab.js and
links.js, so appending a flag changes how every subsequent audit launches the browser —
invalidating scores rather than merely costing quota.

chrome-launcher types chromeFlags as a mutable Array<string>, so the three call sites now
spread. Consumers passing CHROME_FLAGS to chromeLauncher.launch() must do the same; the
constant's comment says so, and type-tests asserts the spread keeps compiling.

Verified with real browser launches: the links command and the lab-audit and lab-save-runs
examples all still start Chrome and write output.
Exported from web-perf-cli/profiles and used to validate --category input, so mutating it
changes which categories the CLI accepts for the rest of the process.
Each constant is asserted three ways, because freezing alone is not the property that matters:
pass it into the option it is the default for, spread it to extend, and fail to mutate it. The
middle assertion is the one the CrUX pair failed — declared `string[]`, the constant could not
be passed back into `formFactors`, so it was frozen and unusable at the same time.

Mutation-tested, six reverts and six failures:

  un-freeze PSI_STRATEGIES            -> caught
  un-freeze DEFAULT_PSI_STRATEGIES    -> caught
  narrow PsiBatchOptions.strategies   -> caught
  un-freeze DEFAULT_SKIP_AUDITS       -> caught
  un-freeze LAB_CATEGORIES            -> caught
  un-freeze CHROME_FLAGS              -> caught
@Hugoer
Hugoer merged commit 5d23119 into main Sep 3, 2026
6 checks passed
@Hugoer
Hugoer deleted the fix/freeze-remaining-exported-constants branch September 3, 2026 20:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Freeze the remaining exported constants that double as default arguments

1 participant