Skip to content

[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
etrepum:claude/migrate-plugins-to-extensions-THIw3
Apr 12, 2026
Merged

Conversation

@etrepum
Copy link
Copy Markdown
Collaborator

@etrepum etrepum commented Apr 11, 2026

Description

Continues the migration of lexical-playground plugins from legacy React
<Plugin /> components to declarative extensions built with
defineExtension. Each plugin is migrated in its own commit so it can
be 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 whose
registration was previously toggled by conditional React rendering use
namedSignals + effect (following the existing MaxLengthExtension
pattern), synced from settings via useSyncExtensionSignal in
Editor.tsx.

Trivial (command + node registration):

  • PageBreakPluginPageBreakExtension
  • TwitterPluginTwitterExtension
  • YouTubePluginYouTubeExtension
  • FigmaPluginFigmaExtension
  • TabFocusPluginTabFocusExtension (FOCUS_COMMAND handler)
  • CollapsiblePluginCollapsibleExtension (nodes, transforms,
    arrow-key/enter handlers, and INSERT_COLLAPSIBLE_COMMAND; folder
    renamed so the sibling CollapsibleContainerNode /
    CollapsibleContentNode / CollapsibleTitleNode files move with it)

Signal-toggled (conditional on a setting):

  • SpecialTextPluginSpecialTextExtension with a disabled signal
    driven by shouldAllowHighlightingWithBrackets
  • CodeHighlightPrismPlugin + CodeHighlightShikiPlugin → unified
    playground CodeHighlightExtension with a
    mode: 'off' | 'prism' | 'shiki' signal driven by isCodeHighlighted
    / isCodeShiki, replacing the previous React-level ternary that
    swapped between two plugin components

Imports from ToolbarPlugin, ComponentPickerPlugin, AutoEmbedPlugin,
and PlaygroundNodes are updated to reference the new extension paths.
Editor.tsx drops the matching <*Plugin /> mounts and App.tsx
registers the new extensions in the appropriate scope —
PlaygroundRichTextExtension for rich-text-only features and
AppExtension for 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 LinkExtension
    in App.tsx.
  • StickyPlugin — a registration-guard useEffect for StickyNode that
    was never mounted. StickyNode is still registered via PlaygroundNodes,
    and the collaboration logic lives in StickyComponent.tsx. The
    collaboration docs link in packages/lexical-website/docs/collaboration/react.md
    is updated to point at StickyComponent.tsx, which is where the
    nested-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-prism now exports CodePrismExtension +
    CodePrismConfig ({disabled, tokenizer}). The extension declares
    CodeExtension as a dependency and uses build: namedSignals(config)
    plus an effect over the signals to (un)register the Prism
    highlighter reactively. Both disabled and tokenizer are signals,
    so the highlighter can be toggled on/off and the tokenizer can be
    hot-swapped at runtime via useSyncExtensionSignal.
  • @lexical/code-shiki now exports CodeShikiExtension +
    CodeShikiConfig with the same shape and behaviour.

Deprecated shim (backward compatible). The existing
CodeHighlighterShikiExtension and CodeHighlighterShikiConfig are
kept and marked @deprecated, pointing at CodeShikiExtension. The
deprecated export is now a thin backward-compat shim that declares
CodeShikiExtension as a dependency and, in its init method, writes
its flat Tokenizer config to
state.getDependency(CodeShikiExtension).config.tokenizer before
CodeShikiExtension builds. This means existing code like
configExtension(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/legacy so it does not collide with the new
extension's @lexical/code-shiki name.

Flow types (packages/lexical-code-prism/flow/LexicalCodePrism.js.flow,
packages/lexical-code-shiki/flow/LexicalCodeShiki.js.flow) are updated
for the new exports and declare the deprecated shim; the
included-extensions.md doc now lists both CodePrismExtension and
CodeShikiExtension.

Playground CodeHighlightExtension

Now depends on configExtension(CodePrismExtension, {disabled: true})
and configExtension(CodeShikiExtension, {disabled: true}), owns a
mode signal, and uses an effect that flips each sub-extension's
disabled signal via getExtensionDependencyFromEditor(editor, …).output.
No more direct registerCodeHighlighting calls from the playground.

Closes #

Test plan

No behavior change expected. Settings that previously toggled plugin
mounting (isCodeHighlighted, isCodeShiki,
shouldAllowHighlightingWithBrackets) should continue to behave the
same way, driven by signals instead of conditional rendering. Existing
CodeHighlighterShikiExtension imports and configExtension calls
should continue to work unchanged via the deprecated shim.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment Apr 11, 2026 7:25pm
lexical-playground Ready Ready Preview, Comment Apr 11, 2026 7:25pm

Request Review

claude added 10 commits April 11, 2026 19:00
…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.
… 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.
@etrepum etrepum force-pushed the claude/migrate-plugins-to-extensions-THIw3 branch from d4e5f06 to 5031163 Compare April 11, 2026 19:23
@etrepum etrepum changed the title [lexical-playground] Refactor: Migrate legacy React plugins to extensions [lexical-playground] Refactor: Migrate more legacy React plugins to extensions Apr 11, 2026
@etrepum etrepum changed the title [lexical-playground] Refactor: Migrate more legacy React plugins to extensions [lexical-code-prism][lexical-code-shiki][lexical-playground] Feature: Add CodePrismExtension, CodeShikiExtension, and migrate playground plugins to extensions Apr 11, 2026
@etrepum etrepum marked this pull request as ready for review April 11, 2026 19:36
@etrepum etrepum added this pull request to the merge queue Apr 12, 2026
Merged via the queue into facebook:main with commit 8760c76 Apr 12, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants