From 259b6e642b9b1e26dcfb70203328357fdf7c7188 Mon Sep 17 00:00:00 2001 From: David Matejka Date: Mon, 8 Jul 2024 11:08:25 +0200 Subject: [PATCH 1/5] perf(binding): marker merger micro optim --- packages/binding/src/core/MarkerMerger.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/binding/src/core/MarkerMerger.ts b/packages/binding/src/core/MarkerMerger.ts index 5643852cf4..05b8304821 100644 --- a/packages/binding/src/core/MarkerMerger.ts +++ b/packages/binding/src/core/MarkerMerger.ts @@ -142,6 +142,9 @@ export class MarkerMerger { if (original === fresh) { return original } + if (original.size === 0) { + return fresh + } const newOriginal: Map = new Map(original) for (const [placeholderName, freshMarker] of fresh) { const markerFromOriginal = newOriginal.get(placeholderName) @@ -160,6 +163,9 @@ export class MarkerMerger { if (original === fresh) { return original } + if (original.size === 0) { + return fresh + } const newOriginal: EntityFieldPlaceholders = new Map(original) for (const [fieldName, freshPlaceholders] of fresh) { const placeholderFromOriginal = newOriginal.get(fieldName) From 1500ab56d2c31c016db9f79d8a1c1306879a6161 Mon Sep 17 00:00:00 2001 From: David Matejka Date: Mon, 8 Jul 2024 11:32:04 +0200 Subject: [PATCH 2/5] perf(binding): fast-path optim in query language --- .../src/queryLanguage/QueryLanguage.ts | 75 +++++++++++++------ .../binding/src/queryLanguage/tokenList.ts | 2 + 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/packages/binding/src/queryLanguage/QueryLanguage.ts b/packages/binding/src/queryLanguage/QueryLanguage.ts index 67a7eda8f9..2d60610899 100644 --- a/packages/binding/src/queryLanguage/QueryLanguage.ts +++ b/packages/binding/src/queryLanguage/QueryLanguage.ts @@ -51,6 +51,7 @@ import { import { Parser } from './Parser' import { GraphQlLiteral } from '@contember/client' import { ParsedHasManyRelation, ParsedHasOneRelation } from './ParserResults' +import { TokenRegExps } from './tokenList' const emptyObject = Object.freeze({}) @@ -293,13 +294,17 @@ export class QueryLanguage { } private static augmentDesugaredHasOneRelationPath( - path: ParsedHasOneRelation[], + path: (ParsedHasOneRelation | string)[], unsugarable: UnsugarableHasOneRelation, environment: Environment, ): HasOneRelation[] { return path.map((desugaredHasOneRelation, i) => // Unsugarable applies to the last - this.desugarHasOneRelation(desugaredHasOneRelation, i === path.length - 1 ? unsugarable : {}, environment), + this.desugarHasOneRelation( + typeof desugaredHasOneRelation === 'string' ? { field: desugaredHasOneRelation } : desugaredHasOneRelation, + i === path.length - 1 ? unsugarable : {}, + environment, + ), ) } @@ -636,19 +641,30 @@ export class QueryLanguage { sugaredRelativeSingleEntity: string | SugaredRelativeSingleEntity, environment: Environment, ): RelativeSingleEntity { + let field: SugaredRelativeSingleEntity['field'] + let unsugarableEntity: Omit if (typeof sugaredRelativeSingleEntity === 'string') { - return this.desugarRelativeSingleEntity({ field: sugaredRelativeSingleEntity }, environment) + field = sugaredRelativeSingleEntity + unsugarableEntity = {} + } else { + field = sugaredRelativeSingleEntity.field + unsugarableEntity = sugaredRelativeSingleEntity } - const { field, ...unsugarableEntity } = sugaredRelativeSingleEntity let hasOneRelationPath: HasOneRelation[] if (typeof field === 'string') { - const parsed = this.parseRelativeSingleEntity(field, environment) - hasOneRelationPath = this.augmentDesugaredHasOneRelationPath( - parsed.hasOneRelationPath, - unsugarableEntity, - environment, - ) + // fast path + if (field.match(TokenRegExps.dotSeparatedIdentifier)) { + const parts = field.split('.') + hasOneRelationPath = this.augmentDesugaredHasOneRelationPath(parts, unsugarableEntity, environment) + } else { + const parsed = this.parseRelativeSingleEntity(field, environment) + hasOneRelationPath = this.augmentDesugaredHasOneRelationPath( + parsed.hasOneRelationPath, + unsugarableEntity, + environment, + ) + } } else { hasOneRelationPath = this.desugarHasOneRelationPath(field, unsugarableEntity, environment) @@ -663,18 +679,30 @@ export class QueryLanguage { sugaredRelativeSingleField: string | SugaredRelativeSingleField, environment: Environment, ): RelativeSingleField { + let field: SugaredRelativeSingleField['field'] + let unsugarableField: Omit if (typeof sugaredRelativeSingleField === 'string') { - return this.desugarRelativeSingleField({ field: sugaredRelativeSingleField }, environment) + field = sugaredRelativeSingleField + unsugarableField = {} + } else { + field = sugaredRelativeSingleField.field + unsugarableField = sugaredRelativeSingleField } - const { field, ...unsugarableField } = sugaredRelativeSingleField - let hasOneRelationPath: HasOneRelation[] let fieldName: FieldName if (typeof field === 'string') { - const parsed = this.parseRelativeSingleField(field, environment) - hasOneRelationPath = this.augmentDesugaredHasOneRelationPath(parsed.hasOneRelationPath, {}, environment) - fieldName = parsed.field + // fast path + if (field.match(TokenRegExps.dotSeparatedIdentifier)) { + const parts = field.split('.') + fieldName = parts.pop()! + hasOneRelationPath = this.augmentDesugaredHasOneRelationPath(parts, {}, environment) + } else { + const parsed = this.parseRelativeSingleField(field, environment) + hasOneRelationPath = this.augmentDesugaredHasOneRelationPath(parsed.hasOneRelationPath, {}, environment) + fieldName = parsed.field + } + } else { hasOneRelationPath = this.desugarHasOneRelationPath(field.hasOneRelationPath, {}, environment) fieldName = field.field @@ -696,16 +724,17 @@ export class QueryLanguage { sugaredRelativeEntityList: string | SugaredRelativeEntityList, environment: Environment, ): RelativeEntityList { + let field: SugaredRelativeEntityList['field'] + let unsugarableEntityList: Omit + if (typeof sugaredRelativeEntityList === 'string') { - return this.desugarRelativeEntityList( - { - field: sugaredRelativeEntityList, - }, - environment, - ) + field = sugaredRelativeEntityList + unsugarableEntityList = {} + } else { + field = sugaredRelativeEntityList.field + unsugarableEntityList = sugaredRelativeEntityList } - const { field, ...unsugarableEntityList } = sugaredRelativeEntityList let hasOneRelationPath: HasOneRelation[] let hasManyRelation: HasManyRelation if (typeof field === 'string') { diff --git a/packages/binding/src/queryLanguage/tokenList.ts b/packages/binding/src/queryLanguage/tokenList.ts index 2026f7deeb..f4c60c2b40 100644 --- a/packages/binding/src/queryLanguage/tokenList.ts +++ b/packages/binding/src/queryLanguage/tokenList.ts @@ -3,6 +3,8 @@ import { createToken, Lexer } from 'chevrotain' export namespace TokenRegExps { export const entityIdentifier = /[A-Z_]\w*/ export const identifier = /[a-zA-Z_]\w*/ + + export const dotSeparatedIdentifier = /^[a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*$/ } const identifier = createToken({ From ac06623448bdf414c46dcb489767cb4f54ea7b6e Mon Sep 17 00:00:00 2001 From: David Matejka Date: Mon, 8 Jul 2024 11:33:44 +0200 Subject: [PATCH 3/5] refactor(react-slate-editor-legacy): simplify prop types --- .../src/blockEditor/BlockEditor.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/react-slate-editor-legacy/src/blockEditor/BlockEditor.tsx b/packages/react-slate-editor-legacy/src/blockEditor/BlockEditor.tsx index cc5c81f859..da0169689d 100644 --- a/packages/react-slate-editor-legacy/src/blockEditor/BlockEditor.tsx +++ b/packages/react-slate-editor-legacy/src/blockEditor/BlockEditor.tsx @@ -2,11 +2,11 @@ import { BindingError, Component, Environment, + Field, FieldValue, HasMany, SugaredField, - SugaredFieldProps, - SugaredRelativeEntityList, + SugaredRelativeEntityList, SugaredRelativeSingleField, TreeNodeEnvironmentFactory, useDesugaredRelativeSingleField, useEntity, @@ -36,17 +36,17 @@ import { createEditor, CreateEditorPublicOptions, paragraphElementType } from '@ export interface BlockEditorProps extends SugaredRelativeEntityList, CreateEditorPublicOptions { - contentField: SugaredFieldProps['field'] - sortableBy: SugaredFieldProps['field'] + contentField: SugaredRelativeSingleField['field'] + sortableBy: SugaredRelativeSingleField['field'] children?: ReactNode referencesField?: SugaredRelativeEntityList | string - referenceDiscriminationField?: SugaredFieldProps['field'] + referenceDiscriminationField?: SugaredRelativeSingleField['field'] monolithicReferencesMode?: boolean renderReference?: ComponentType embedReferenceDiscriminateBy?: SugaredDiscriminateBy - embedContentDiscriminationField?: SugaredFieldProps['field'] + embedContentDiscriminationField?: SugaredRelativeSingleField['field'] embedHandlers?: Iterable renderSortableBlock: OverrideRenderElementOptions['renderSortableBlock'] } @@ -196,11 +196,11 @@ const BlockEditorComponent: FunctionComponent = Component( {...(typeof props.referencesField === 'string' ? { field: props.referencesField } : props.referencesField)} initialEntityCount={0} > - + {props.children} {props.embedContentDiscriminationField && ( <> - + {embedHandlers.map((handler, i) => ( {handler.staticRender(environment)} ))} @@ -212,8 +212,8 @@ const BlockEditorComponent: FunctionComponent = Component( return ( <> - - + + {!props.monolithicReferencesMode && references} {props.monolithicReferencesMode && references} From 82307d00c241bcc18b0c1a67826d015e71a4cc97 Mon Sep 17 00:00:00 2001 From: David Matejka Date: Mon, 8 Jul 2024 11:34:02 +0200 Subject: [PATCH 4/5] perf(react-repeater): simplify sortableBy type --- .../src/components/Repeater.tsx | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/packages/react-repeater/src/components/Repeater.tsx b/packages/react-repeater/src/components/Repeater.tsx index 45f0bf787b..0eab07131f 100644 --- a/packages/react-repeater/src/components/Repeater.tsx +++ b/packages/react-repeater/src/components/Repeater.tsx @@ -2,32 +2,21 @@ import React, { useEffect, useMemo } from 'react' import { verifySortableProp } from '../internal/verifySortableProp' import { useCreateRepeaterMethods } from '../internal/useCreateRepeaterMethods' import { RepeaterEntityListAccessorContext, RepeaterMethodsContext, RepeaterSortedEntitiesContext } from '../contexts' -import { - Component, - EntityListSubTree, - HasMany, - repairEntitiesOrder, - sortEntities, - SugaredField, - SugaredFieldProps, - useEntityList, - useEntityListSubTree, - useEnvironment, -} from '@contember/react-binding' -import { EntityListAccessor, QueryLanguage, SugaredQualifiedEntityList, SugaredRelativeEntityList } from '@contember/binding' +import { Component, EntityListSubTree, HasMany, repairEntitiesOrder, sortEntities, SugaredField, useEntityList, useEntityListSubTree, useEnvironment } from '@contember/react-binding' +import { EntityListAccessor, QueryLanguage, SugaredQualifiedEntityList, SugaredRelativeEntityList, SugaredRelativeSingleField } from '@contember/binding' export type RepeaterRelativeProps = & SugaredRelativeEntityList & { children?: React.ReactNode - sortableBy?: SugaredFieldProps['field'] + sortableBy?: SugaredRelativeSingleField['field'] } export type RepeaterQualifiedProps = & SugaredQualifiedEntityList & { children?: React.ReactNode - sortableBy?: SugaredFieldProps['field'] + sortableBy?: SugaredRelativeSingleField['field'] } export type RepeaterProps = @@ -90,7 +79,7 @@ const RepeaterQualified = Component( interface RepeaterInnerProps { accessor: EntityListAccessor children: React.ReactNode - sortableBy?: SugaredFieldProps['field'] + sortableBy?: SugaredRelativeSingleField['field'] } const RepeaterInner = ({ sortableBy, accessor, children }: RepeaterInnerProps) => { From a422e0f79b2ec78dc84dcd556066eac2da372a45 Mon Sep 17 00:00:00 2001 From: David Matejka Date: Mon, 8 Jul 2024 14:04:53 +0200 Subject: [PATCH 5/5] chore: ae up --- build/api/binding.api.md | 2 ++ build/api/react-repeater.api.md | 6 +++--- build/api/react-slate-editor-legacy.api.md | 9 +++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/build/api/binding.api.md b/build/api/binding.api.md index 7953840bed..a96b527efc 100644 --- a/build/api/binding.api.md +++ b/build/api/binding.api.md @@ -1838,6 +1838,8 @@ export namespace TokenRegExps { entityIdentifier: RegExp; const // (undocumented) identifier: RegExp; + const // (undocumented) + dotSeparatedIdentifier: RegExp; } // @public (undocumented) diff --git a/build/api/react-repeater.api.md b/build/api/react-repeater.api.md index 415e2ae978..131c9e1a3b 100644 --- a/build/api/react-repeater.api.md +++ b/build/api/react-repeater.api.md @@ -11,9 +11,9 @@ import { EntityListAccessor } from '@contember/binding'; import { JSX as JSX_2 } from 'react/jsx-runtime'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; -import { SugaredFieldProps } from '@contember/react-binding'; import { SugaredQualifiedEntityList } from '@contember/binding'; import { SugaredRelativeEntityList } from '@contember/binding'; +import { SugaredRelativeSingleField } from '@contember/binding'; // @public (undocumented) export const Repeater: React_2.NamedExoticComponent; @@ -83,13 +83,13 @@ export type RepeaterProps = RepeaterQualifiedProps | RepeaterRelativeProps; // @public (undocumented) export type RepeaterQualifiedProps = SugaredQualifiedEntityList & { children?: React_2.ReactNode; - sortableBy?: SugaredFieldProps['field']; + sortableBy?: SugaredRelativeSingleField['field']; }; // @public (undocumented) export type RepeaterRelativeProps = SugaredRelativeEntityList & { children?: React_2.ReactNode; - sortableBy?: SugaredFieldProps['field']; + sortableBy?: SugaredRelativeSingleField['field']; }; // @public (undocumented) diff --git a/build/api/react-slate-editor-legacy.api.md b/build/api/react-slate-editor-legacy.api.md index 4a8fec3342..fc980bc44b 100644 --- a/build/api/react-slate-editor-legacy.api.md +++ b/build/api/react-slate-editor-legacy.api.md @@ -37,6 +37,7 @@ import { RenderElementProps } from 'slate-react'; import * as Slate from 'slate'; import { SugaredFieldProps } from '@contember/react-binding'; import { SugaredRelativeEntityList } from '@contember/react-binding'; +import { SugaredRelativeSingleField } from '@contember/react-binding'; // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@contember/react-slate-editor-legacy" does not have an export "BlockRepeater" // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@contember/react-slate-editor-legacy" does not have an export "DiscriminatedBlocks" @@ -57,9 +58,9 @@ export interface BlockEditorProps extends SugaredRelativeEntityList, CreateEdito // (undocumented) children?: ReactNode; // (undocumented) - contentField: SugaredFieldProps['field']; + contentField: SugaredRelativeSingleField['field']; // (undocumented) - embedContentDiscriminationField?: SugaredFieldProps['field']; + embedContentDiscriminationField?: SugaredRelativeSingleField['field']; // (undocumented) embedHandlers?: Iterable; // (undocumented) @@ -67,7 +68,7 @@ export interface BlockEditorProps extends SugaredRelativeEntityList, CreateEdito // (undocumented) monolithicReferencesMode?: boolean; // (undocumented) - referenceDiscriminationField?: SugaredFieldProps['field']; + referenceDiscriminationField?: SugaredRelativeSingleField['field']; // (undocumented) referencesField?: SugaredRelativeEntityList | string; // (undocumented) @@ -77,7 +78,7 @@ export interface BlockEditorProps extends SugaredRelativeEntityList, CreateEdito // (undocumented) renderSortableBlock: OverrideRenderElementOptions['renderSortableBlock']; // (undocumented) - sortableBy: SugaredFieldProps['field']; + sortableBy: SugaredRelativeSingleField['field']; } // @public (undocumented)