perf(richtext-lexical)!: significantly reduce lexical rerendering and amount of network requests from blocks (#9255)

The field RSC now provides an initial state for all lexical blocks. This
completely obliterates any flashes and lexical block loading states when
loading or saving a document.

Previously, when a document is loaded or saved, every lexical block was
sending a network request in order to fetch their form state. Now, this
is batched and handled in the lexical server component. All lexical
block form states are sent to the client together with the parent
lexical field, and are thus available immediately.

We also do the same with block collapsed preferences. Thus, there are no
loading states or layout shifts/flashes of blocks anymore.

Additionally, when saving a document while your cursor is inside a
lexical field, the cursor position is preserved. Previously, a document
save would kick your cursor out of the lexical field.

## Look at how nice this is:


https://github.com/user-attachments/assets/21d736d4-8f80-4df0-a782-7509edd993da

**BREAKING:**

This removes the `feature.hooks.load` and `feature.hooks.save`
interfaces from custom lexical features, as they weren't used internally
and added unnecessary, additional overhead.

If you have custom features that use those, you can migrate to using
normal payload hooks that run on the server instead of the client.
This commit is contained in:
Alessio Gravili
2024-11-17 01:31:55 -07:00
committed by GitHub
parent abe4cc87ca
commit 35917c67d7
20 changed files with 394 additions and 302 deletions

View File

@@ -715,6 +715,8 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
}
renderFieldFn({
id,
collectionSlug,
data: fullData,
fieldConfig: fieldConfig as Field,
fieldSchemaMap,
@@ -726,6 +728,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
parentSchemaPath,
path,
permissions,
preferences,
previousFieldState: previousFormState?.[path],
req,
schemaPath,

View File

@@ -14,7 +14,8 @@ import { fieldAffectsData } from 'payload/shared'
import type { RenderFieldMethod } from './types.js'
import { RenderServerComponent } from '../../elements/RenderServerComponent/index.js'
import { FieldDescription } from '../../fields/FieldDescription/index.js'
// eslint-disable-next-line payload/no-imports-from-exports-dir -- need this to reference already existing bundle. Otherwise, bundle size increases., payload/no-imports-from-exports-dir
import { FieldDescription } from '../../exports/client/index.js'
const defaultUIFieldComponentKeys: Array<'Cell' | 'Description' | 'Field' | 'Filter'> = [
'Cell',
@@ -24,6 +25,8 @@ const defaultUIFieldComponentKeys: Array<'Cell' | 'Description' | 'Field' | 'Fil
]
export const renderField: RenderFieldMethod = ({
id,
collectionSlug,
data,
fieldConfig,
fieldSchemaMap,
@@ -35,6 +38,7 @@ export const renderField: RenderFieldMethod = ({
parentSchemaPath,
path,
permissions: incomingPermissions,
preferences,
req,
schemaPath,
siblingData,
@@ -72,6 +76,7 @@ export const renderField: RenderFieldMethod = ({
}
const serverProps: ServerComponentProps = {
id,
clientField,
data,
field: fieldConfig,
@@ -79,9 +84,13 @@ export const renderField: RenderFieldMethod = ({
permissions,
// TODO: Should we pass explicit values? initialValue, value, valid
// value and initialValue should be typed
collectionSlug,
formState,
i18n: req.i18n,
operation,
payload: req.payload,
preferences,
req,
siblingData,
user: req.user,
}

View File

@@ -1,5 +1,6 @@
import type {
Data,
DocumentPreferences,
Field,
FieldSchemaMap,
FieldState,
@@ -10,11 +11,13 @@ import type {
} from 'payload'
export type RenderFieldArgs = {
collectionSlug: string
data: Data
fieldConfig: Field
fieldSchemaMap: FieldSchemaMap
fieldState: FieldState
formState: FormState
id?: number | string
indexPath: string
operation: Operation
parentPath: string
@@ -26,6 +29,7 @@ export type RenderFieldArgs = {
}
| null
| SanitizedFieldPermissions
preferences: DocumentPreferences
previousFieldState: FieldState
req: PayloadRequest
schemaPath: string