If you had a lot of fields and collections, createClientConfig would be extremely slow, as it was copying a lot of memory. In my test config with a lot of fields and collections, it took 4 seconds(!!). And not only that, it also ran between every single page navigation. This PR significantly speeds up the createClientConfig function. In my test config, its execution speed went from 4 seconds to 50 ms. Additionally, createClientConfig is now properly cached in both dev & prod. It no longer runs between every single page navigation. Even if you trigger a full page reload, createClientConfig will be cached and not run again. Despite that, HMR remains fully-functional. This will make payload feel noticeably faster for large configs - especially if it contains a lot of richtext fields, as it was previously deep-copying the relatively large richText editor configs over and over again. ## Before - 40 sec navigation speed https://github.com/user-attachments/assets/fe6b707a-459b-44c6-982a-b277f6cbb73f ## After - 1 sec navigation speed https://github.com/user-attachments/assets/384fba63-dc32-4396-b3c2-0353fcac6639 ## Todo - [x] Implement ClientSchemaMap and cache it, to remove createClientField call in our form state endpoint - [x] Enable schemaMap caching for dev - [x] Cache lexical clientField generation, or add it to the parent clientConfig ## Lexical changes Red: old / removed Green: new  ### Speed up version queries This PR comes with performance optimizations for fetching versions before a document is loaded. Not only does it use the new select API to limit the fields it queries, it also completely skips a database query if the current document is published. ### Speed up lexical init Removes a bunch of unnecessary deep copying of lexical objects which caused higher memory usage and slower load times. Additionally, the lexical default config sanitization now happens less often.
128 lines
3.0 KiB
TypeScript
128 lines
3.0 KiB
TypeScript
import type { I18n } from '@payloadcms/translations'
|
|
import type { Field, FieldSchemaMap, SanitizedConfig } from 'payload'
|
|
|
|
import { MissingEditorProp } from 'payload'
|
|
import { getFieldPaths, tabHasName } from 'payload/shared'
|
|
|
|
type Args = {
|
|
config: SanitizedConfig
|
|
fields: Field[]
|
|
i18n: I18n<any, any>
|
|
parentIndexPath: string
|
|
parentSchemaPath: string
|
|
schemaMap: FieldSchemaMap
|
|
}
|
|
|
|
export const traverseFields = ({
|
|
config,
|
|
fields,
|
|
i18n,
|
|
parentIndexPath,
|
|
parentSchemaPath,
|
|
schemaMap,
|
|
}: Args) => {
|
|
for (const [index, field] of fields.entries()) {
|
|
const { indexPath, schemaPath } = getFieldPaths({
|
|
field,
|
|
index,
|
|
parentIndexPath: 'name' in field ? '' : parentIndexPath,
|
|
parentPath: '',
|
|
parentSchemaPath,
|
|
})
|
|
|
|
schemaMap.set(schemaPath, field)
|
|
|
|
switch (field.type) {
|
|
case 'array':
|
|
case 'group':
|
|
traverseFields({
|
|
config,
|
|
fields: field.fields,
|
|
i18n,
|
|
parentIndexPath: '',
|
|
parentSchemaPath: schemaPath,
|
|
schemaMap,
|
|
})
|
|
|
|
break
|
|
|
|
case 'blocks':
|
|
field.blocks.map((block) => {
|
|
const blockSchemaPath = `${schemaPath}.${block.slug}`
|
|
|
|
schemaMap.set(blockSchemaPath, block)
|
|
traverseFields({
|
|
config,
|
|
fields: block.fields,
|
|
i18n,
|
|
parentIndexPath: '',
|
|
parentSchemaPath: blockSchemaPath,
|
|
schemaMap,
|
|
})
|
|
})
|
|
|
|
break
|
|
case 'collapsible':
|
|
case 'row':
|
|
traverseFields({
|
|
config,
|
|
fields: field.fields,
|
|
i18n,
|
|
parentIndexPath: indexPath,
|
|
parentSchemaPath,
|
|
schemaMap,
|
|
})
|
|
|
|
break
|
|
|
|
case 'richText':
|
|
if (!field?.editor) {
|
|
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
|
}
|
|
|
|
if (typeof field.editor === 'function') {
|
|
throw new Error('Attempted to access unsanitized rich text editor.')
|
|
}
|
|
|
|
if (typeof field.editor.generateSchemaMap === 'function') {
|
|
field.editor.generateSchemaMap({
|
|
config,
|
|
field,
|
|
i18n,
|
|
schemaMap,
|
|
schemaPath,
|
|
})
|
|
}
|
|
|
|
break
|
|
|
|
case 'tabs':
|
|
field.tabs.map((tab, tabIndex) => {
|
|
const { indexPath: tabIndexPath, schemaPath: tabSchemaPath } = getFieldPaths({
|
|
field: {
|
|
...tab,
|
|
type: 'tab',
|
|
},
|
|
index: tabIndex,
|
|
parentIndexPath: indexPath,
|
|
parentPath: '',
|
|
parentSchemaPath,
|
|
})
|
|
|
|
schemaMap.set(tabSchemaPath, tab)
|
|
|
|
traverseFields({
|
|
config,
|
|
fields: tab.fields,
|
|
i18n,
|
|
parentIndexPath: tabHasName(tab) ? '' : tabIndexPath,
|
|
parentSchemaPath: tabHasName(tab) ? tabSchemaPath : parentSchemaPath,
|
|
schemaMap,
|
|
})
|
|
})
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|