Skip to content

refactor(bundle-size): use free-function helpers for DOM event APIs#560

Merged
layershifter merged 1 commit into
microsoft:masterfrom
layershifter:chore/bundle-size-events-helpers
Jun 12, 2026
Merged

refactor(bundle-size): use free-function helpers for DOM event APIs#560
layershifter merged 1 commit into
microsoft:masterfrom
layershifter:chore/bundle-size-events-helpers

Conversation

@layershifter

Copy link
Copy Markdown
Member

Summary

Introduces three single-character-mangleable wrappers in Utils.ts for the DOM event APIs whose property names would otherwise be preserved verbatim by the minifier:

  • addListener / removeListener — thin wraps over addEventListener / removeEventListener, accept nullish targets.
  • dispatchEvent — wraps target.dispatchEvent(event), returns false for nullish targets (matching the prior ?.dispatchEvent short-circuit shape that most call sites already handled).

All src/**/*.ts call sites converted (~71 sites across 11 files). Raw .addEventListener / .removeEventListener / .dispatchEvent property accesses now eliminated outside Utils.ts.

Enforced via a new no-restricted-syntax block in eslint.config.mjs scoped to src/**/*.ts (with src/Utils.ts exempted) so the wrappers can't drift back into raw calls in future PRs.

Notes

A small structural tweak in Deloser._scheduleRestoreFocus: the prior !curDeloserElement?.dispatchEvent(...) short-circuit was rewritten as if (!curDeloserElement) return; ...dispatchEvent(...) to preserve type integrity now that the event constructor's owner: HTMLElement field is no longer protected by optional-chaining laziness. Same control flow as before.

Stack context

A narrower companion to #541 — that PR also has these event-helper changes alongside the timer-helper conversions. Whichever lands first will leave the other a smaller diff after rebase.

Introduces three single-character-mangleable wrappers in `Utils.ts` for
the DOM event APIs whose property names would otherwise be preserved
verbatim by the minifier:

- `addListener` / `removeListener` — thin wraps over `addEventListener` /
  `removeEventListener`, accept nullish targets.
- `dispatchEvent` — wraps `target.dispatchEvent(event)`, returns `false`
  for nullish targets (matching the prior `?.dispatchEvent` short-circuit
  shape that most call sites already handled).

All `src/**/*.ts` call sites converted (~71 sites across 11 files). Raw
`.addEventListener` / `.removeEventListener` / `.dispatchEvent` property
accesses now eliminated outside `Utils.ts`.

Enforced via a new `no-restricted-syntax` block in `eslint.config.mjs`
scoped to `src/**/*.ts` (with `src/Utils.ts` exempted) so the wrappers
can't drift back into raw calls in future PRs.

A small structural tweak in `Deloser._scheduleRestoreFocus`: the prior
`!curDeloserElement?.dispatchEvent(...)` short-circuit was rewritten as
an explicit `if (!curDeloserElement) return; ...dispatchEvent(...)` to
preserve type integrity now that the constructor's `owner: HTMLElement`
field is no longer protected by optional-chaining laziness. Same control
flow as before.
@github-actions

Copy link
Copy Markdown

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
tabster
all exports
111.331 kB
29.989 kB
110.491 kB
29.988 kB
-840 B
-1 B
tabster
createTabster (core)
38.354 kB
11.39 kB
38.098 kB
11.41 kB
-256 B
20 B
tabster
focusable.findAll
38.377 kB
11.399 kB
38.121 kB
11.418 kB
-256 B
19 B
tabster
focusable.findLast
38.378 kB
11.398 kB
38.122 kB
11.418 kB
-256 B
20 B
tabster
focusable.findNext
38.378 kB
11.398 kB
38.122 kB
11.418 kB
-256 B
20 B
tabster
focusable.findPrev
38.378 kB
11.398 kB
38.122 kB
11.418 kB
-256 B
20 B
tabster
getCrossOrigin
108.145 kB
29.263 kB
107.424 kB
29.275 kB
-721 B
12 B
tabster
getDeloser
47.785 kB
13.677 kB
47.459 kB
13.693 kB
-326 B
16 B
tabster
getGroupper
45.58 kB
13.066 kB
45.2 kB
13.071 kB
-380 B
5 B
tabster
getModalizer
47.423 kB
13.82 kB
47.125 kB
13.835 kB
-298 B
15 B
tabster
getMover
53.03 kB
15.32 kB
52.657 kB
15.331 kB
-373 B
11 B
tabster
getObservedElement
44.138 kB
12.967 kB
43.877 kB
12.978 kB
-261 B
11 B
tabster
getOutline
47.054 kB
13.636 kB
46.733 kB
13.653 kB
-321 B
17 B
tabster
getRestorer
41.073 kB
12.002 kB
40.683 kB
12.006 kB
-390 B
4 B

