Skip to content

Split the selector engine and the CSS value layer into their own modules - #87

Open
brainkim wants to merge 36 commits into
mainfrom
split-modules
Open

Split the selector engine and the CSS value layer into their own modules#87
brainkim wants to merge 36 commits into
mainfrom
split-modules

Conversation

@brainkim

@brainkim brainkim commented Sep 7, 2026

Copy link
Copy Markdown
Member

Two cuts along slot ownership, so no symbol crosses a module boundary, and a sweep so CSS text is parsed in one place.

src/internal/selectors.ts (2155 lines out of dom.ts). Selector parsing, compilation and matching. It reads the tree through four link functions dom.ts now exports, getParentNode, getFirstChildNode, getNextSiblingNode and getPreviousSiblingNode, and the state predicates that already existed as functions, now exported. Those predicates read slots, so they stay in dom.ts; the export list is the selector engine's state interface. dom.ts imports the five query functions back.

src/internal/cssvalues.ts (about 5800 lines out of cssom.ts). Every declaration that touches no slot, no class and nothing from dom.ts: the value grammar, serialization, colors, shorthands, the property tables, and the media query, grid track, easing, calc and counter parsers, with the loops that fill their tables. cssom.ts keeps the CSSOM objects, the cascade, and computed and used values at about 7500 lines, and imports the value functions and tables it needs. The dependency runs one way. Six tree-walking helpers that passed the purity test on a technicality went back to cssom.ts.

One parser. dom.ts parsed IntersectionObserver's rootMargin with its own regex; layout.ts read flex numbers, order, z-index, alignment qualifiers and grid-auto-flow inline; painter.ts parsed font-weight, text-decoration-line, outline-width and the Canvas and Highlight system colors itself. Each now calls a named function in cssvalues.ts.

Namespaces. The six namespace URIs live once in htmltables.ts, beside the tables that name elements in them, instead of three copies.

dom.ts goes from 33,501 to 31,416 lines and cssom.ts from 13,200 to about 7500.

Typecheck, lint, suite 1619/1619 on node and bun, 37/37 examples, lib.dom drift 148 exact.

🤖 Generated with Claude Code

https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D

It parses and compiles selectors and matches them against the tree. It
reads the tree through four link functions dom.ts exports and the state
predicates that already existed as functions, so no symbol crosses the
module boundary. dom.ts imports the five query functions back.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
@brainkim brainkim changed the title Split the selector engine and CSS value parsing into their own modules Move the selector engine to its own module Sep 7, 2026
brainkim and others added 4 commits September 7, 2026 13:28
Every declaration in cssom.ts that touches no slot, no class and nothing
from dom.ts moves: the value grammar, serialization, colors, shorthands,
the property tables, and the media query, grid track, easing, calc and
counter parsers, with the loops that fill their tables. cssom.ts keeps
the CSSOM objects, the cascade, and computed and used values, and
imports the value functions and tables it needs. layout and painter
import the value types and color helpers from the new module.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
dom.ts parsed IntersectionObserver's rootMargin with its own length
regex; layout.ts read flex-grow, flex-shrink, order, z-index, alignment
qualifiers and grid-auto-flow inline; painter.ts parsed font-weight,
text-decoration-line, outline-width and the Canvas and Highlight system
colors itself. Each now calls a function in cssvalues.ts: parseCSSNumber,
parseCSSInteger, parseEdgeLengths, parseAlignmentKeyword,
parseGridAutoFlow, parseFontWeight, parseTextDecorationLine,
isCanvasColor and isHighlightColor, next to the parsers they duplicated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
dom.ts, cssom.ts and the value layer each declared their own copy of the
HTML namespace. The six namespace URIs live once, beside the tables that
name elements in them, and every module imports the ones it reads.
Three types nothing imports lose their export.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Six functions that take a node moved with the value layer because they
read the tree through the public DOM API alone. They are cascade
helpers, not value parsing, and go back beside their callers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
@brainkim brainkim changed the title Move the selector engine to its own module Split the selector engine and the CSS value layer into their own modules Sep 7, 2026
brainkim and others added 2 commits September 7, 2026 13:53
The NUL that keeps an unnamed @layer block unreachable by name was a
raw byte in the source, which made grep treat the file as binary.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The cascade reads 98 names from cssvalues.ts, a wide one-way interface
like the one index.ts takes from dom.ts, so it imports the module the
same way and each call names the layer it asks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
brainkim and others added 18 commits September 8, 2026 14:06
The value, stylesheet, selector and at-rule prelude node types are
declared on the css-tree module, so cssvalues, cssom and selectors read
them as CSSTree.* instead of exporting them to each other.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The CSSOM rule types, the resolved-value property lists, the inset
tables, the list gutter, the descriptor names, the scope and layer
sentinels and the paint-only list move to cssom. The grid placement
and track sentinels move to layout. Computed styles reflect every
indexed property, as CSSStyleProperties already does.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Each set cssom tested membership in becomes a predicate, the shorthand
maps become getLonghands and getShorthands, the initial-value lookup
becomes getInitialValue, the keyframes name serializes in cssvalues,
and a selector reading says whether it reaches siblings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The legacy pseudo-element set becomes isLegacyPseudoElement. The empty
namespaces object goes away: an undefined namespaces means a sheet
declared none, so the parameters are optional and cssom no longer
compares against the sentinel to decide whether a rule carries
namespaces. The attribute-name serializer takes a flag instead of a
namespaces object it recognized by identity.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
LayoutNode and the flex, table, grid, block and margin-collapse
algorithms over it know nothing about the DOM, and flex.test already
drives them without one. layout.ts keeps the mapping from the DOM to
boxes and imports the solver.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The selection's two composed points are made once and moved. Its own
range is moved too while no script holds it, and replaced only after
getRangeAt hands it out. The caret-column probe in modify asks layout
for a caret rect at a point instead of measuring a throwaway range.
The only internal ranges left to release are the four extraction
subranges, which the spec defines as live, so the live-range counter
and its test go.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The HTML Standard's IDL now annotates reflection, so
scripts/generate-html-tables.ts reads it through @webref/idl and
writes src/generated/htmltables.ts: each interface's reflected
attributes, each tag's interface from @webref/elements, the ARIA mixin,
and the event handler mixins. htmltables.ts keeps what the IDL leaves
to prose: enumerated attributes, attributes with their own setter, and
the supported token lists, and composes the tables dom.ts installs.

