Gutenberg: Extensibility: Introduce `BlockSave` which would work similar to `BlockEdit`

Created on 4 Dec 2017  路  26Comments  路  Source: WordPress/gutenberg

Issue Overview


This issue explores ways to give more flexibility for extending core/* block types.
Context: https://wordpress.org/gutenberg/handbook/extensibility/

The current set of available hooks is very limited and is not enough for providing _meaningful_ customizations.

Specifically:

  1. I see no way to add more raw attributes (the ones that are _NOT_ computed via HTML selectors). getSaveContent.extraProps provides no help here. It only gives us a way to add inline HTML attributes to the final block output.
  2. We need BlockSave which would be similar to BlockEdit hook. Ideally, BlockSave should be called in getSaveContent which would properly allow us to proxy the final result through a component (via HOCs/render props) that will modify the resulting HTML.

https://github.com/WordPress/gutenberg/blob/ff9c02a755b14d4a61b9465cf8f6345ddf9f4302/blocks/api/serializer.js#L37-L66

Probably more once I get more deeply into that...

Related Issues and/or PRs

2474 #3318


I understand that giving such powerful things into the users hands is quite dangerous. The posts may break in case the third-party plugin gets disabled in many unexpected ways (a missing BlockSave, in that particular example, would potentially blow up the attributes parsing with selector | src sources).

But, I really believe that there is a lot more to be done in that area that waits to be explored.

[Feature] Blocks [Feature] Extensibility

All 26 comments

I see no way to add more raw attributes (the ones that are NOT computed via HTML selectors). getSaveContent.extraProps provides no help here. It only gives us a way to add inline HTML attributes to the final block output.

the getSaveContent.extraProps is the equivalent of BlockSave right now.
Can you clarify (maybe give an example, what attributes you want to add to blocks...), I didn't understand this point. What are "raw attributes not computed via html selectors"?

I was looking to add some values into attrs (which are parseable from the PHP side, without mutating the block innerHTML) and with getSaveContent.extraProps there's no way to do so.


What are "raw attributes not computed via html selectors"?

Values that go into attrs of a block -- color and textColor in that case, without ever touching the innerHTML:

<!-- wp:button {"color":"#eee","textColor":"#0693e3"} -->
<div class="wp-block-button alignnone" style="background-color:#eee" border_type="dashed"><a style="color:#0693e3">heyasda</a></div>
<!-- /wp:button -->

@andreamiddleton These attributes are not saved or extracted from the innerHTML, you can achieve what you want by hooking into registerBlockType and adding a new attribute. Here's an example https://github.com/WordPress/gutenberg/blob/master/blocks/hooks/custom-class-name.js#L86

Oh, that's very cool! I was really close to that part of the code, thanks.

This kinda makes the BlockSave useless. But it may be useful for other, more advanced, use cases which I can't think of right now.

Getting back to it, I was forced to do it that way:

let settings = wp.blocks.getBlockType('core/button')
wp.blocks.unregisterBlockType('core/button')
wp.blocks.registerBlockType('core/button', {
    ...oldSettings,
    attributes: {
        ...settings.attributes,
        heya: {
            type: 'string',
            default: '#cf2e2e',
        },
    },
})

The reason for that hack was this: wp-blocks script does two things:

  1. Registers the hooks (in wp.hooks.*) to be received
  2. Calls each wp.blocks.registerBlockType method of core's blocks _immediately_, in a _sync_ way

This in itself is not a problem. The problem arise when third-party plugins try to hook into registerBlockType. They literally have no time to do their transformations because action 1) and 2) happen one after another, with no way to interfere between them.


A possible solution:

Split the wp-blocks script into wp-register-blocks-hooks and wp-blocks and allow third-party scripts to perform actions right _after_ wp-register-blocks-hooks but before wp-blocks. Otherwise everyone will be forced to re-register blocks, which may become not a very cheap operation.

Good point @andreamiddleton I'm reopening and renaming the issue. We should try to address this issue.

cc @gziolo @aduth

Alternatively we change blocks hooks to operate on the global hooks instance, then simply encourage plugin authors to register their hooks prior to the wp-blocks handle being loaded.

Related: https://github.com/WordPress/gutenberg/pull/3493#discussion_r153968061

Yes, I have tried that option. Of course it didn't worked out for me they way one would expect. Frankly, either one will work for me, whichever you get to decide to go with.

Alternatively we change blocks hooks to operate on the global hooks instance, then simply encourage plugin authors to register their hooks prior to the wp-blocks handle being loaded.

It's going to be partially updated with #3827. wp-hooks is always registered before wp-blocks, so it should be possible. This change enforces plugin developer to always register a hook before the modified module is going to to be imported.

Correct me if I'm wrong, but it's also impossible to hook to the blocks.BlockEdit hook, after the last update (1.9)

@kmgalanakis kinda so. It works for blocks that are added _after_ the initial load. Those that happened to be in the editor at the first time just got rendered without taking blocks.BlockEdit into account.

@andreiglingeanu I cannot get it working even for the blocks that are added after the initial load, unfortunately. Have you checked yourself with the latest master?

@andreiglingeanu @kmgalanakis I'm working on the PR which should allow to add a filter after BlockEdit got imported, but wasn't yet rendered. I hope that it will help to manage those filters and solve the issue with the enforced dependency of JS files.

block.BlockEdit should be fine now. I think @youknowriad is thinking how to solve the issue with blocks.registerBlockType :)

Confirmed, blocks.BlockEdit is just fine, blocks.registerBlockType is not

4428 adds another improvement for block.BlockEdit. It will allow to add/remove filters at any time in the Gutenberg's lifecycle and it will automatically re-render all related components :)

Just merged #4428 which should further improve using hooks with the core block. Hopefully, it won't be necessary to unregister them to add modifications from now on.

Sounds great!

@andreiglingeanu I renamed this issue because the original title in my opinion is no longer relevant to what is missing. Does it make sense?

Yes, makes total sense

It's interesting to me how blocks will behave once plugins that used to intercept the saving process would get disabled. Will those blocks just be marked as stale/broken?

There is an unknown type handler set in here: https://github.com/WordPress/gutenberg/blob/24cfb23b025ad3e44be542b1346f582a8f29b3e5/blocks/library/index.js#L90. So it will be treated as a freeform block - the one that handles also old posts created with the Classic editor.

It applies to both edit and save. They will still work but the block will be treated as HTML code.

@aduth, did we introduce something similar with your change to how save is being processed? I think blocks.getSaveElement plays a similar role to what we can do with blocks.BlockEdit.

blocks.getSaveElement: A filter that applies to the result of a block's save function. This filter is used to replace or extend the element, for example using wp.element.cloneElement to modify the element's props or replace its children, or returning an entirely new element.

It is a bit different than:

blocks.BlockEdit: Used to modify the block's edit component. It receives the original block edit component and returns a new wrapped component.

but this is done this way by design to provide a better automation for the saved content.

Link to full docs for reference: https://github.com/WordPress/gutenberg/blob/master/docs/extensibility.md#modifying-blocks-experimental.

Correct, I should have been more explicit with linking in #4786, but the new blocks.getSaveElement adds a higher level of control over the save output than just injecting props. It's used for backwards-compatibility for the new RawHTML component.

@andreiglingeanu, I think we addressed all concerns with the linked PRs. Let's close this one. If there are further issues discovered let's create separate actionable issues. Thanks for your feedback and all your help 馃挴

It's great, thank you for taking it in consideration! Will definitely try everything in practice and see how it plays out, thanks again.

Was this page helpful?
0 / 5 - 0 ratings