🤖 This report was generated against 18520c1dd878114021e8bc15e1030f4399aa83fa

@layershifter

Copy link
Copy Markdown
Member Author

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

@layershifter
layershifter merged commit 7116678 into microsoft:master Jun 12, 2026
3 checks passed
layershifter added a commit to layershifter/tabster that referenced this pull request Jun 12, 2026
Introduces single-character-mangleable wrappers in `Utils.ts` for the
window timer APIs whose property names would otherwise be preserved
verbatim by the minifier:

- `createTimer` / `setTimer` / `clearTimer` / `isTimerActive`, paired with
  an opaque `Timer = { id }` handle stored on instance fields.

All `src/**/*.ts` call sites converted; raw `.setTimeout` / `.clearTimeout`
property accesses now eliminated outside `Utils.ts`.

Enforced via two additional `no-restricted-syntax` entries in
`eslint.config.mjs` (next to the existing event-helper rules from microsoft#560)
so the wrappers can't drift back into raw timer calls in future PRs.

Follow-up to microsoft#560 — same pattern applied to timer APIs.
layershifter added a commit to layershifter/tabster that referenced this pull request Jun 17, 2026
Introduces single-character-mangleable wrappers in `Utils.ts` for the
window timer APIs whose property names would otherwise be preserved
verbatim by the minifier:

- `setTimer` / `clearTimer` / `isTimerActive`, operating on an opaque
  `Timer = { id }` handle stored on instance fields.

`setTimer(t, window, callback, delay)` lazily creates the `Timer` when
passed `undefined` and returns it, so callers just write
`this._timer = setTimer(this._timer, ...)` — no separate constructor-time
allocation. `Timer.id` is typed as `ReturnType<typeof setTimeout>` so the
id round-trips through `window.setTimeout` / `clearTimeout` without a cast;
`undefined` is the not-pending sentinel.

All `src/**/*.ts` call sites converted; raw `.setTimeout` / `.clearTimeout`
property accesses now eliminated outside `Utils.ts`.

Enforced via two additional `no-restricted-syntax` entries in
`eslint.config.mjs` (next to the existing event-helper rules from microsoft#560)
so the wrappers can't drift back into raw timer calls in future PRs.

Follow-up to microsoft#560 — same pattern applied to timer APIs.
layershifter added a commit to layershifter/tabster that referenced this pull request Jun 17, 2026
Introduces single-character-mangleable wrappers in `Utils.ts` for the
window timer APIs whose property names would otherwise be preserved
verbatim by the minifier:

- `setTimer` / `clearTimer` / `isTimerActive`, operating on an opaque
  `Timer = { id }` handle stored on instance fields.

`setTimer(t, window, callback, delay)` lazily creates the `Timer` when
passed `undefined` and returns it, so callers just write
`this._timer = setTimer(this._timer, ...)` — no separate constructor-time
allocation. `Timer.id` is typed as `ReturnType<typeof setTimeout>` so the
id round-trips through `window.setTimeout` / `clearTimeout` without a cast;
`undefined` is the not-pending sentinel.

All `src/**/*.ts` call sites converted; raw `.setTimeout` / `.clearTimeout`
property accesses now eliminated outside `Utils.ts`.

Enforced via two additional `no-restricted-syntax` entries in
`eslint.config.mjs` (next to the existing event-helper rules from microsoft#560)
so the wrappers can't drift back into raw timer calls in future PRs.

Follow-up to microsoft#560 — same pattern applied to timer APIs.
layershifter added a commit to layershifter/tabster that referenced this pull request Jun 17, 2026
Introduces single-character-mangleable wrappers in `Utils.ts` for the
window timer APIs whose property names would otherwise be preserved
verbatim by the minifier:

- `setTimer` / `clearTimer` / `isTimerActive`, operating on an opaque
  `Timer = { id }` handle stored on instance fields.

`setTimer(t, window, callback, delay)` lazily creates the `Timer` when
passed `undefined` and returns it, so callers just write
`this._timer = setTimer(this._timer, ...)` — no separate constructor-time
allocation. `Timer.id` is typed as `ReturnType<typeof setTimeout>` so the
id round-trips through `window.setTimeout` / `clearTimeout` without a cast;
`undefined` is the not-pending sentinel.

All `src/**/*.ts` call sites converted; raw `.setTimeout` / `.clearTimeout`
property accesses now eliminated outside `Utils.ts`.

Enforced via two additional `no-restricted-syntax` entries in
`eslint.config.mjs` (next to the existing event-helper rules from microsoft#560)
so the wrappers can't drift back into raw timer calls in future PRs.

Follow-up to microsoft#560 — same pattern applied to timer APIs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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.

1 participant