The generated reflections add the ones the hand table lacked and
correct three: lowsrc is a URL, headers is a string, and the marquee
scroll attributes are unsigned longs with defaults.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The property index says which longhands inherit and which take a
color, and the grammar index names the colors, line styles, easing
keywords and generic families. scripts/generate-css-properties.ts now
emits those, with the named colors' values from Bun.color, and
cssvalues builds its sets from them. Inheritance now follows the
index for every longhand instead of a hand list of 28, with the text
decorations kept as this engine's own addition.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The values are the CSS Color 4 table. color-name is that table as a
package, so the generator reads it there and runs under Node again.
The generated values are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
No index carries the CSS Color 4 values, so the generator holds the
table itself and checks it against the grammar's names on every run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
IntersectionObserver's rootMargin takes one to four lengths in px or a
percentage. The old parser silently read anything else as 0 and the
shared edge parser read it as a length, so the constructor now throws
the SyntaxError the spec asks for. selectors.ts reads XML_NAMESPACE
from htmltables instead of spelling the URI again.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
LayoutNode's setters were Yoga's C API carried over. A restyle now
builds a fresh Style with createStyle, fills it in, assigns it and
invalidates once, so setStyles and the styling flag that batched the
setters go, and a property the element stopped declaring falls back to
its initial value instead of the last restyle's. The two DOM-side ties,
measureContent and staticPositionFunc, are accessors. freeRecursive,
a destructor ported into a garbage-collected language, goes too: every
caller already unlinks the node and drops it from the maps.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
brainkim and others added 11 commits September 9, 2026 14:22
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
reflection.ts holds what the IDL leaves to prose and composes the
tables dom.ts installs; the namespace URIs and the forwarded body
handlers, which are not reflection, move into dom.ts. The generated
file is htmlidl.ts, named for its source. The solver lives at
layout/solver.ts beside the DOM mapping that drives it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
The node carried the pass that last styled it, the measurement key, and
the paint extents the painter culls rows by, none of which the solver
reads. layout.ts keeps them beside the node instead, and computes the
extents after each solve. The flag that tells an algorithm to place its
children rather than only size them is named placing, which is what the
test already called it, and no longer shares a name with the node's
performLayout method.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
dom.ts keeps dom/reflection.ts and dom/inspector.ts; cssom.ts keeps
cssom/cssvalues.ts, cssom/selectors.ts and cssom/useragent.ts; layout.ts
already kept layout/solver.ts. The peers, exchange, input, screen,
painter and text, stay at the top.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
cssselectors.ts, htmlreflection.ts and layoutsolver.ts say what they
select, reflect and solve without a directory to say it for them, as
cssvalues.ts already did. inspector.ts and useragent.ts return to the
top unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Bw3yEu4cfkuDkaZ3RSy4D
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