Repository navigation
Add the RenderAdapter interface for every output operation of the VM - #21711
Draft
NullVoxPopuli-ai-agent wants to merge 4 commits into
Draft
NullVoxPopuli-ai-agent wants to merge 4 commits into
NullVoxPopuli-ai-agent wants to merge 4 commits into
Conversation
| const node = (value: SimpleNode) => value as unknown as TreeNode; | ||
| const simple = <T extends SimpleNode>(value: TreeNode | null) => value as unknown as T; | ||
|
|
||
| class TreeRenderer implements DOMRenderer { |
The VM called the DOM in two ways. Some calls went through `DOMTreeConstruction` and `DOMChanges`. About 100 other calls read or wrote a member of a node directly, for example `node.nextSibling` and `element.setAttribute()`. `DOMRenderer` is one interface with every operation that the VM does on the output tree. `DOMRendererImpl` is its implementation for a browser document and for a SimpleDOM document. All code in `@glimmer/runtime` and `@glimmer/node` now calls the interface, and nodes are only handles there. A different output, for example a terminal, can implement the same interface on its own node objects. The new test does that with plain objects that have no DOM member. Changes to private API: - `Environment#dom` replaces `getDOM()` and `getAppendOperations()`. - `EnvironmentOptions#dom` replaces `appendOperations` and `updateOperations`. - `DOMRendererImpl` replaces `DOMTreeConstruction` and `DOMChanges`. - `NodeDOMRenderer` replaces `NodeDOMTreeConstruction`. - `InternalModifierManager#create` gets the environment as argument 5. The `on` modifier needs it for the listeners. - `normalizeProperty`, `dynamicAttribute`, `clear`, and `move` take the renderer as argument 1. - `sanitizeAttributeValue` takes the tag name, not the element. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`renderComponent` takes `env.dom`, and `BaseRenderer.strict` takes a `DOMRenderer` in place of a document. This gives a renderer that is not the DOM an Ember entry point. The proof test now renders with Ember's `renderComponent` and the run loop, not with the Glimmer render functions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NullVoxPopuli-ai-agent
force-pushed
the
nvp/dom-renderer-interface
branch
from
October 27, 2026 20:47
8ee6b7c to
ed18c7f
Compare
This was referenced Oct 27, 2026
`EnvironmentImpl`, `runtimeOptions`, `BaseRenderer`, and the classic `Renderer` take a `DOMRenderer`, not a document or an options object. `EnvironmentOptions` is gone. The private `env.document` option of `renderComponent` stays. It is turned into a `DOMRenderer` before the renderer is made. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A renderer (`BaseRenderer`, the classic `Renderer`) owns the roots and the render loop. The object that does the output operations is a render adapter, so the names say that: - `DOMRenderer` (interface) → `RenderAdapter` - `DOMRendererImpl` → `DOMRenderAdapter` - `NodeDOMRenderer` → `NodeDOMRenderAdapter` - `env.dom` → `env.adapter`, and the private `renderComponent` option `env.dom` → `env.adapter` Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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.
RenderAdapteris a new interface that has every operation the VM does on the output tree. All code in@glimmer/runtimeand@glimmer/nodenow calls it, and does not read or write a member of a node.Start with
packages/@glimmer/interfaces/lib/render-adapter.d.ts. It is the full interface in about 140 lines.Names
A renderer and a render adapter are two different things:
BaseRenderer, the classicRenderer, and the one thatrenderComponentmakes.RenderAdapteris the interface.DOMRenderAdapterimplements it for the DOM. A terminal output would be a different adapter, with the same renderers.Why
A different output, for example a terminal, needs one interface to implement. Before this PR, the VM called the DOM in two ways:
DOMTreeConstructionandDOMChanges.node.nextSibling,element.setAttribute(), andelement[name] = value.What the interface has
createElement,createElementNS,createTextNode,createCommentinsertBefore,insertHTMLBefore,removeChild,clearChildrenparentNode,firstChild,lastChild,nextSibling,previousSibling,querySelectorisNode,isFragment,isElement,isText,isComment,tagName,namespaceURI,nodeValue,setNodeValueattributeNames,getAttribute,setAttribute,removeAttribute,hasProperty,getProperty,setPropertyaddEventListener,removeEventListenerThe interface does not have
getProp,toBool,toIterator, lookups, or managers. Those are the same for each output.What uses it
DOMRenderAdapterin@glimmer/runtime/lib/dom/render-adapter.tsis the adapter for a browser document and for a SimpleDOM document. It replacesDOMOperations,DOMTreeConstruction, andDOMChanges.NodeDOMRenderAdapterin@glimmer/nodeextends it.Environment#adapterholds the adapter.EnvironmentImplandruntimeOptionstake aRenderAdapteras argument 1.onmodifier all callenv.adapter.BaseRenderer,BaseRenderer.strict, and the classicRenderertake aRenderAdapter.renderComponentacceptsenv.adapter. This is the Ember entry point for a different adapter.Proof that the VM uses only the interface
packages/@glimmer-workspace/integration-tests/test/render-adapter-test.tshas aRenderAdapteron plain objects with no DOM member. A direct DOM access by the VM fails on these objects. The test calls Ember'srenderComponentwithenv: { adapter }, and the run loop does the updates. It renders and updates:{{#if}}and{{#each}}, with add, move, and removeonmodifierChanges to private API
Environment#adapterreplacesgetDOM()andgetAppendOperations().EnvironmentImplandruntimeOptionstake aRenderAdapterin place of theEnvironmentOptionsobject.EnvironmentOptions(document,appendOperations,updateOperations) is removed.DOMRenderAdapterreplacesDOMTreeConstructionandDOMChanges.NodeDOMRenderAdapterreplacesNodeDOMTreeConstruction. The exports of@ember/-internals/glimmerhave the new names.InternalModifierManager#creategets the environment as argument 5. Theonmodifier needs it.normalizeProperty,dynamicAttribute,clear, andmovetake the adapter as argument 1.sanitizeAttributeValuetakes the tag name, not the element.BaseRenderer,BaseRenderer.strict, and the classicRenderertake aRenderAdapterin place of a document. Only the privateenv.documentoption ofrenderComponentstill takes a document, and it becomes aDOMRenderAdapterbefore the renderer is made.Not in this PR
Renderer(applications,{{outlet}}) makes its adapter from the-documentservice. Remove theservice:-documentregistration #21772 removes that service.SimpleNodeandSimpleElement. A different adapter must cast its nodes.Component(appendTo,readDOMAttr,this.element) and the event dispatcher call the DOM directly.isSerializationFirstNodeand the debug checks in@glimmer/debugreadnodeTypeandnodeValuedirectly.Checks
pnpm type-checkandpnpm lint:eslintandpnpm lint:formatpass.pnpm testpasses: 9221 tests, 0 failures, 21 skipped.🤖 Generated with Claude Code