Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: config.blocks - deduplicate shared blocks #10905

Draft
wants to merge 27 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
db352a5
types, sanitize, type gen
AlessioGr Jan 19, 2025
c342dbe
importmap
AlessioGr Jan 19, 2025
ca20b80
handle string blocks everywhere
AlessioGr Jan 31, 2025
73b05e0
Merge remote-tracking branch 'origin/main' into feat/dedupe-blocks
AlessioGr Jan 31, 2025
809e113
fix ui build
AlessioGr Jan 31, 2025
321096d
fix next build
AlessioGr Jan 31, 2025
19f13d1
handle client blockMap
AlessioGr Feb 7, 2025
5130b87
Merge remote-tracking branch 'origin/main' into feat/dedupe-blocks
AlessioGr Feb 7, 2025
cb172a3
migrate clientblocks
AlessioGr Feb 7, 2025
8c6c729
Merge remote-tracking branch 'origin/main' into feat/dedupe-blocks
AlessioGr Feb 7, 2025
5bf1f82
it builds
AlessioGr Feb 7, 2025
2bdf8d2
Merge remote-tracking branch 'origin/main' into feat/dedupe-blocks
AlessioGr Feb 7, 2025
7701ab4
fix logic errors causing errors
AlessioGr Feb 8, 2025
015b4eb
fixes
AlessioGr Feb 8, 2025
8f61411
fix errors
AlessioGr Feb 8, 2025
8c4ea88
fix error due to sanitizeConfig update
AlessioGr Feb 8, 2025
a86bb0a
fix error accessing blockMap if no block references defined
AlessioGr Feb 8, 2025
3a41a63
Merge remote-tracking branch 'origin/main' into feat/dedupe-blocks
AlessioGr Feb 8, 2025
bfad073
Merge remote-tracking branch 'origin/main' into feat/dedupe-blocks
AlessioGr Feb 8, 2025
03b0922
fix tests
AlessioGr Feb 10, 2025
0e435fa
Merge remote-tracking branch 'origin/main' into feat/dedupe-blocks
AlessioGr Feb 10, 2025
4e3dca3
fix unit tests
AlessioGr Feb 10, 2025
3c84f88
feat: field.blockReferences
AlessioGr Feb 10, 2025
f625a4f
Merge remote-tracking branch 'origin/main' into feat/dedupe-blocks
AlessioGr Feb 10, 2025
da24eb2
we do need to sanitize field.blockReferences
AlessioGr Feb 10, 2025
36877e3
fix dumb logic
AlessioGr Feb 10, 2025
ec17695
fix: do not add empty blockReferences property if it's not required
AlessioGr Feb 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions packages/db-mongodb/src/models/buildSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type { IndexOptions, Schema, SchemaOptions, SchemaTypeOptions } from 'mon
import mongoose from 'mongoose'
import {
type ArrayField,
type Block,
type BlocksField,
type CheckboxField,
type CodeField,
Expand Down Expand Up @@ -193,11 +192,12 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
schema.add({
[field.name]: localizeSchema(field, fieldSchema, payload.config.localization),
})

field.blocks.forEach((blockItem: Block) => {
;(field.blockReferences ?? field.blocks).forEach((blockItem) => {
const blockSchema = new mongoose.Schema({}, { _id: false, id: false })

blockItem.fields.forEach((blockField) => {
const block = typeof blockItem === 'string' ? payload.blocks[blockItem] : blockItem

block.fields.forEach((blockField) => {
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[blockField.type]
if (addFieldSchema) {
addFieldSchema(blockField, blockSchema, payload, buildSchemaOptions)
Expand All @@ -207,11 +207,11 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
if (field.localized && payload.config.localization) {
payload.config.localization.localeCodes.forEach((localeCode) => {
// @ts-expect-error Possible incorrect typing in mongoose types, this works
schema.path(`${field.name}.${localeCode}`).discriminator(blockItem.slug, blockSchema)
schema.path(`${field.name}.${localeCode}`).discriminator(block.slug, blockSchema)
})
} else {
// @ts-expect-error Possible incorrect typing in mongoose types, this works
schema.path(field.name).discriminator(blockItem.slug, blockSchema)
schema.path(field.name).discriminator(block.slug, blockSchema)
}
})
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ const hasRelationshipOrUploadField = ({ fields }: { fields: Field[] }): boolean

if ('blocks' in field) {
for (const block of field.blocks) {
if (typeof block === 'string') {
// Skip - string blocks have been added in v3 and thus don't need to be migrated
continue
}
if (hasRelationshipOrUploadField({ fields: block.fields })) {
return true
}
Expand Down
30 changes: 18 additions & 12 deletions packages/db-mongodb/src/queries/getLocalizedSortProperty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,24 @@ export const getLocalizedSortProperty = ({
}

if (matchedField.type === 'blocks') {
nextFields = matchedField.blocks.reduce((flattenedBlockFields, block) => {
return [
...flattenedBlockFields,
...block.flattenedFields.filter(
(blockField) =>
(fieldAffectsData(blockField) &&
blockField.name !== 'blockType' &&
blockField.name !== 'blockName') ||
!fieldAffectsData(blockField),
),
]
}, [])
nextFields = (matchedField.blockReferences ?? matchedField.blocks).reduce(
(flattenedBlockFields, _block) => {
// TODO: iterate over blocks mapped to block slug in v4, or pass through payload.blocks
const block =
typeof _block === 'string' ? config.blocks.find((b) => b.slug === _block) : _block
return [
...flattenedBlockFields,
...block.flattenedFields.filter(
(blockField) =>
(fieldAffectsData(blockField) &&
blockField.name !== 'blockType' &&
blockField.name !== 'blockName') ||
!fieldAffectsData(blockField),
),
]
},
[],
)
}

const result = incomingResult ? `${incomingResult}.${localizedSegment}` : localizedSegment
Expand Down
19 changes: 14 additions & 5 deletions packages/db-mongodb/src/queries/sanitizeQueryValue.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import type { FlattenedBlock, FlattenedField, Payload, RelationshipField } from 'payload'
import type {
FlattenedBlock,
FlattenedField,
Payload,
RelationshipField,
SanitizedConfig,
} from 'payload'

import { Types } from 'mongoose'
import { createArrayFromCommaDelineated } from 'payload'
Expand Down Expand Up @@ -40,14 +46,17 @@ const buildExistsQuery = (formattedValue, path, treatEmptyString = true) => {
// returns nestedField Field object from blocks.nestedField path because getLocalizedPaths splits them only for relationships
const getFieldFromSegments = ({
field,
payload,
segments,
}: {
field: FlattenedBlock | FlattenedField
payload: Payload
segments: string[]
}) => {
if ('blocks' in field) {
for (const block of field.blocks) {
const field = getFieldFromSegments({ field: block, segments })
for (const _block of field.blockReferences ?? field.blocks) {
const block: FlattenedBlock = typeof _block === 'string' ? payload.blocks[_block] : _block
const field = getFieldFromSegments({ field: block, payload, segments })
if (field) {
return field
}
Expand All @@ -67,7 +76,7 @@ const getFieldFromSegments = ({
}

segments.shift()
return getFieldFromSegments({ field: foundField, segments })
return getFieldFromSegments({ field: foundField, payload, segments })
}
}
}
Expand All @@ -91,7 +100,7 @@ export const sanitizeQueryValue = ({
if (['array', 'blocks', 'group', 'tab'].includes(field.type) && path.includes('.')) {
const segments = path.split('.')
segments.shift()
const foundField = getFieldFromSegments({ field, segments })
const foundField = getFieldFromSegments({ field, payload, segments })

if (foundField) {
field = foundField
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ const traverseFields = ({
case 'blocks': {
const blocksSelect = select[field.name] as SelectType

for (const block of field.blocks) {
for (const _block of field.blockReferences ?? field.blocks) {
const block = typeof _block === 'string' ? adapter.payload.blocks[_block] : _block
if (
(selectMode === 'include' && blocksSelect[block.slug] === true) ||
(selectMode === 'exclude' && typeof blocksSelect[block.slug] === 'undefined')
Expand Down
4 changes: 2 additions & 2 deletions packages/db-mongodb/src/utilities/sanitizeRelationshipIDs.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { CollectionConfig, Field, SanitizedConfig, TraverseFieldsCallback } from 'payload'

import { Types } from 'mongoose'
import { APIError, traverseFields } from 'payload'
import { traverseFields } from 'payload'
import { fieldAffectsData } from 'payload/shared'

type Args = {
Expand Down Expand Up @@ -150,7 +150,7 @@ export const sanitizeRelationshipIDs = ({
}
}

traverseFields({ callback: sanitize, fields, fillEmpty: false, ref: data })
traverseFields({ callback: sanitize, config, fields, fillEmpty: false, ref: data })

return data
}
3 changes: 2 additions & 1 deletion packages/drizzle/src/find/traverseFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ export const traverseFields = ({
}
}

field.blocks.forEach((block) => {
;(field.blockReferences ?? field.blocks).forEach((_block) => {
const block = typeof _block === 'string' ? adapter.payload.blocks[_block] : _block
const blockKey = `_blocks_${block.slug}`

let blockSelect: boolean | SelectType | undefined
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FlattenedField } from 'payload'
import type { FlattenedBlock, FlattenedField } from 'payload'

type Args = {
doc: Record<string, unknown>
Expand Down Expand Up @@ -51,7 +51,10 @@ export const traverseFields = ({ doc, fields, locale, path, rows }: Args) => {
Object.entries(rowData).forEach(([locale, localeRows]) => {
if (Array.isArray(localeRows)) {
localeRows.forEach((row, i) => {
const matchedBlock = field.blocks.find((block) => block.slug === row.blockType)
// Can ignore string blocks, as those were arred in v3 and don't need to be migrated
const matchedBlock = field.blocks.find(
(block) => typeof block !== 'string' && block.slug === row.blockType,
) as FlattenedBlock | undefined

if (matchedBlock) {
return traverseFields({
Expand All @@ -69,7 +72,10 @@ export const traverseFields = ({ doc, fields, locale, path, rows }: Args) => {

if (Array.isArray(rowData)) {
rowData.forEach((row, i) => {
const matchedBlock = field.blocks.find((block) => block.slug === row.blockType)
// Can ignore string blocks, as those were arred in v3 and don't need to be migrated
const matchedBlock = field.blocks.find(
(block) => typeof block !== 'string' && block.slug === row.blockType,
) as FlattenedBlock | undefined

if (matchedBlock) {
return traverseFields({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ export const traverseFields = (args: Args) => {

case 'blocks': {
return field.blocks.forEach((block) => {
// Can ignore string blocks, as those were arred in v3 and don't need to be migrated
if (typeof block === 'string') {
return
}

const newTableName = args.adapter.tableNameMap.get(
`${args.rootTableName}_blocks_${toSnakeCase(block.slug)}`,
)
Expand Down
15 changes: 11 additions & 4 deletions packages/drizzle/src/queries/getTableColumnFromPath.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { SQL } from 'drizzle-orm'
import type { SQLiteTableWithColumns } from 'drizzle-orm/sqlite-core'
import type { FlattenedField, NumberField, TextField } from 'payload'
import type { FlattenedBlock, FlattenedField, NumberField, TextField } from 'payload'

import { and, eq, like, sql } from 'drizzle-orm'
import { type PgTableWithColumns } from 'drizzle-orm/pg-core'
Expand Down Expand Up @@ -176,7 +176,12 @@ export const getTableColumnFromPath = ({
// find the block config using the value
const blockTypes = Array.isArray(value) ? value : [value]
blockTypes.forEach((blockType) => {
const block = field.blocks.find((block) => block.slug === blockType)
const block =
adapter.payload.blocks[blockType] ??
((field.blockReferences ?? field.blocks).find(
(block) => typeof block !== 'string' && block.slug === blockType,
) as FlattenedBlock | undefined)

newTableName = adapter.tableNameMap.get(
`${tableName}_blocks_${toSnakeCase(block.slug)}`,
)
Expand All @@ -201,11 +206,13 @@ export const getTableColumnFromPath = ({
}
}

const hasBlockField = field.blocks.some((block) => {
const hasBlockField = (field.blockReferences ?? field.blocks).some((_block) => {
const block = typeof _block === 'string' ? adapter.payload.blocks[_block] : _block

newTableName = adapter.tableNameMap.get(`${tableName}_blocks_${toSnakeCase(block.slug)}`)
constraintPath = `${constraintPath}${field.name}.%.`

let result
let result: TableColumn
const blockConstraints = []
const blockSelectFields = {}
try {
Expand Down
4 changes: 3 additions & 1 deletion packages/drizzle/src/schema/traverseFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,9 @@ export const traverseFields = ({
case 'blocks': {
const disableNotNullFromHere = Boolean(field.admin?.condition) || disableNotNull

field.blocks.forEach((block) => {
;(field.blockReferences ?? field.blocks).forEach((_block) => {
const block = typeof _block === 'string' ? adapter.payload.blocks[_block] : _block

const blockTableName = createTableName({
adapter,
config: block,
Expand Down
21 changes: 17 additions & 4 deletions packages/drizzle/src/transform/read/traverseFields.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { FlattenedField, JoinQuery, SanitizedConfig } from 'payload'
import type { FlattenedBlock, FlattenedField, JoinQuery, SanitizedConfig } from 'payload'

import { fieldAffectsData, fieldIsVirtual } from 'payload/shared'
import { fieldIsVirtual } from 'payload/shared'

import type { DrizzleAdapter } from '../../types.js'
import type { BlocksMap } from '../../utilities/createBlocksMap.js'
Expand Down Expand Up @@ -216,7 +216,11 @@ export const traverseFields = <T extends Record<string, unknown>>({

Object.entries(result[field.name]).forEach(([locale, localizedBlocks]) => {
result[field.name][locale] = localizedBlocks.map((row) => {
const block = field.blocks.find(({ slug }) => slug === row.blockType)
const block =
adapter.payload.blocks[row.blockType] ??
((field.blockReferences ?? field.blocks).find(
(block) => typeof block !== 'string' && block.slug === row.blockType,
) as FlattenedBlock | undefined)

if (block) {
const blockResult = traverseFields<T>({
Expand Down Expand Up @@ -265,7 +269,16 @@ export const traverseFields = <T extends Record<string, unknown>>({
row.id = row._uuid
delete row._uuid
}
const block = field.blocks.find(({ slug }) => slug === row.blockType)

if (typeof row.blockType !== 'string') {
return acc
}

const block =
adapter.payload.blocks[row.blockType] ??
((field.blockReferences ?? field.blocks).find(
(block) => typeof block !== 'string' && block.slug === row.blockType,
) as FlattenedBlock | undefined)

if (block) {
if (
Expand Down
10 changes: 8 additions & 2 deletions packages/drizzle/src/transform/write/blocks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FlattenedBlocksField } from 'payload'
import type { FlattenedBlock, FlattenedBlocksField } from 'payload'

import toSnakeCase from 'to-snake-case'

Expand Down Expand Up @@ -51,7 +51,13 @@ export const transformBlocks = ({
if (typeof blockRow.blockType !== 'string') {
return
}
const matchedBlock = field.blocks.find(({ slug }) => slug === blockRow.blockType)

const matchedBlock =
adapter.payload.blocks[blockRow.blockType] ??
((field.blockReferences ?? field.blocks).find(
(block) => typeof block !== 'string' && block.slug === blockRow.blockType,
) as FlattenedBlock | undefined)

if (!matchedBlock) {
return
}
Expand Down
4 changes: 2 additions & 2 deletions packages/drizzle/src/transform/write/traverseFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ export const traverseFields = ({
}

if (field.type === 'blocks') {
field.blocks.forEach(({ slug }) => {
blocksToDelete.add(toSnakeCase(slug))
;(field.blockReferences ?? field.blocks).forEach((block) => {
blocksToDelete.add(toSnakeCase(typeof block === 'string' ? block : block.slug))
})

if (field.localized) {
Expand Down
15 changes: 11 additions & 4 deletions packages/graphql/src/schema/buildObjectType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,15 @@ export function buildObjectType({
}
},
blocks: (objectTypeConfig: ObjectTypeConfig, field: BlocksField) => {
const blockTypes: GraphQLObjectType<any, any>[] = field.blocks.reduce((acc, block) => {
if (!graphqlResult.types.blockTypes[block.slug]) {
const blockTypes: GraphQLObjectType<any, any>[] = (
field.blockReferences ?? field.blocks
).reduce((acc, _block) => {
const blockSlug = typeof _block === 'string' ? _block : _block.slug
if (!graphqlResult.types.blockTypes[blockSlug]) {
// TODO: iterate over blocks mapped to block slug in v4, or pass through payload.blocks
const block =
typeof _block === 'string' ? config.blocks.find((b) => b.slug === _block) : _block

const interfaceName =
block?.interfaceName || block?.graphQL?.singularName || toWords(block.slug, true)

Expand All @@ -133,8 +140,8 @@ export function buildObjectType({
}
}

if (graphqlResult.types.blockTypes[block.slug]) {
acc.push(graphqlResult.types.blockTypes[block.slug])
if (graphqlResult.types.blockTypes[blockSlug]) {
acc.push(graphqlResult.types.blockTypes[blockSlug])
}

return acc
Expand Down
4 changes: 3 additions & 1 deletion packages/next/src/views/LivePreview/Context/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { ClientField, LivePreviewConfig } from 'payload'

import { DndContext } from '@dnd-kit/core'
import { useConfig } from '@payloadcms/ui'
import { fieldSchemaToJSON } from 'payload/shared'
import React, { useCallback, useEffect, useState } from 'react'

Expand Down Expand Up @@ -43,6 +44,7 @@ export const LivePreviewProvider: React.FC<LivePreviewProviderProps> = ({
const iframeRef = React.useRef<HTMLIFrameElement>(null)

const [iframeHasLoaded, setIframeHasLoaded] = useState(false)
const { config } = useConfig()

const [zoom, setZoom] = useState(1)

Expand All @@ -59,7 +61,7 @@ export const LivePreviewProvider: React.FC<LivePreviewProviderProps> = ({
React.useState<LivePreviewConfig['breakpoints'][0]['name']>('responsive')

const [fieldSchemaJSON] = useState(() => {
return fieldSchemaToJSON(fieldSchema)
return fieldSchemaToJSON(fieldSchema, config)
})

// The toolbar needs to freely drag and drop around the page
Expand Down
Loading
Loading