Skip to content

format: parse schemas lazily - #373

Merged
gnidan merged 3 commits into
mainfrom
format-lazy-schemas
Oct 8, 2026
Merged

gnidan merged 3 commits into
mainfrom
format-lazy-schemas

Conversation

@gnidan

@gnidan gnidan commented Oct 8, 2026

Copy link
Copy Markdown
Member

The package root re-exports schemas, and schemas/index.ts parsed all of the schema YAML at module load. Every consumer of the root, including @ethdebug/pointers, paid for the yaml library, about 144 KB of schema sources, and the parse, even when it never touched schemas.

This keeps the API as it is and moves the cost to first use:

  • Each entry of schemas is a getter that parses that one schema on first read, then turns into a plain data property. Keys, enumeration order, values, and the type stay the same, and assignment still works.
  • package.json declares "sideEffects": false, so a bundler drops #schemas, #describe, the YAML sources, and yaml when nothing imports them. describeSchema already parsed only on call.
  • The generated yamls.ts used to carry a runtime as const copy of every parsed schema only to derive the Schema<Id> type. The generator now writes that type directly, so the module holds only the YAML strings.

Measured with esbuild (minified ESM, gzipped) and Node 20 import time:

consumer before after
import { isPointer } from "@ethdebug/format" 64.4 KB, ~110 ms 1.5 KB, ~3 ms
import { dereference } from "@ethdebug/pointers" 70.7 KB, ~90-140 ms 7.8 KB, ~5 ms
import { schemas } from "@ethdebug/format" 64.4 KB, ~90-120 ms 60.4 KB, ~5 ms (parse moves to first read)

New tests check that importing the root parses no YAML until a schema is read, that every schema equals its parsed YAML, and that an esbuild bundle of the root without schemas holds no yaml code or schema sources.

The package root re-exports `schemas`, which parsed every schema YAML
at module load. Consumers that only use the types and guards (such as
@ethdebug/pointers) paid for the yaml library, all schema sources, and
the parse.

Define each entry of `schemas` as a getter that parses on first read
and then becomes a plain data property. Mark the package side-effect
free so bundlers drop the schema modules when nothing imports them. The
generated Schema type no longer comes from a runtime `as const` copy
of every parsed schema; the generator writes it as a type instead.
@gnidan
gnidan force-pushed the format-lazy-schemas branch from f9d9172 to d6bf7b0 Compare October 8, 2026 22:43
@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-08 23:24 UTC

gnidan added 2 commits October 8, 2026 19:14
Freezing or sealing `schemas` made each accessor non-configurable, so
the first read threw when it tried to redefine the property. Keep the
parsed value in the accessor's closure and redefine the property only
while it is still configurable. Load a fresh module for each test so the
tests do not share state.
The `#...` imports resolved to `dist/` under vitest, so a stale build
could hide a regression. Alias them to their `types` entries, which
point at source. Also build the bundle test's plugin paths with a URL
relative to the package and skip imports that have no `types` entry.
@gnidan
gnidan merged commit 889f1b9 into main Oct 8, 2026
8 checks passed
@gnidan
gnidan deleted the format-lazy-schemas branch October 8, 2026 23:21
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