[lexical][lexical-html] Feature: Extensible DOM create/update/export#8353
Merged
etrepum merged 47 commits intofacebook:mainfrom Apr 18, 2026
Merged
[lexical][lexical-html] Feature: Extensible DOM create/update/export#8353etrepum merged 47 commits intofacebook:mainfrom
etrepum merged 47 commits intofacebook:mainfrom
Conversation
…via EditorDOMRenderConfig, DOMImportExtension, DOMRenderExtension Synced with main and consolidated extracted modules back into index.ts. Key changes: - Add EditorDOMRenderConfig interface and DEFAULT_EDITOR_DOM_CONFIG for pluggable DOM rendering - Add DOMImportExtension and DOMRenderExtension with context-based override system - Add EmitterState for stateful block/inline handling during DOM import - Simplify listeners from Map to Set (remove callback-return pattern) - Remove resetOnCopyNode mechanism - Extend DOMExportOutput with append and $getChildNodes hooks - Remove package-lock files (npm->pnpm migration) https://claude.ai/code/session_01AnZUp1X3Apkrqq9SDG8e3m
- Remove resetOnCopyNode from StateValueConfig interface - Remove resetOnCopyNode usage from MarkdownTransformers - Update LexicalNodeState tests to reflect removal of resetOnCopyNode https://claude.ai/code/session_01AnZUp1X3Apkrqq9SDG8e3m
These features were added to main after the feature branch diverged. The previous merge incorrectly treated them as removals. This commit restores them while keeping the feature branch additions (EditorDOMRenderConfig, branded types, $getEditorDOMRenderConfig, etc.) https://claude.ai/code/session_01AnZUp1X3Apkrqq9SDG8e3m
The size check optimization is incorrect because nodes can have equivalent state even when one has more explicit entries (with default values) than the other. https://claude.ai/code/session_01AnZUp1X3Apkrqq9SDG8e3m
Remove the incomplete DOMImport subsystem from @lexical/html while keeping the DOMRenderExtension which is closer to ready. This removes DOMImportExtension, DOMImportConfig, EmitterState, ImportContext, compileDOMImportOverrides, compileLegacyImportDOM, importOverride, and all associated types, constants, and tests. https://claude.ai/code/session_016Y5bfCbn4XZFUTjtAM1UP5
Sync outdated dependency versions with the rest of the repository: - shiki/shikijs: ^3.3.0 → ^4.0.2 (matching @lexical/code-shiki) - react/react-dom: ^19.1.0 → ^19.2.5 - @types/react: ^19.1.2 → ^19.2.14 - @types/react-dom: ^19.1.2 → ^19.1.9 - prettier: ^3.5.3 → ^3.8.1 - vite: ^7.1.4 → ^7.3.2 https://claude.ai/code/session_016Y5bfCbn4XZFUTjtAM1UP5
Align all shared dependencies with the root workspace package.json: - vite: ^7.1.4 → ^8.0.8 - @vitejs/plugin-react: ^5.0.2 → ^6.0.1 - typescript: ^5.9.2 → ^6.0.2 - cross-env: ^7.0.3 → ^10.1.0 - react/react-dom: ^19.1.0 → ^19.2.5 - @types/react: ^19.1.2 → ^19.2.14 - @types/react-dom: ^19.1.2 → ^19.2.3 - prettier: ^3.5.3 → ^3.8.1 - shiki/shikijs: ^3.3.0 → ^4.0.2 (matching @lexical/code-shiki) https://claude.ai/code/session_016Y5bfCbn4XZFUTjtAM1UP5
- @ark-ui/react: ^5.6.0 → ^5.36.0 - lucide-react: ^0.503.0 → ^1.8.0 - inline-style-parser: ^0.2.4 → ^0.2.7 - csstype: ^3.1.3 → ^3.2.3 https://claude.ai/code/session_016Y5bfCbn4XZFUTjtAM1UP5
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
lexical/flow/Lexical.js.flow: - Complete EditorDOMRenderConfig with $getDOMSlot, $extractWithChild, $shouldInclude, $shouldExclude - Add DEFAULT_EDITOR_DOM_CONFIG, $getEditorDOMRenderConfig, $isLexicalNode lexical-html/flow/LexicalHtml.js.flow: - Add all new DOMRender types (DOMRenderConfig, DOMRenderMatch, DOMRenderExtensionOutput, RenderStateConfig, NodeMatch, etc.) - Add contextUpdater, contextValue, domOverride, DOMRenderExtension - Add $getRenderContextValue, $withRenderContext, RenderContextExport, RenderContextRoot - Add $generateDOMFromNodes, $generateDOMFromRoot, getConversionFunction, $unwrapArtificialNodes, $wrapContinuousInlinesInPlace, isDomNodeBetweenTwoInlineNodes, IGNORE_TAGS - Remove stale FindCachedParentDOMNode types https://claude.ai/code/session_016Y5bfCbn4XZFUTjtAM1UP5
The root package.json had examples/dev-* in the npm "workspaces" field, which caused npm to treat dev examples as workspace members. This broke integration tests because npm install would encounter workspace:* references from sibling packages and fail with EUNSUPPORTEDPROTOCOL. The pnpm-workspace.yaml already explicitly excludes examples, so the npm workspaces entry was unnecessary and conflicting. https://claude.ai/code/session_016Y5bfCbn4XZFUTjtAM1UP5
Generated by build-release for new error messages added in this branch. https://claude.ai/code/session_016Y5bfCbn4XZFUTjtAM1UP5
- Remove extends of root tsconfig.json which caused tsc to type-check monorepo package sources and fail on process/global references - Change build script from vite.config.monorepo.ts to default vite.config (monorepo config is for local dev only, matching other examples) https://claude.ai/code/session_016Y5bfCbn4XZFUTjtAM1UP5
zurfyx
approved these changes
Apr 17, 2026
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.
Description
Addresses the
createDOM/updateDOM/exportDOMpart of #7259 by parameterizing the DOM functionality from the reconciler and@lexical/htmlto use the editor config viaDOMRenderExtension. Part of the thinking here is that we may be able to leverage this work later to make the reconciler itself more flexible.(Commit history is noisy because this has been collecting dust and had some failed experiments. I used claude to strip it down to what is actually worth adding soon)
Follow-up work (out of scope)
Does not yet consider the different types of export, e.g. export for clipboard vs. export for serialization.
DOMExportExtension currently only has a natural topological sort of overrides by extension, it should also do another sort to pull wildcard and predicate based matches to the highest precedence and then sort by specificity (topological sort of class inheritance) so that SubclassNode overrides have higher precedence than SuperclassNode overrides.
The same sort of approach can probably also be used for other tree based import/export (json, markdown ast, etc.).
$getDOMSlotcould possibly be used for nodes other than just ElementNode. The motivating use case there would be able to inject "widget decorators" (in prosemirror terms) that sit before or after specific nodes to facilitate UI that is "outside" of the document. The sorts of things we do now with popovers.DOMImportExtension - the importDOM code is in a much worse shape and is a lot trickier to augment, I've decided to tackle that separately since the export is in a pretty good state and would solve some current pain points.
EditorDOMRenderConfig
This data structure moves responsibility for all DOM rendering (create/update) and export to a single configuration in the editor. These
$createDOM,$exportDOM,$updateDOM, etc. properties are all functions which are eventually responsible for calling their respective node methods (at least by default).The trick here is that we can wrap these implementations with middleware style functions that can be composed to override or otherwise run code that happens "around" the default implementations. This is very difficult to do with the existing infrastructure, especially when writing code that needs to target all nodes, all nodes with some specific NodeState, etc. Since they are middleware, they have the ability to call the
$next()function to get the result of the next implementation (e.g. "calling super" and then enhancing its result) or not (to completely override).DOMRenderExtension
This provides a mechanism to compile the EditorDOMRenderConfig using composable configuration, targeting either all nodes with
'*'or some subset of nodes by an array ofNodeClassor$isNodeClassguards. This first pass is a relatively blunt approach but we can build more specific extensions to consolidate this wrapping in the future for optimization reasons.Examples:
Enhancing TextNode export to remove the 'white-space' style unless it's necessary to support the encoding of the content:
Adding an arbitrary id property to any node (e.g. for supporting deep linking)
New APIs
All new APIs are marked `@experimental` and are subject to change.
lexicalEditorDOMRenderConfig$createDOM,$getDOMSlot,$exportDOM,$extractWithChild,$updateDOM,$shouldInclude,$shouldExcludeDEFAULT_EDITOR_DOM_CONFIG$getEditorDOMRenderConfig(editor?)$isLexicalNode(node)LexicalNodeinstancesCreateEditorArgs.domPartial<EditorDOMRenderConfig>to override rendering at editor creation@lexical/htmlDOMRenderExtensionDOMRenderMatchoverrides into anEditorDOMRenderConfigdomOverride(nodes, config)$generateDOMFromNodes(container, selection?, editor?)EditorDOMRenderConfig$generateDOMFromRoot(container, root?)$getRenderContextValue(cfg, editor?)$withRenderContext(pairs, editor?)RenderContextRoottruewhen export was initiated from the document rootRenderContextExporttrueduring$generateHtmlFromNodescontextValue(cfg, value)contextUpdater(cfg, updater)DOMRenderConfigDOMRenderExtensionDOMRenderMatch<T>NodeMatch<T>AnyDOMRenderMatchDOMRenderMatchDOMRenderExtensionOutputDOMRenderExtensionRenderStateConfig<V>AnyRenderStateConfigRenderStateConfigAnyRenderStateConfigPairOrUpdaterRenderStateConfigContextPairOrUpdater<Ctx, V>Test plan
Currently it's primarily some minimal tests that show the legacy support works the same way that the legacy code does
Also ships a dev-node-state-style example that demonstrates one of the use cases (having NodeState apply styles to any node both in create and export).