[lexical-code-prism][lexical-code-shiki][lexical-playground] Feature: Add CodePrismExtension, CodeShikiExtension, and migrate playground plugins to extensions#8346
Merged
etrepum merged 11 commits intofacebook:mainfrom Apr 12, 2026
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…tension Converts the legacy React PageBreakPlugin into a declarative PageBreakExtension using defineExtension, registering the node and INSERT_PAGE_BREAK command through the extension system.
Converts the legacy React TwitterPlugin into a declarative TwitterExtension using defineExtension, registering the TweetNode and INSERT_TWEET_COMMAND through the extension system.
Converts the legacy React YouTubePlugin into a declarative YouTubeExtension using defineExtension, registering the YouTubeNode and INSERT_YOUTUBE_COMMAND through the extension system.
Converts the legacy React FigmaPlugin into a declarative FigmaExtension using defineExtension, registering the FigmaNode and INSERT_FIGMA_COMMAND through the extension system.
…nsion Converts the legacy React TabFocusPlugin into a declarative TabFocusExtension using defineExtension, registering the FOCUS_COMMAND handler through the extension system.
…leExtension Converts the legacy React CollapsiblePlugin into a declarative CollapsibleExtension using defineExtension, registering the collapsible nodes, INSERT_COLLAPSIBLE_COMMAND, node transforms, and keyboard navigation handlers through the extension system.
…xtExtension Converts the legacy React SpecialTextPlugin into a declarative SpecialTextExtension using defineExtension with a disabled signal, registering the SpecialTextNode and its text node transform through the extension system. The transform is dynamically (un)registered based on the shouldAllowHighlightingWithBrackets setting.
…ighlightExtension Converts the legacy React CodeHighlightPrismPlugin and CodeHighlightShikiPlugin into a unified declarative CodeHighlightExtension using defineExtension with a mode signal. The active highlighter is selected dynamically via the isCodeHighlighted/isCodeShiki settings and (un)registered through the extension system.
This playground wrapper around @lexical/react/LexicalLinkPlugin was not referenced anywhere; link support is already registered declaratively via LinkExtension in App.tsx.
The playground StickyPlugin file was only a registration-guard useEffect for StickyNode and was not referenced anywhere. StickyNode itself is still registered via PlaygroundNodes and the collaboration logic lives in StickyComponent.tsx. Update the collaboration docs link to point at StickyComponent so readers land on the real example.
1c69e11 to
d4e5f06
Compare
… Add CodePrismExtension and CodeShikiExtension with disabled/tokenizer signals
Adds declarative extension exports for both code highlighters with a
runtime-configurable disabled signal and a tokenizer signal via
namedSignals, following the same pattern as MaxLengthExtension and
SelectionAlwaysOnDisplayExtension.
- @lexical/code-prism now exports CodePrismExtension + CodePrismConfig
({disabled, tokenizer}). The extension depends on CodeExtension and
uses an effect over namedSignals to (un)register the Prism highlighter
when the disabled or tokenizer signals change.
- @lexical/code-shiki now exports CodeShikiExtension + CodeShikiConfig
with the same shape and behaviour.
- The previous CodeHighlighterShikiExtension / CodeHighlighterShikiConfig
are kept as @deprecated backward-compatibility shims. The deprecated
extension declares CodeShikiExtension as a dependency and, in its init
method, forwards the flat Tokenizer config to
CodeShikiExtension.config.tokenizer in-place before CodeShikiExtension
builds, so existing configExtension(CodeHighlighterShikiExtension,
customTokenizer) usage continues to work unchanged.
- The lexical-playground CodeHighlightExtension depends on both
CodePrismExtension and CodeShikiExtension (initially disabled) and
flips their disabled signals based on a mode signal ('off' | 'prism' |
'shiki'), selecting the active engine declaratively rather than
importing registerCodeHighlighting from each package.
Flow types and the included-extensions doc are updated for the new
exports; the deprecated shim is also declared in the Shiki flow types.
d4e5f06 to
5031163
Compare
ivailop7
approved these changes
Apr 11, 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
Continues the migration of
lexical-playgroundplugins from legacy React<Plugin />components to declarative extensions built withdefineExtension. Each plugin is migrated in its own commit so it canbe reviewed independently, and every commit passes
pnpm run ci-check.Playground plugin migrations
Eight plugins are migrated. Node-and-command plugins become
straightforward extensions with
nodes+register. Plugins whoseregistration was previously toggled by conditional React rendering use
namedSignals+effect(following the existingMaxLengthExtensionpattern), synced from settings via
useSyncExtensionSignalinEditor.tsx.Trivial (command + node registration):
PageBreakPlugin→PageBreakExtensionTwitterPlugin→TwitterExtensionYouTubePlugin→YouTubeExtensionFigmaPlugin→FigmaExtensionTabFocusPlugin→TabFocusExtension(FOCUS_COMMANDhandler)CollapsiblePlugin→CollapsibleExtension(nodes, transforms,arrow-key/enter handlers, and
INSERT_COLLAPSIBLE_COMMAND; folderrenamed so the sibling
CollapsibleContainerNode/CollapsibleContentNode/CollapsibleTitleNodefiles move with it)Signal-toggled (conditional on a setting):
SpecialTextPlugin→SpecialTextExtensionwith adisabledsignaldriven by
shouldAllowHighlightingWithBracketsCodeHighlightPrismPlugin+CodeHighlightShikiPlugin→ unifiedplayground
CodeHighlightExtensionwith amode: 'off' | 'prism' | 'shiki'signal driven byisCodeHighlighted/
isCodeShiki, replacing the previous React-level ternary thatswapped between two plugin components
Imports from
ToolbarPlugin,ComponentPickerPlugin,AutoEmbedPlugin,and
PlaygroundNodesare updated to reference the new extension paths.Editor.tsxdrops the matching<*Plugin />mounts andApp.tsxregisters the new extensions in the appropriate scope —
PlaygroundRichTextExtensionfor rich-text-only features andAppExtensionfor features that previously ran in both modes.Plugins that render portals / floating panels / modals / dialogs
(Toolbar, FloatingLink, FloatingTextFormat, CodeActionMenu,
DraggableBlock, Comment, EmojiPicker, Mentions, ComponentPicker,
ContextMenu, AutoEmbed, Equations, Excalidraw, Poll, Table* plugins,
Actions, Shortcuts, VersionsPlugin, TreeView, TestRecorder, etc.) are
intentionally out of scope here — they still need a React host.
Dead code cleanup
Two playground plugin files were unreferenced anywhere in the codebase
and are removed:
LinkPlugin— an orphan wrapper around@lexical/react/LexicalLinkPlugin;link support is already registered declaratively via
LinkExtensionin
App.tsx.StickyPlugin— a registration-guarduseEffectforStickyNodethatwas never mounted.
StickyNodeis still registered viaPlaygroundNodes,and the collaboration logic lives in
StickyComponent.tsx. Thecollaboration docs link in
packages/lexical-website/docs/collaboration/react.mdis updated to point at
StickyComponent.tsx, which is where thenested-editor + real-time sync example actually lives.
New code highlighter extensions
The playground aggregator needs to route the same editor between two
different highlighter implementations, which motivated exposing
extensions for each engine:
@lexical/code-prismnow exportsCodePrismExtension+CodePrismConfig({disabled, tokenizer}). The extension declaresCodeExtensionas a dependency and usesbuild: namedSignals(config)plus an
effectover the signals to (un)register the Prismhighlighter reactively. Both
disabledandtokenizerare signals,so the highlighter can be toggled on/off and the tokenizer can be
hot-swapped at runtime via
useSyncExtensionSignal.@lexical/code-shikinow exportsCodeShikiExtension+CodeShikiConfigwith the same shape and behaviour.Deprecated shim (backward compatible). The existing
CodeHighlighterShikiExtensionandCodeHighlighterShikiConfigarekept and marked
@deprecated, pointing atCodeShikiExtension. Thedeprecated export is now a thin backward-compat shim that declares
CodeShikiExtensionas a dependency and, in itsinitmethod, writesits flat
Tokenizerconfig tostate.getDependency(CodeShikiExtension).config.tokenizerbeforeCodeShikiExtensionbuilds. This means existing code likeconfigExtension(CodeHighlighterShikiExtension, customTokenizer)continues to work without modification, and the resulting signal is
initialized with the forwarded tokenizer without a wasteful register /
re-register. The shim is registered under the runtime name
@lexical/code-shiki/legacyso it does not collide with the newextension's
@lexical/code-shikiname.Flow types (
packages/lexical-code-prism/flow/LexicalCodePrism.js.flow,packages/lexical-code-shiki/flow/LexicalCodeShiki.js.flow) are updatedfor the new exports and declare the deprecated shim; the
included-extensions.mddoc now lists bothCodePrismExtensionandCodeShikiExtension.Playground
CodeHighlightExtensionNow depends on
configExtension(CodePrismExtension, {disabled: true})and
configExtension(CodeShikiExtension, {disabled: true}), owns amodesignal, and uses aneffectthat flips each sub-extension'sdisabledsignal viagetExtensionDependencyFromEditor(editor, …).output.No more direct
registerCodeHighlightingcalls from the playground.Closes #
Test plan
No behavior change expected. Settings that previously toggled plugin
mounting (
isCodeHighlighted,isCodeShiki,shouldAllowHighlightingWithBrackets) should continue to behave thesame way, driven by signals instead of conditional rendering. Existing
CodeHighlighterShikiExtensionimports andconfigExtensioncallsshould continue to work unchanged via the deprecated shim.