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

Blocks: move bootstrapped block types to Redux state #53807

Merged
merged 9 commits into from
Aug 30, 2023
6 changes: 5 additions & 1 deletion docs/reference-guides/data/data-core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,10 @@ The actions in this package shouldn't be used directly. Instead, use the functio

<!-- START TOKEN(Autogenerated actions|../../../packages/blocks/src/store/actions.js) -->

Nothing to document.
### reapplyBlockTypeFilters

Signals that all block types should be computed again. It uses stored unprocessed block types and all the most recent list of registered filters.

It addresses the issue where third party block filters get registered after third party blocks. A sample sequence: 1. Filter A. 2. Block B. 3. Block C. 4. Filter D. 5. Filter E. 6. Block F. 7. Filter G. In this scenario some filters would not get applied for all blocks because they are registered too late.

<!-- END TOKEN(Autogenerated actions|../../../packages/blocks/src/store/actions.js) -->
98 changes: 11 additions & 87 deletions packages/blocks/src/api/registration.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
/* eslint no-console: [ 'error', { allow: [ 'error', 'warn' ] } ] */

/**
* External dependencies
*/
import { camelCase } from 'change-case';

/**
* WordPress dependencies
*/
Expand All @@ -15,8 +10,8 @@ import { _x } from '@wordpress/i18n';
* Internal dependencies
*/
import i18nBlockSchema from './i18n-block.json';
import { BLOCK_ICON_DEFAULT } from './constants';
import { store as blocksStore } from '../store';
import { unlock } from '../lock-unlock';

/**
* An icon type definition. One of a Dashicon slug, an element,
Expand Down Expand Up @@ -129,8 +124,6 @@ import { store as blocksStore } from '../store';
* then no preview is shown.
*/

const serverSideBlockDefinitions = {};

