-
Notifications
You must be signed in to change notification settings - Fork 17
Description
Problem
Currently, WPGraphQL for Content Blocks calls render_block() repeatedly and unnecessarily:
- Whenever the
renderedHtmlfor a block is returned, (even if it was rendered by a parentBlock):wp-graphql-content-blocks/includes/Type/InterfaceType/EditorBlockInterface.php
Lines 134 to 139 in b42afd7
'renderedHtml' => [ 'type' => 'String', 'description' => __( 'The rendered HTML for the block', 'wp-graphql-content-blocks' ), 'resolve' => static function ( $block ) { return render_block( $block ); }, - Whenever an
anchorfield is resolved:(related Implement Block Supports as GraphQL Interfaces #354 )wp-graphql-content-blocks/includes/Field/BlockSupports/Anchor.php
Lines 26 to 30 in b42afd7
'anchor' => [ 'type' => 'string', 'description' => __( 'The anchor field for the block.', 'wp-graphql-content-blocks' ), 'resolve' => static function ( $block ) { $rendered_block = wp_unslash( render_block( $block ) ); - Whenever each and every Block Attribute attempts to resolve:
wp-graphql-content-blocks/includes/Blocks/Block.php
Lines 255 to 280 in b42afd7
foreach ( $block_attributes as $attribute_name => $attribute_config ) { $graphql_type = $this->get_attribute_type( $attribute_name, $attribute_config, $prefix ); if ( empty( $graphql_type ) ) { continue; } // Create the field config. $fields[ Utils::format_field_name( $attribute_name ) ] = [ 'type' => $graphql_type, 'description' => sprintf( // translators: %1$s is the attribute name, %2$s is the block name. __( 'The "%1$s" field on the "%2$s" block or block attributes', 'wp-graphql-content-blocks' ), $attribute_name, $prefix ), 'resolve' => function ( $block ) use ( $attribute_name, $attribute_config ) { $config = [ $attribute_name => $attribute_config, ]; $result = $this->resolve_block_attributes_recursive( $block['attrs'], wp_unslash( render_block( $block ) ), $config ); return $result[ $attribute_name ]; }, ]; }//end foreach
This results in various issues and incompatibilities, but the biggest one is performance as the number of calls increases exponentially depending on the number of nested blocks and individual fields requested by graphql.
Proposed Solution
We should limit our calls to render_block() and reuse the results once we have them.
There are multiple different ways we can reduce these calls, and they don't all need to be breaking, or done together:
-
Long term, the best approach would be to use a WPGraphQL
Modelinstead of resolving on a field-level, so results can be cached and reused.- See an outdated experimentation at feat!: implement block model rtCamp/wp-graphql-content-blocks#31
-
(On a model or in args), we can cache a copy of the rendered block the first time we use it.
- Prior art from wp-rest-blocks: https://github.com/spacedmonkey/wp-rest-blocks/blob/bafb87a86d723b5ec54440e6fd6feb7fd94d6879/src/data.php#L72
-
We can implement actual caching (with wp_cache_set() and get() ) so we dont need to keep reparsing/rerendering the same thing.
Details
I'm highlighting some findings from a Performance Audit conducted by my friend and colleague @aryanjasala . I'll be sharing the link in the wp-graphql maintainer's discord channel - it's too big for a GH issue.
When comparing FSE on Traditional WP to WPGraphQL Content Blocks on a basic page with patterns.
-
Traditional WP:
- Stack trace: https://gist.github.com/aryanjasala/e812efb81c446185564a9557a2e87ffc
- XHProf profile: https://gist.github.com/aryanjasala/e6c671e23d983c547521fa472462c980
render_block()calls: 1 + 5- Inclusive Wall Time: 1,99,227µs (0.19s)
-
WPGraphQL Content Blocks
- Stack Trace: https://gist.github.com/aryanjasala/8e2e2a1d782b3a254ea1ca811acdcfd6
- XHProf profile: https://gist.github.com/aryanjasala/817853950fd622581ab1afe7674fb939
render_block()calls: 62- Inclusive Wall Time: 1,003,537 µs (1.0035s)
Keep in mind that traditional WordPress is actually doing even more heavy lifting than nodeByUri(...) { editorBlocks {...} }, since it needs to parse and render the entire template.
Test Data
Post content 👇
```htmlI’m Leia Acosta, a passionate photographer who finds inspiration in capturing the fleeting beauty of life.
GraphQL Query 👇
query GetContentNode($uri: String!) {
nodeByUri(uri: $uri) {
__typename
... on Page {
id
title
editorBlocks {
clientId
parentClientId
renderedHtml
type
... on CoreAudio {
attributes {
autoplay
caption
loop
preload
src
style
}
}
... on CoreButton {
attributes {
buttonType: type
cssClassName
linkClassName
linkTarget
rel
style
tagName
text
title
url
}
}
... on CoreButtons {
attributes {
cssClassName
style
}
}
... on CoreCode {
attributes {
style
content
cssClassName
}
}
... on CoreColumn {
attributes {
cssClassName
style
width
}
}
... on CoreColumns {
attributes {
cssClassName
style
}
}
... on CoreCover {
attributes {
alt
backgroundType
contentPosition
customGradient
customOverlayColor
dimRatio
focalPoint
gradient
hasParallax
isDark
isRepeated
minHeight
minHeightUnit
overlayColor
style
tagName
url
useFeaturedImage
}
}
... on CoreDetails {
attributes {
showContent
style
summary
}
}
... on CoreFile {
attributes {
displayPreview
downloadButtonText
fileId
fileName
href
previewHeight
showDownloadButton
style
textLinkHref
textLinkTarget
}
}
... on CoreFreeform {
attributes {
content
}
}
... on CoreGallery {
attributes {
caption
style
}
}
... on CoreGroup {
attributes {
style
tagName
}
}
... on CoreHeading {
attributes {
content
cssClassName
level
style
}
}
... on CoreHtml {
attributes {
content
}
}
... on CoreImage {
attributes {
alt
aspectRatio
caption
href
linkClass
linkTarget
rel
scale
style
title
url
imageHeight: height
width
sizeSlug
lightbox
}
}
... on CoreList {
attributes {
cssClassName
ordered
reversed
start
style
type
}
}
... on CoreListItem {
attributes {
content
style
}
}
... on CoreMediaText {
attributes {
className
focalPoint
href
imageFill
linkClass
linkTarget
mediaAlt
mediaId
mediaPosition
mediaSizeSlug
mediaType
mediaUrl
mediaWidth
rel
style
}
}
... on CoreParagraph {
attributes {
backgroundColor
content
cssClassName
direction
fontFamily
fontSize
style
textColor
}
}
... on CorePostContent {
renderedHtml
}
... on CorePreformatted {
attributes {
content
style
}
}
... on CorePullquote {
attributes {
citation
style
textAlign
pullquoteValue: value
}
}
... on CoreQuote {
attributes {
citation
cssClassName
style
value
}
}
... on CoreSeparator {
attributes {
cssClassName
style
}
}
... on CoreSpacer {
attributes {
height
style
width
}
}
... on CoreTemplatePart {
renderedHtml
attributes {
className
slug
templatePartTagName: tagName
theme
}
}
... on CoreVerse {
attributes {
content
style
}
}
... on CoreVideo {
attributes {
autoplay
caption
controls
loop
muted
playsInline
poster
videoPreload: preload
src
style
tracks
}
}
}
}
}
}Environment
- WPGraphQL: 2.1.1
- WPGraphQL Content Blocks: 4.8.2
- WordPress Core: 6.7.2
- WP_ENVIRONMENT_TYPE:
production - PHP:8.2.27
- Benchmarked using| LocalWP + XHProf + XHGUI
Metadata
Metadata
Assignees
Labels
Type
Projects
Status


