Gathering detailed insights and metrics for @lexical/devtools-core
Gathering detailed insights and metrics for @lexical/devtools-core
Gathering detailed insights and metrics for @lexical/devtools-core
Gathering detailed insights and metrics for @lexical/devtools-core
lexical
Lexical is an extensible text editor framework that provides excellent reliability, accessible and performance.
@vue/devtools-core
> Internal core functions shared across @vue/devtools packages.
react-devtools-core
Use react-devtools outside of the browser
@etrepum/lexical-builder-devtools-core
[EXPERIMENTAL] Lexical Builder Devtools
npm install @lexical/devtools-core
87.8
Supply Chain
60.3
Quality
98.6
Maintenance
100
Vulnerability
100
License
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
19,996 Stars
4,066 Commits
1,705 Forks
121 Watching
306 Branches
489 Contributors
Updated on 27 Nov 2024
Minified
Minified + Gzipped
TypeScript (70.95%)
JavaScript (26.53%)
CSS (2.17%)
HTML (0.12%)
Swift (0.08%)
Shell (0.06%)
Svelte (0.04%)
Astro (0.03%)
MDX (0.01%)
Cumulative downloads
Total Downloads
Last day
-0.5%
59,456
Compared to previous day
Last week
4.2%
304,533
Compared to previous week
Last month
21.3%
1,243,204
Compared to previous month
Last year
0%
5,428,544
Compared to previous year
Lexical is an extensible JavaScript web text-editor framework with an emphasis on reliability, accessibility, and performance. Lexical aims to provide a best-in-class developer experience, so you can easily prototype and build features with confidence. Combined with a highly extensible architecture, Lexical allows developers to create unique text editing experiences that scale in size and functionality.
For documentation and more information about Lexical, be sure to visit the Lexical website.
Here are some examples of what you can do with Lexical:
Overview:
Note: Lexical is not only limited to React. Lexical can support any underlying DOM based library once bindings for that library have been created.
Install lexical
and @lexical/react
:
npm install --save lexical @lexical/react
Below is an example of a basic plain text editor using lexical
and @lexical/react
(try it yourself).
1import {$getRoot, $getSelection} from 'lexical'; 2import {useEffect} from 'react'; 3 4import {LexicalComposer} from '@lexical/react/LexicalComposer'; 5import {PlainTextPlugin} from '@lexical/react/LexicalPlainTextPlugin'; 6import {ContentEditable} from '@lexical/react/LexicalContentEditable'; 7import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin'; 8import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin'; 9import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; 10import {LexicalErrorBoundary} from '@lexical/react/LexicalErrorBoundary'; 11 12const theme = { 13 // Theme styling goes here 14 // ... 15} 16 17// When the editor changes, you can get notified via the 18// LexicalOnChangePlugin! 19function onChange(editorState) { 20 editorState.read(() => { 21 // Read the contents of the EditorState here. 22 const root = $getRoot(); 23 const selection = $getSelection(); 24 25 console.log(root, selection); 26 }); 27} 28 29// Lexical React plugins are React components, which makes them 30// highly composable. Furthermore, you can lazy load plugins if 31// desired, so you don't pay the cost for plugins until you 32// actually use them. 33function MyCustomAutoFocusPlugin() { 34 const [editor] = useLexicalComposerContext(); 35 36 useEffect(() => { 37 // Focus the editor when the effect fires! 38 editor.focus(); 39 }, [editor]); 40 41 return null; 42} 43 44// Catch any errors that occur during Lexical updates and log them 45// or throw them as needed. If you don't throw them, Lexical will 46// try to recover gracefully without losing user data. 47function onError(error) { 48 console.error(error); 49} 50 51function Editor() { 52 const initialConfig = { 53 namespace: 'MyEditor', 54 theme, 55 onError, 56 }; 57 58 return ( 59 <LexicalComposer initialConfig={initialConfig}> 60 <PlainTextPlugin 61 contentEditable={<ContentEditable />} 62 placeholder={<div>Enter some text...</div>} 63 ErrorBoundary={LexicalErrorBoundary} 64 /> 65 <OnChangePlugin onChange={onChange} /> 66 <HistoryPlugin /> 67 <MyCustomAutoFocusPlugin /> 68 </LexicalComposer> 69 ); 70}
The core of Lexical is a dependency-free text editor framework that allows developers to build powerful, simple and complex, editor surfaces. Lexical has a few concepts that are worth exploring:
Editor instances are the core thing that wires everything together. You can attach a contenteditable DOM element to editor instances, and also
register listeners and commands. Most importantly, the editor allows for updates to its EditorState
. You can create an editor instance
using the createEditor()
API, however you normally don't have to worry when using framework bindings such as @lexical/react
as this
is handled for you.
An Editor State is the underlying data model that represents what you want to show on the DOM. Editor States contain two parts:
Editor States are immutable once created, and in order to create one, you must do so via editor.update(() => {...})
. However, you
can also "hook" into an existing update using node transforms or command handlers – which are invoked as part of an existing update
workflow to prevent cascading/waterfalling of updates. You can retrieve the current editor state using editor.getEditorState()
.
Editor States are also fully serializable to JSON and can easily be serialized back into the editor using editor.parseEditorState()
.
When you want to read and/or update the Lexical node tree, you must do it via editor.update(() => {...})
. You may also do
read-only operations with the editor state via editor.read(() => {...})
or editor.getEditorState().read(() => {...})
.
The closure passed to the update or read call is important, and must be synchronous. It's the only place where you have full
"lexical" context of the active editor state, and providing you with access to the Editor State's node tree. We promote using
the convention of using $
prefixed functions (such as $getRoot()
) to convey that these functions must be called in this
context. Attempting to use them outside of a read or update will trigger a runtime error.
For those familiar with React Hooks, you can think of these $functions as having similar functionality:
Feature | React Hooks | Lexical $functions |
---|---|---|
Naming Convention | useFunction | $function |
Context Required | Can only be called while rendering | Can only be called while in an update or read |
Can be composed | Hooks can call other hooks | $functions can call other $functions |
Must be synchronous | ✅ | ✅ |
Other rules | ❌ Must be called unconditionally in the same order | ✅ None |
Node Transforms and Command Listeners are called with an implicit editor.update(() => {...})
context.
It is permitted to do nested updates, or nested reads, but an update should not be nested in a read
or vice versa. For example, editor.update(() => editor.update(() => {...}))
is allowed. It is permitted
to nest nest an editor.read
at the end of an editor.update
, but this will immediately flush the update
and any additional update in that callback will throw an error.
All Lexical Nodes are dependent on the associated Editor State. With few exceptions, you should only call methods
and access properties of a Lexical Node while in a read or update call (just like $
functions). Methods
on Lexical Nodes will first attempt to locate the latest (and possibly a writable) version of the node from the
active editor state using the node's unique key. All versions of a logical node have the same key. These keys
are managed by the Editor, are only present at runtime (not serialized), and should be considered to be random and
opaque (do not write tests that assume hard-coded values for keys).
This is done because the editor state's node tree is recursively frozen after reconciliation to
support efficient time travel (undo/redo and similar use cases). Methods that update nodes
first call node.getWritable()
, which will create a writable clone of a frozen node. This would normally
mean that any existing references (such as local variables) would refer to a stale version of the node, but
having Lexical Nodes always refer to the editor state allows for a simpler and less error-prone data model.
:::tip
If you use editor.read(() => { /* callback */ })
it will first flush any pending updates, so you will
always see a consistent state. When you are in an editor.update
, you will always be working with the
pending state, where node transforms and DOM reconciliation may not have run yet.
editor.getEditorState().read()
will use the latest reconciled EditorState
(after any node transforms,
DOM reconciliation, etc. have already run), any pending editor.update
mutations will not yet be visible.
:::
Lexical has its own DOM reconciler that takes a set of Editor States (always the "current" and the "pending") and applies a "diff" on them. It then uses this diff to update only the parts of the DOM that need changing. You can think of this as a kind-of virtual DOM, except Lexical is able to skip doing much of the diffing work, as it knows what was mutated in a given update. The DOM reconciler adopts performance optimizations that benefit the typical heuristics of a content editable – and is able to ensure consistency for LTR and RTL languages automatically.
Outside of invoking updates, the bulk of work done with Lexical is via listeners, node transforms and commands. These all stem from
the editor and are prefixed with register
. Another important feature is that all the register methods return a function to easily unsubscribe them. For example here is how you listen to an update to a Lexical editor:
1const unregisterListener = editor.registerUpdateListener(({editorState}) => { 2 // An update has occurred! 3 console.log(editorState); 4}); 5 6// Ensure we remove the listener later! 7unregisterListener();
Commands are the communication system used to wire everything together in Lexical. Custom commands can be created using createCommand()
and
dispatched to an editor using editor.dispatchCommand(command, payload)
. Lexical dispatches commands internally when key presses are triggered
and when other important signals occur. Commands can also be handled using editor.registerCommand(handler, priority)
, and incoming commands are
propagated through all handlers by priority until a handler stops the propagation (in a similar way to event propagation in the browser).
This section covers how to use Lexical, independently of any framework or library. For those intending to use Lexical in their React applications,
it's advisable to check out the source-code for the hooks that are shipped in @lexical/react
.
When you work with Lexical, you normally work with a single editor instance. An editor instance can be thought of as the one responsible for wiring up an EditorState with the DOM. The editor is also the place where you can register custom nodes, add listeners, and transforms.
An editor instance can be created from the lexical
package and accepts an optional configuration object that allows for theming and other options:
1import {createEditor} from 'lexical'; 2 3const config = { 4 namespace: 'MyEditor', 5 theme: { 6 ... 7 }, 8}; 9 10const editor = createEditor(config);
Once you have an editor instance, when ready, you can associate the editor instance with a content editable <div>
element in your document:
1const contentEditableElement = document.getElementById('editor'); 2 3editor.setRootElement(contentEditableElement);
If you want to clear the editor instance from the element, you can pass null
. Alternatively, you can switch to another element if need be,
just pass an alternative element reference to setRootElement()
.
With Lexical, the source of truth is not the DOM, but rather an underlying state model
that Lexical maintains and associates with an editor instance. You can get the latest
editor state from an editor by calling editor.getEditorState()
.
Editor states are serializable to JSON, and the editor instance provides a useful method to deserialize stringified editor states.
1const stringifiedEditorState = JSON.stringify(editor.getEditorState().toJSON());
2
3const newEditorState = editor.parseEditorState(stringifiedEditorState);
There are a few ways to update an editor instance:
editor.update()
editor.setEditorState()
editor.registerNodeTransform()
editor.registerCommand(EXAMPLE_COMMAND, () => {...}, priority)
The most common way to update the editor is to use editor.update()
. Calling this function
requires a function to be passed in that will provide access to mutate the underlying
editor state. When starting a fresh update, the current editor state is cloned and
used as the starting point. From a technical perspective, this means that Lexical leverages a technique
called double-buffering during updates. There's an editor state to represent what is current on
the screen, and another work-in-progress editor state that represents future changes.
Reconciling an update is typically an async process that allows Lexical to batch multiple synchronous
updates of the editor state together in a single update to the DOM – improving performance. When
Lexical is ready to commit the update to the DOM, the underlying mutations and changes in the update
batch will form a new immutable editor state. Calling editor.getEditorState()
will then return the
latest editor state based on the changes from the update.
Here's an example of how you can update an editor instance:
1import {$getRoot, $getSelection, $createParagraphNode} from 'lexical'; 2 3// Inside the `editor.update` you can use special $ prefixed helper functions. 4// These functions cannot be used outside the closure, and will error if you try. 5// (If you're familiar with React, you can imagine these to be a bit like using a hook 6// outside of a React function component). 7editor.update(() => { 8 // Get the RootNode from the EditorState 9 const root = $getRoot(); 10 11 // Get the selection from the EditorState 12 const selection = $getSelection(); 13 14 // Create a new ParagraphNode 15 const paragraphNode = $createParagraphNode(); 16 17 // Create a new TextNode 18 const textNode = $createTextNode('Hello world'); 19 20 // Append the text node to the paragraph 21 paragraphNode.append(textNode); 22 23 // Finally, append the paragraph to the root 24 root.append(paragraphNode); 25});
If you want to know when the editor updates so you can react to the changes, you can add an update listener to the editor, as shown below:
1editor.registerUpdateListener(({editorState}) => { 2 // The latest EditorState can be found as `editorState`. 3 // To read the contents of the EditorState, use the following API: 4 5 editorState.read(() => { 6 // Just like editor.update(), .read() expects a closure where you can use 7 // the $ prefixed helper functions. 8 }); 9});
Please read the CONTRIBUTING.md.
Download and install VSCode
Install extensions
editor.defaultFormatter
editor.formatOnSave
Note: Lexical does not support Internet Explorer or legacy versions of Edge.
git checkout -b my-new-branch
git commit -a -m 'Description of the changes'
git push origin my-new-branch
If you have any questions about Lexical, would like to discuss a bug report, or have questions about new integrations, feel free to join us at our Discord server.
Lexical engineers are checking this regularly.
npm run test-unit
runs only unit tests.npm run test-e2e-chromium
runs only chromium e2e tests.npm run debug-test-e2e-chromium
runs only chromium e2e tests in head mode for debugging.npm run test-e2e-firefox
runs only firefox e2e tests.npm run debug-test-e2e-firefox
runs only firefox e2e tests in head mode for debugging.npm run test-e2e-webkit
runs only webkit e2e tests.npm run debug-test-e2e-webkit
runs only webkit e2e tests in head mode for debugging.Lexical is MIT licensed.
No vulnerabilities found.
Reason
all changesets reviewed
Reason
30 commit(s) and 18 issue activity found in the last 90 days -- score normalized to 10
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
Reason
no binaries found in the repo
Reason
security policy file detected
Details
Reason
dependency not pinned by hash detected -- score normalized to 5
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
project is not fuzzed
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
19 existing vulnerabilities detected
Details
Score
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More