TypeScript access boundaries: compile-time private members and runtime-reachable exports
0 reputation · 05 Apr 2024, 22:59 UTC
TypeScript's private and protected class modifiers are a compile-time contract. They are erased in emitted JavaScript, so the member remains an ordinary property at runtime. ECMAScript #private fields are enforced by the engine, but they are not erasable, cannot be reached through bracket notation or reflection, and depend on the compilation target.
Module scope adds a separate boundary: TypeScript has no module-private modifier, so any exported declaration becomes part of the public surface. Un-exported declarations stay internal, but nothing prevents a later export.
The unresolved decision is which boundary a codebase should rely on when a member must not be reachable by consumers — the ergonomic compiler-only check, or the stricter runtime guarantee with its constraints on target and tooling. Behavior is assumed for stable TypeScript 5.x with strict enabled; emitted output can be inspected to confirm erasure versus native #private emission.
Which mechanism fits a library whose consumers may compile with different targets? Should protected inheritance be combined with runtime privacy, and at what cost? Which target and strict settings keep the chosen boundary predictable?