function isObject( object ) {
return object !== null && typeof object === 'object';
}
Expand All @@ -142,65 +135,9 @@ function isObject( object ) {
*/
// eslint-disable-next-line camelcase
export function unstable__bootstrapServerSideBlockDefinitions( definitions ) {
for ( const blockName of Object.keys( definitions ) ) {
// Don't overwrite if already set. It covers the case when metadata
// was initialized from the server.
if ( serverSideBlockDefinitions[ blockName ] ) {
// We still need to polyfill `apiVersion` for WordPress version
// lower than 5.7. If it isn't present in the definition shared
// from the server, we try to fallback to the definition passed.
// @see https://github.com/WordPress/gutenberg/pull/29279
if (
serverSideBlockDefinitions[ blockName ].apiVersion ===
undefined &&
definitions[ blockName ].apiVersion
) {
serverSideBlockDefinitions[ blockName ].apiVersion =
definitions[ blockName ].apiVersion;
}
// The `ancestor` prop is not included in the definitions shared
// from the server yet, so it needs to be polyfilled as well.
// @see https://github.com/WordPress/gutenberg/pull/39894
if (
serverSideBlockDefinitions[ blockName ].ancestor ===
undefined &&
definitions[ blockName ].ancestor
) {
serverSideBlockDefinitions[ blockName ].ancestor =
definitions[ blockName ].ancestor;
}
// The `selectors` prop is not yet included in the server provided
// definitions. Polyfill it as well. This can be removed when the
// minimum supported WordPress is >= 6.3.
if (
serverSideBlockDefinitions[ blockName ].selectors ===
undefined &&
definitions[ blockName ].selectors
) {
serverSideBlockDefinitions[ blockName ].selectors =
definitions[ blockName ].selectors;
}

if (
serverSideBlockDefinitions[ blockName ]
.__experimentalAutoInsert === undefined &&
definitions[ blockName ].__experimentalAutoInsert
) {
serverSideBlockDefinitions[
blockName
].__experimentalAutoInsert =
definitions[ blockName ].__experimentalAutoInsert;
}
continue;
}

serverSideBlockDefinitions[ blockName ] = Object.fromEntries(
Object.entries( definitions[ blockName ] )
.filter(
( [ , value ] ) => value !== null && value !== undefined
)
.map( ( [ key, value ] ) => [ camelCase( key ), value ] )
);
const { addBootstrappedBlockType } = unlock( dispatch( blocksStore ) );
for ( const [ name, blockType ] of Object.entries( definitions ) ) {
addBootstrappedBlockType( name, blockType );
}
}

Expand Down Expand Up @@ -302,29 +239,16 @@ export function registerBlockType( blockNameOrMetadata, settings ) {
return;
}

const { addBootstrappedBlockType, addUnprocessedBlockType } = unlock(
dispatch( blocksStore )
);

if ( isObject( blockNameOrMetadata ) ) {
unstable__bootstrapServerSideBlockDefinitions( {
[ name ]: getBlockSettingsFromMetadata( blockNameOrMetadata ),
} );
const metadata = getBlockSettingsFromMetadata( blockNameOrMetadata );
addBootstrappedBlockType( name, metadata );
}

const blockType = {
name,
icon: BLOCK_ICON_DEFAULT,
keywords: [],
attributes: {},
providesContext: {},
usesContext: [],
selectors: {},
supports: {},
styles: [],
variations: [],
save: () => null,
...serverSideBlockDefinitions?.[ name ],
...settings,
};

dispatch( blocksStore ).__experimentalRegisterBlockType( blockType );
addUnprocessedBlockType( name, settings );

return select( blocksStore ).getBlockType( name );
}
Expand Down
118 changes: 34 additions & 84 deletions packages/blocks/src/api/test/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
import { addFilter, removeAllFilters, removeFilter } from '@wordpress/hooks';
import { logged } from '@wordpress/deprecated';
import { select } from '@wordpress/data';
import { select, dispatch } from '@wordpress/data';

/**
* Internal dependencies
Expand Down Expand Up @@ -33,6 +33,7 @@ import {
import { BLOCK_ICON_DEFAULT, DEPRECATED_ENTRY_KEYS } from '../constants';
import { omit } from '../utils';
import { store as blocksStore } from '../../store';
import { unlock } from '../../lock-unlock';

const noop = () => {};

Expand All @@ -48,19 +49,14 @@ describe( 'blocks', () => {
title: 'block title',
};

beforeAll( () => {
// Initialize the block store.
require( '../../store' );
} );

afterEach( () => {
getBlockTypes().forEach( ( block ) => {
unregisterBlockType( block.name );
} );
const registeredNames = Object.keys(
unlock( select( blocksStore ) ).getUnprocessedBlockTypes()
);
dispatch( blocksStore ).removeBlockTypes( registeredNames );
setFreeformContentHandlerName( undefined );
setUnregisteredTypeHandlerName( undefined );
setDefaultBlockName( undefined );
unstable__bootstrapServerSideBlockDefinitions( {} );

// Reset deprecation logging to ensure we properly track warnings.
for ( const key in logged ) {
Expand Down Expand Up @@ -392,80 +388,6 @@ describe( 'blocks', () => {
} );
} );

// This test can be removed once the polyfill for apiVersion gets removed.
it( 'should apply apiVersion on the client when not set on the server', () => {
const blockName = 'core/test-block-back-compat';
unstable__bootstrapServerSideBlockDefinitions( {
[ blockName ]: {
category: 'widgets',
},
} );
unstable__bootstrapServerSideBlockDefinitions( {
[ blockName ]: {
apiVersion: 3,
category: 'ignored',
},
} );

const blockType = {
title: 'block title',
};
registerBlockType( blockName, blockType );
expect( getBlockType( blockName ) ).toEqual( {
apiVersion: 3,
name: blockName,
save: expect.any( Function ),
title: 'block title',
category: 'widgets',
icon: { src: BLOCK_ICON_DEFAULT },
attributes: {},
providesContext: {},
usesContext: [],
keywords: [],
selectors: {},
supports: {},
styles: [],
variations: [],
} );
} );

// This test can be removed once the polyfill for ancestor gets removed.
it( 'should apply ancestor on the client when not set on the server', () => {
const blockName = 'core/test-block-with-ancestor';
unstable__bootstrapServerSideBlockDefinitions( {
[ blockName ]: {
category: 'widgets',
},
} );
unstable__bootstrapServerSideBlockDefinitions( {
[ blockName ]: {
ancestor: 'core/test-block-ancestor',
category: 'ignored',
},
} );

const blockType = {
title: 'block title',
};
registerBlockType( blockName, blockType );
expect( getBlockType( blockName ) ).toEqual( {
ancestor: 'core/test-block-ancestor',
name: blockName,
save: expect.any( Function ),
title: 'block title',
category: 'widgets',
icon: { src: BLOCK_ICON_DEFAULT },
attributes: {},
providesContext: {},
usesContext: [],
keywords: [],
selectors: {},
supports: {},
styles: [],
variations: [],
} );
} );

// This can be removed once polyfill adding selectors has been removed.
it( 'should apply selectors on the client when not set on the server', () => {
const blockName = 'core/test-block-with-selectors';
Expand Down Expand Up @@ -920,6 +842,34 @@ describe( 'blocks', () => {
'Declaring non-string block descriptions is deprecated since version 6.2.'
);
} );

it( 're-applies block filters', () => {
// register block
registerBlockType( 'test/block', defaultBlockSettings );

// register a filter after registering a block
addFilter(
'blocks.registerBlockType',
'core/blocks/reapply',
( settings ) => ( {
...settings,
title: settings.title + ' filtered',
} )
);

// check that block type has unfiltered values
expect( getBlockType( 'test/block' ).title ).toBe(
'block title'
);

// reapply the block filters
dispatch( blocksStore ).reapplyBlockTypeFilters();

// check that block type has filtered values
expect( getBlockType( 'test/block' ).title ).toBe(
'block title filtered'
);
} );
} );

test( 'registers block from metadata', () => {
Expand Down
Loading