From 94db813bc53f52bd28268fd3f7b4daf7982109cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Szab=C3=B3?= Date: Wed, 24 Mar 2021 20:34:45 +0100 Subject: [PATCH] Parsing patterns when idling (performance follow-up for inserting patterns into containers) (#29444) We need to parse all the patterns to determine whether a pattern can be inserted into the selected destination. We parse them immediately when the editor loads which freezes the editor until the parser finishes. To avoid the thread blocking, this PR changes it to only parse when we have resources to do so. --- .../src/components/block-list/index.js | 2 + .../components/block-patterns-list/index.js | 12 ++-- .../src/components/block-preview/index.js | 7 +- packages/block-editor/src/store/selectors.js | 30 +++++---- .../block-editor/src/store/test/selectors.js | 4 ++ .../src/utils/pre-parse-patterns.js | 64 +++++++++++++++++++ packages/edit-post/src/editor.js | 4 -- .../edit-site/src/components/editor/index.js | 4 -- 8 files changed, 102 insertions(+), 25 deletions(-) create mode 100644 packages/block-editor/src/utils/pre-parse-patterns.js diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 40dbb02d4c6d4..8e03ebf6b6594 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -20,6 +20,7 @@ import useInsertionPoint from './insertion-point'; import BlockPopover from './block-popover'; import { store as blockEditorStore } from '../../store'; import { useScrollSelectionIntoView } from '../selection-scroll-into-view'; +import { usePreParsePatterns } from '../../utils/pre-parse-patterns'; import { LayoutProvider, defaultLayout } from './layout'; export const BlockNodes = createContext(); @@ -30,6 +31,7 @@ export default function BlockList( { className, __experimentalLayout } ) { const [ blockNodes, setBlockNodes ] = useState( {} ); const insertionPoint = useInsertionPoint( ref ); useScrollSelectionIntoView( ref ); + usePreParsePatterns(); const isLargeViewport = useViewportMatch( 'medium' ); const { diff --git a/packages/block-editor/src/components/block-patterns-list/index.js b/packages/block-editor/src/components/block-patterns-list/index.js index 1601cbb728b57..b3169619f0d70 100644 --- a/packages/block-editor/src/components/block-patterns-list/index.js +++ b/packages/block-editor/src/components/block-patterns-list/index.js @@ -1,8 +1,6 @@ /** * WordPress dependencies */ -import { useMemo } from '@wordpress/element'; -import { parse } from '@wordpress/blocks'; import { VisuallyHidden, __unstableComposite as Composite, @@ -11,16 +9,22 @@ import { } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ import BlockPreview from '../block-preview'; import InserterDraggableBlocks from '../inserter-draggable-blocks'; +import { store as blockEditorStore } from '../../store'; function BlockPattern( { isDraggable, pattern, onClick, composite } ) { - const { content, viewportWidth } = pattern; - const blocks = useMemo( () => parse( content ), [ content ] ); + const { name, viewportWidth } = pattern; + const { blocks } = useSelect( + ( select ) => + select( blockEditorStore ).__experimentalGetParsedPattern( name ), + [ name ] + ); const instanceId = useInstanceId( BlockPattern ); const descriptionId = `block-editor-block-patterns-list__item-description-${ instanceId }`; diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index 1c293fc15a2ed..dfec2f64fab28 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -24,10 +24,15 @@ export function BlockPreview( { __experimentalLive = false, __experimentalOnClick, } ) { - const settings = useSelect( + const originalSettings = useSelect( ( select ) => select( blockEditorStore ).getSettings(), [] ); + const settings = useMemo( () => { + const _settings = { ...originalSettings }; + _settings.__experimentalBlockPatterns = []; + return _settings; + }, [ originalSettings ] ); const renderedBlocks = useMemo( () => castArray( blocks ), [ blocks ] ); if ( ! blocks || blocks.length === 0 ) { return null; diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index fdb74c6bcfe83..d3dcb13a58314 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -15,7 +15,6 @@ import { filter, mapKeys, orderBy, - every, } from 'lodash'; import createSelector from 'rememo'; @@ -1792,13 +1791,18 @@ export const __experimentalGetAllowedBlocks = createSelector( ] ); -const __experimentalGetParsedPatterns = createSelector( - ( state ) => { +export const __experimentalGetParsedPattern = createSelector( + ( state, patternName ) => { const patterns = state.settings.__experimentalBlockPatterns; - return map( patterns, ( pattern ) => ( { + const pattern = patterns.find( ( { name } ) => name === patternName ); + if ( ! pattern ) { + return null; + } + + return { ...pattern, - contentBlocks: parse( pattern.content ), - } ) ); + blocks: parse( pattern.content ), + }; }, ( state ) => [ state.settings.__experimentalBlockPatterns ] ); @@ -1813,17 +1817,19 @@ const __experimentalGetParsedPatterns = createSelector( */ export const __experimentalGetAllowedPatterns = createSelector( ( state, rootClientId = null ) => { - const patterns = __experimentalGetParsedPatterns( state ); - + const patterns = state.settings.__experimentalBlockPatterns; if ( ! rootClientId ) { return patterns; } - const patternsAllowed = filter( patterns, ( { contentBlocks } ) => { - return every( contentBlocks, ( { name } ) => + const parsedPatterns = patterns.map( ( { name } ) => + __experimentalGetParsedPattern( state, name ) + ); + const patternsAllowed = filter( parsedPatterns, ( { blocks } ) => + blocks.every( ( { name } ) => canInsertBlockType( state, name, rootClientId ) - ); - } ); + ) + ); return patternsAllowed; }, diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 817b1ffc31990..167c830f51036 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -3377,10 +3377,12 @@ describe( 'selectors', () => { settings: { __experimentalBlockPatterns: [ { + name: 'pattern-a', title: 'pattern with a', content: ``, }, { + name: 'pattern-b', title: 'pattern with b', content: '', @@ -3411,10 +3413,12 @@ describe( 'selectors', () => { settings: { __experimentalBlockPatterns: [ { + name: 'pattern-a', title: 'pattern a', scope: { block: [ 'test/block-a' ] }, }, { + name: 'pattern-b', title: 'pattern b', scope: { block: [ 'test/block-b' ] }, }, diff --git a/packages/block-editor/src/utils/pre-parse-patterns.js b/packages/block-editor/src/utils/pre-parse-patterns.js new file mode 100644 index 0000000000000..c5e1d5ad41ac5 --- /dev/null +++ b/packages/block-editor/src/utils/pre-parse-patterns.js @@ -0,0 +1,64 @@ +/** + * WordPress dependencies + */ +import { useSelect, select } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../store'; + +const requestIdleCallback = ( () => { + if ( typeof window === 'undefined' ) { + return ( callback ) => { + setTimeout( () => callback( Date.now() ), 0 ); + }; + } + + return window.requestIdleCallback || window.requestAnimationFrame; +} )(); + +const cancelIdleCallback = ( () => { + if ( typeof window === 'undefined' ) { + return clearTimeout; + } + + return window.cancelIdleCallback || window.cancelAnimationFrame; +} )(); + +export function usePreParsePatterns() { + const patterns = useSelect( + ( _select ) => + _select( blockEditorStore ).getSettings() + .__experimentalBlockPatterns, + [] + ); + + useEffect( () => { + if ( ! patterns?.length ) { + return; + } + + let handle; + let index = -1; + + const callback = () => { + index++; + if ( index >= patterns.length ) { + return; + } + + select( blockEditorStore ).__experimentalGetParsedPattern( + patterns[ index ].name + ); + + handle = requestIdleCallback( callback ); + }; + + handle = requestIdleCallback( callback ); + return () => cancelIdleCallback( handle ); + }, [ patterns ] ); + + return null; +} diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index 8fa7df26eb4f2..3f0fa4d1105c7 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -82,10 +82,6 @@ function Editor( { const isFSETheme = getEditorSettings().isFSETheme; const isViewable = getPostType( postType )?.viewable ?? false; - // Prefetch and parse patterns. This ensures patterns are loaded and parsed when - // the editor is loaded rather than degrading the performance of the inserter. - select( 'core/block-editor' ).__experimentalGetAllowedPatterns(); - return { hasFixedToolbar: isFeatureActive( 'fixedToolbar' ) || diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 9a7255a1d2b25..75fa489bec299 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -65,10 +65,6 @@ function Editor( { initialSettings } ) { const postType = getEditedPostType(); const postId = getEditedPostId(); - // Prefetch and parse patterns. This ensures patterns are loaded and parsed when - // the editor is loaded rather than degrading the performance of the inserter. - select( 'core/block-editor' ).__experimentalGetAllowedPatterns(); - // The currently selected entity to display. Typically template or template part. return { isInserterOpen: isInserterOpened(),