-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Block API: Introduce block definitions and implementations #6733
Conversation
So not including the source type? Ideally these source types are environment-agnostic, which is partly why I've discouraged using those like the |
So do you see parsing moving to block itself, returning some canonical object form? |
@aduth No, I see parsing/serializing as an implementation detail, For me the source of truth is the block object regardless of the way it's serialized (html, markdown, native UI...). This is why I think it's shouldn't be in the block "definition". Granted that right now we save a serialization but I see it as just a backwards compatibility concern instead of a "conceptual" choice. The conceptual choice is that a post content is an array of blocks, each block is an object with attributes, each attribute has a type. |
This seems like a point we should clarify, as it's been an operating assumption for me that HTML is the source of truth:
https://make.wordpress.org/core/2017/01/17/editor-technical-overview/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The makes a lot of sense to me.
What about the icon? It's considered as implementation for now mainly because we allow svg elements which can't be defined server-side. An option would be to transform it to svg string to allow it to be moved to the definition of the block.
We could have icon
supported in both places but only SVGs supported in the implementation.
Alternatively we could move away from inline SVGs and have a seperate iconURL
property which points to an external SVG or image.
What about the blocks.registerBlockType filter? The most logical thing to do is to split it to two filters: addBlockType (server-side) and implementBlockType (client-side)
👍
That said, all of the markup-based sources rely on some ability to parse HTML, which is easier in the browser than it is in other contexts.
We could always port hpq to PHP. I've written something similar to this in the past using DOMDocument.
blocks/store/actions.js
Outdated
@@ -17,6 +17,20 @@ export function addBlockTypes( blockTypes ) { | |||
}; | |||
} | |||
|
|||
/** | |||
* Returns an action object used in signalling that block types have been added. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs updating.
|
||
return { | ||
...blockTypeDefinition, | ||
...blockTypeImplementation, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sense to me that the implementation can override attributes that are in the definition.
What about the icon? It's considered as implementation for now mainly because we allow svg elements which can't be defined server-side. An option would be to transform it to svg string to allow it to be moved to the definition of the block.
We could have icon
supported both in addBlockType
and implementBlockType
. SVGs would only be supported in implementBlockType
. The attribute specified in the implementation takes priority, as above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sense to me that the implementation can override attributes that are in the definition.
It's not quite as obvious to me. What's the use case?
blocks/store/selectors.js
Outdated
...blockTypeDefinition, | ||
...blockTypeImplementation, | ||
attributes: mapValues( blockTypeDefinition.attributes, ( attribute, key ) => { | ||
const implementationAttribute = blockTypeImplementation.attributes ? blockTypeImplementation.attributes[ key ] : {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using get( blockTypeImplementation.attributes, [ key ], {} )
here might be easier to understand.
We had a discussion with @aduth about this and one of the reasons I prefer avoiding the "source" definition to be done server-side is that the parsing (source definition) goes together with serializing (the So if we move the attributes parsing server-side, we'd have to move the For these reasons, I prefer to consider the blocks as the source of truth conceptually and leave parsing/serializing as implementation details. |
Let me answer your questions based on my reflections after explorations done in #5802 and #5652.
I would also include
I think it is fine to allow strings on the server and treat svg as an advanced implementation detail. I wouldn't spend too much time on it, but it would be handy to have a fallback defined on the server so we could lazy load definition of the block on the client.
Yes, we should have two types of hooks, one on the server and one on the client. I did that in #5802. My another related comment from a different PR: To satisfy a few different requirements we should expose a hook on PHP side to make it possible to perform some operations on attributes and other block properties. This was also recognized as a blocker for blocks to use supports align as discussed in here: #5099 (comment). Long story short - we need to have one place where properties get updated to avoid code duplication and confusion. That's why it seems like we should explicitly pick what happens on the server and what on the client. |
2412caf
to
40a497a
Compare
So it looks like this PR already meets all/most of the feedback here. I took a look at #5802 and it looks like it could be considered as a follow-up to the current PR.
So, I'd like some feedback from @mtias here before we proceed (Whether you agree or not with this #6733 (comment) ) |
core-blocks/video/test/index.js
Outdated
import { blockEditRender } from '../../test/helpers'; | ||
|
||
describe( 'core/video', () => { | ||
test( 'block edit matches snapshot', () => { | ||
const wrapper = blockEditRender( name, settings ); | ||
const wrapper = blockEditRender( name, { ...definition, ...implementation } ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need type
and default
for attributes
in tests?
It won't be provided because implementation
doesn't contain it.
It's a shallow merge.
40a497a
to
45445b6
Compare
45445b6
to
44ab993
Compare
I rebased this one. As discussed in WCEU, let's move this forward to move things to the server as a follow up. |
@@ -168,7 +168,20 @@ export function registerBlockType( name, settings ) { | |||
set( settings, [ 'supports', 'multiple' ], ! settings.useOnce ); | |||
} | |||
|
|||
dispatch( 'core/blocks' ).addBlockTypes( settings ); | |||
const implementationOnlyAttributes = [ 'transforms', 'edit', 'save', 'icon', 'getEditWrapperProps' ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deprecated
is missing in here. Is it intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it's not. I intentionally avoided any deprecations for now. The idea is to do it only once, when we have all the bits in (the server-side part essentially)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, it doesn't matter in this PR, but definitely something, we should keep in mind. Should we add todo comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's not necessary because that's the first thing we should do when we move registration to the server. Tell people to register their blocks on the server.
].forEach( ( { name, settings } ) => { | ||
registerBlockType( name, settings ); | ||
} ); | ||
|
||
[ video ].forEach( ( { name, definition, implementation } ) => { | ||
dispatch( 'core/blocks' ).addBlockTypes( { name, ...definition } ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is not acceptable solution because it will break all plugins that depend on the following filter:
settings = applyFilters( 'blocks.registerBlockType', settings, name );
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition this code bypass all validations we have in registerBlockType
function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good points.
The filter
How to approach this? should we just leave the video block as is for now and refactor all the blocks at once once we create the alternative filters and move things to the server? I'm trying to avoid telling people to rewrite their code twice and only introduce deprecations once we have everything.
In addition this code bypass all validations we have in registerBlockType function.
I guess we should split validation between the two actions. I'll give it a shot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two times yes :)
It was good for illustration how this can work, but we still need those wrapping API functions to make sure everything is valid.
I'm not really on board with where this is going on attributes fragmentation. If the reason is that parsing and serialization should be kept together, what sense do the attributes types have in the server definition? What about comment-serialized attributes? It seems odd to me we have some idea of what attributes exist and where to find them from the server, but that otherwise we distinguish the implementation to have its own behaviors. Would we want separate implementations, and could each implementation have its own sourcing / serialization behaviors? Is that even desirable? |
I don't know if that's desirable but if you see the block JSON as the source of truth (instead of the HTML), attributes have a meaning, they are what defines the block content. And just having a type and a default value for each attribute is what we need in that case. In the https://wordpress.org/plugins/averroes/ plugin I have an example of an alternative implementation where we consider the JSON as the source of truth with custom sourcing / serialization behaviors (from markdown to JSON, from JSON to markdown). |
#8046 introduces temporary helper |
since the very beginning of the decision to serialize Gutenbergs posts into HTML it has been a truth that "the block doesn't exist outside of memory where the block code is executing." the "block" is defined by code that interprets a bag of attributes. that we serialize into our blocks cannot exist without the implementation. they become void of meaning. potentially we can escape this by storing all attributes in the comment attributes thus remove the need to source them from I probably missed the relevant discussion, but what are we solving by making the server more aware of blocks? what would be lacking if we sourced all the attributes and provided them to the dynamic block renderers? what are we trying to accomplish by making this change? It seems like we are making a fundamental change with the patch. |
* | ||
* @return {Object} Updated state. | ||
*/ | ||
export function implementations( state = {}, action ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't necessarily oppose, but to me it was unexpected to see this as a separate branch on the client. Does the client really care about which bits of information are part of the definition and which of the implementation? As long as we have distinct actions, we can treat the bits differently as they come in if need be, but then we store everything in a common place for a single block type.
After the discussions in #core-editor we're going to take another approach to this. Having a |
In order to move forward with the server-side awareness of blocks we need to split the block registration into two steps:
1- Defining the existence of the block: Which means specifying its generic properties (name, title, category, description, attribute types)
2- Implementing block types: which means providing
edit/save/parse
definitions to allow manipulating the block type in the web editor. Other implementation could exist in separate contexts.This PR is an exploration to split the block settings between two calls:
dispatch( 'core/blocks' ).addBlockType( { name, ...definition } )
(the generic part that can be moved server-side later on.dispatch( 'core/blocks' ).implementBlockType( { name, edit, save, transforms } )
It leverages the selectors "abstraction" to avoid breaking changes.
This raises several questions:
What belongs to the definition and what is implementation? I'm considering the block name, title, category, description and attributes types (not the way we parse and serialize them) as the canonical definition of the block.
What about the
icon
? It's considered as implementation for now mainly because we allow svg elements which can't be defined server-side. An option would be to transform it to svg string to allow it to be moved to the definition of the block.What about the
blocks.registerBlockType
filter? The most logical thing to do is to split it to two filters:addBlockType
(server-side) andimplementBlockType
(client-side)At the moment, this raises more questions than it solves, but let's discuss to find the best approach before introducing any deprecations.