v0.51.0 is a monthly release with one large new feature: nodes can now declare their JSON serialization as a schema on $config. This declarative schema is faster, safer, and more flexible than the hand-written methods. The metadata it provides enables a compact export option with all default values elided, the code generator that provides faster import and export for every node that lexical ships, a new @lexical/fast-check package for property-based tests, and it provides a path to future features that rely on knowing the structure of the nodes (e.g. additional codecs and dev tools).
This is a big step towards Lexical 1.0, the next few releases are expected to be focused on moving everything (including docs and examples) to the latest Extension and $config APIs and removing deprecated functionality. Two related changes in this release are that we are publishing ESM-only to npm now (cjs builds are still supported in the source tree), and LexicalComposer is deprecated for LexicalExtensionComposer. Expect anything @deprecated to be removed soon.
Breaking Changes
- npm packages are ESM only. Every package declares
"type": "module"and itsexportsmap offersdevelopment/production/defaultconditions with norequirebranch; the CommonJS build remains in-tree only for Meta's www. Bundlers are unaffected. CommonJS code canrequire()the packages on Node.js 20.19+ (the builds have no top-levelawait), and older Node.js must useawait import(...)— but a dependency the app also loads throughrequire()can then exist twice, which breaks handing its objects to Lexical (yjsdocs to@lexical/yjs,@preact/signals-coresignals from@lexical/extension), so load those as ESM too. Metro bundles both builds unless you add the condition for Lexical's packages; see the module formats FAQ (#9127) lexical—exportJSONmay serialize the instance as-is. A property declared withwithFieldis read straight off the node instead of through its accessor, soexportJSONno longer resolvesgetLatest()for you and a stale reference can write stale values (and a property whose accessor a subclass overrode still resolves, so one object can mix the two). Nothing inside Lexical is affected — the walk, the clipboard selection export andeditorState.toJSON()all start from the node map — but callgetLatest()yourself on a reference you kept across a mutation (#8602)@lexical/code— the deprecated Prism re-exports are gone, along with the@lexical/code-prismdependency (so Prism no longer lands in a bundle that only imports@lexical/code). ImportCODE_LANGUAGE_FRIENDLY_NAME_MAP,CODE_LANGUAGE_MAP,getCodeLanguageOptions,getCodeLanguages,getCodeThemeOptions,getLanguageFriendlyName,normalizeCodeLanguage(formerly alsonormalizeCodeLang),PrismTokenizerandregisterCodeHighlightingfrom@lexical/code-prism(#9141)
Deprecations
@lexical/react—LexicalComposeris deprecated in favor ofLexicalExtensionComposerand is expected to be removed in a future major.LexicalComposercannot accept extensions, so any feature shipped as one is unreachable from an editor it builds; the migration is usually a two-line change (#9168, React extensions guide)
New APIs & Features
lexical— Declarative serialization schemas (experimental). A node that uses$configdeclares its serialized properties once in the newjsonproperty, and that single declaration drivesimportJSON,updateFromJSONandexportJSON:nodeSchema,withField/withAccessors, and the value combinators (stringValue,numberValue,booleanValue,enumValue,arrayValue,objectValue,unionValue,rawValue,transformValue,nullable,optional, …). Every built-in node declares one, and most custom nodes now need no JSON serialization code at all. Output is unchanged by default (#8602, serialization docs).lexical— Compact JSON export, opt-in viaeditorState.toJSON(true)or$withCompactExport(true, cb)(with$isCompactExport()for schema getters, plus$exportNodeJSONand theCompactSerializedEditorState/ParsableSerializedEditorState/LexicalParseJSON/SerializedPartialtypes). It omits properties equal to their schema default, properties the parser derives, and the deprecatedversion. The raw objects are well under half the legacy size — which matters for structured clones and worker messages — but after gzip the two are a wash. Both forms parse back to the same document; only a Lexical new enough to know the schemas can read the compact one, so keep writing the legacy form until every reader is upgraded (#8602)@lexical/compiler— Generated serialization code. Every built-in node class ships straight-line serialization generated from its own schema at build time, producing byte-identical JSON to the schema-driven path that continues to serve custom nodes (#8602)@lexical/fast-check— New package deriving fast-check arbitraries from those schemas:nodeArbitrary(NodeClass)generates exactly the serialized JSON a node's parser accepts, each property independently present or absent. Useful for the bugs fixtures hide, such as a propertyafterCloneFromforgot to copy.fast-checkis a peer dependency;getComposedSchemaFieldsin core inspects a class's schema without generating anything (#8602)@lexical/extension— Published as subpath exports, one entry per extension (@lexical/extension/AutoFocusExtension, …), with compiler and lint enforcement of the source import convention. The barrel import is unchanged, and shared modules such as signals keep a single instance (#9161). This is primarily only useful as a consumer if you are not using tree-shaking.
Notable Fixes
Bundling, tree-shaking & types
- A production import of only
createCommandno longer retains the editor (#9121), and the remaining module-scope side effects that defeated tree-shaking are gone, with a bare-import residue check now run over every entry (#9124); extension editors build under loose-mode transpilation (#9134) - Flow:
defineExtensionwas uncallable, making the extension types unusable (#9132); declarations can now express an extension with dependencies (#9176); modern variance keywords survive into the www stubs (#9154)
Selection & editing
- No redundant selection-change command on Safari link selection (#9163);
TextNode.setMode,setTextContentandisSimpleTextread the latest state (#9162);$removeTextFromCaretRangeno longer leaves a childless list or table as the root's only child (#9116); a text entity keeps the style and detail of the text it replaces (#9130); selection overlay listeners are notified when rectangles are removed (#9117)
Lists, tables & Markdown
- Indenting between two nested lists no longer converts the second list's type (#9118);
$mergeCellsno longer keeps the empty paragraph of the target cell (#9122); text left outside a Markdown link keeps its format (#9131)
Playground & docs
- Card/Review CSS placeholders are cleared reliably on paste in Safari (#9129) and releasing a CardNode drag no longer selects the node (#9135); new docs on Lexical being a singleton per app, and why a library must declare it as a peer dependency (#9125)
Plus the usual dependency updates across the rollup, shikijs, vitest, docusaurus/typedoc and flow/hermes groups.
What's Changed
- v0.50.0 by @etrepum in #9110
- [lexical][lexical-compiler] Bug Fix: Let esbuild drop the editor from a createCommand-only production import by @zurfyx in #9121
- [*] Bug Fix: Remove the remaining module-scope side effects that defeat tree-shaking by @etrepum in #9124
- [lexical-playground] Bug Fix: clear Card/Review CSS placeholders reliably on paste in Safari by @sahiee-dev in #9129
- [lexical-website] Documentation Update: document that Lexical is a singleton per app and libraries must declare it as a peer dependency by @potatowagon in #9125
- [lexical-markdown] Bug Fix: text left outside a markdown link keeps its format by @Om-singhaI in #9131
- [lexical] Fix: make the extension Flow types usable (defineExtension was uncallable) by @potatowagon in #9132
- build(deps-dev): bump esbuild from 0.27.7 to 0.28.1 by @dependabot[bot] in #9123
- [lexical-table] Bug Fix: $mergeCells no longer keeps the empty paragraph of the target cell by @Om-singhaI in #9122
- [lexical-utils] Bug Fix: notify when selection overlay rectangles are removed by @mehuljariwala in #9117
- [lexical] Bug Fix: $removeTextFromCaretRange no longer leaves a childless list or table as the root's only child by @Om-singhaI in #9116
- [lexical-playground] Bug Fix: avoid node selection on CardNode drag release by @kavyansh18 in #9135
- [lexical-extension] Fix: use Array.from over iterable spread so extension editors build under loose-mode transpilation by @potatowagon in #9134
- [lexical-text] Bug Fix: a text entity keeps the style and detail of the text it replaces by @Om-singhaI in #9130
- [*] Breaking change: Publish ESM only to npm and keep the CommonJS build for www by @etrepum in #9127
- [lexical-code] Breaking change: drop the deprecated prism re-exports and the @lexical/code-prism dependency by @etrepum in #9141
- build(deps-dev): bump vitest from 4.1.8 to 4.1.11 by @dependabot[bot] in #9140
- build(deps): bump the docusaurus-and-typedoc group with 13 updates by @dependabot[bot] in #9142
- build(deps-dev): bump the flow-and-hermes group with 4 updates by @dependabot[bot] in #9143
- build(deps-dev): bump baseline-browser-mapping from 2.10.33 to 2.11.0 by @dependabot[bot] in #9139
- build(deps): bump the shikijs group with 6 updates by @dependabot[bot] in #9145
- build(deps): bump the rollup group across 1 directory with 5 updates by @dependabot[bot] in #9144
- [lexical-list] Bug Fix: indenting between two nested lists no longer converts the second list's type by @Om-singhaI in #9118
- [*] Bug Fix: Keep modern Flow variance keywords in the www Flow stubs by @potatowagon in #9154
- build(deps-dev): bump the dev-dependencies group across 1 directory with 26 updates by @dependabot[bot] in #9150
- build(deps-dev): bump flow-bin from 0.330.0 to 0.331.0 in the flow-and-hermes group by @dependabot[bot] in #9156
- [lexical-extension][*] Refactor: publish @lexical/extension as subpath exports with compiler+lint enforcement by @etrepum in #9161
- [lexical] Bug Fix: Avoid redundant selection change command by @levensta in #9163
- [lexical] Bug Fix: TextNode setMode, setTextContent and isSimpleText read the latest state by @Dev-next-gen in #9162
- [lexical-react] Chore: Deprecate LexicalComposer in favor of LexicalExtensionComposer by @potatowagon in #9168
- build(deps-dev): bump eslint from 10.9.1 to 10.10.0 in the dev-dependencies group across 1 directory by @dependabot[bot] in #9158
- build(deps): bump date-fns from 4.1.0 to 4.4.0 by @dependabot[bot] in #9159
- [lexical][*] Feature: node JSON serialization schemas, with compact export by @etrepum in #8602
- build(deps): bump @babel/parser from 8.0.0 to 8.0.4 by @dependabot[bot] in #9160
- build(deps): bump immutable from 4.3.8 to 4.3.9 by @dependabot[bot] in #9175
- build(deps-dev): bump the vitest group across 1 directory with 2 updates by @dependabot[bot] in #9157
- [lexical] Fix: Flow declarations cannot express an extension with dependencies by @potatowagon in #9176
New Contributors
- @mehuljariwala made their first contribution in #9117
- @kavyansh18 made their first contribution in #9135
- @Dev-next-gen made their first contribution in #9162
Full Changelog: v0.50.0...v0.51.0