Repository navigation
chore: split uneval into an internal analyze/render emitter - #223
Open
elliott-with-the-longest-name-on-github wants to merge 3 commits into
Open
elliott-with-the-longest-name-on-github wants to merge 3 commits into
elliott-with-the-longest-name-on-github wants to merge 3 commits into
Conversation
|
elliott-with-the-longest-name-on-github
added this pull request to stack #224
October 8, 2026 21:46
uneval into an internal analyze/render emitter
elliott-with-the-longest-name-on-github
marked this pull request as ready for review
October 9, 2026 16:38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Splits
uneval's internals into two steps, sounevalStream(#218) can reuse the emitter without growingunevala pile of special cases. The split also gives the stream a clean point between walk and render: once the walk has run, it knows whether the value contained any promises, and a head without them is justanalysis.render(), identical touneval. Nothing public changes and the output is byte-for-byte the same.uneval(value, replacer)is now justanalyze(value, { replace }).render(). Basically, the walk stays exactly where it was, and everything afterwalk(value)moves intorender. Render reuses the walk's state (seen,names), so calling it twice throws instead of quietly producing garbage.knownreturns a token (the stream uses its ownLocation) andrenderpasses that same token toreference, so there's no second lookup. The rule for which primitives are worth a slot (strings ≥128 chars, bigints with ≥128-char literals) now lives in the emitter, soprimitiveonly gets called for those.Hooks are only checked where they were before. A plain
unevalcall costs one extra object (theAnalysis), and the no-hook path reuses a shared frozen{}instead of allocating hook objects. Typical cases inpnpm bench:comparefall inside run-to-run noise. On the micro end (small object, 100k calls of a 3-key object) it's ~3% slower, about the same as the hooks version this replaces. That's the per-call closure setup, not per-node work.There's also a separate commit fixing the benchmark imports, which have pointed at
../../index.jssince #209 moved it tosrc/. Without it,bench:comparedoesn't run onmainat all.test/uneval-hooks.test.jscovers each hook. It also checks thatanalyze(v).render()matchesuneval(v)across a spread of fixtures, that a secondrenderthrows, that tokens make the round trip, and that short primitives never reachprimitive.