Universal blocks and block hydration #38224
Replies: 9 comments 22 replies
-
I'd like to note another approach I have considered but ultimately decided against, because I've stumbled across a PR to Jetpack where it was already tried 🙂. The idea is to prerender React components to html templates at build time using React's // This runs at build time. In the actual code we wouldn't save the
// file manually but rather let webpack generate it for us.
const markup = renderToStaticMarkup(
<UpgradeNudge planName="#planName#" upgradeUrl="#upgradeUrl#" />
);
saveTemplate(markup, "_inc/blocks/upgrade.html"); // This runs on the server at request time.
// The actual code is a little more complicated to also handle i18n and some edge cases.
ob_start();
include JETPACK__PLUGIN_DIR . "_inc/blocks/upgrade.html";
$markup = ob_get_clean();
foreach ( $props as $key => $value ) {
$markup = str_replace(
"#$key#", // <---- like `planName` in the code snippet above
$value,
$markup
);
}
echo $markup; However, this approach would only work for very simple blocks where the props do not determine the structure of the final HTML. If you have a component like this: const DogsList = ({ dogs }) => (
<ul>
{dogs.map(({ name }) => (
<li>
{name} {name === "Max" ? "is NOT a good dog." : "is a good dog!"}
</li>
))}
</ul>
); this approach would not work because you cannot know before request-time how many dogs there are and if In order to be able to generate a template that could handle such components we would need a full-blown compiler that could turn the |
Beta Was this translation helpful? Give feedback.
-
The proposal in OP (and the related demo) is one specific way of attacking the problem of block hydration. I'd like to step back and note some more abstract steps or "building blocks" that I think going to be necessary for a full solution. This is a non-exhaustive list, of course 🙂
|
Beta Was this translation helpful? Give feedback.
-
@michalczaplinski to your knowledge have any of these projects defined a block AST? |
Beta Was this translation helpful? Give feedback.
-
This is my summary of the problem to see if I understand it correctly. The power of JSX is that it is just JavaScript after transpiling. Though, this makes using it as a templating language for PHP very problematic because it practically requires transpiling JS to PHP. Creating a new templating language or using an existing one would complicate the ecosystem of React based blocks. The common target is serialized blocks. Server rendering is required to generate markup for good SEO and quickly displaying properly formatted content on initial page load. The interactively has to be able to hydrate from this markup. The solution needs to render in PHP, hydrate in JS, and remain compatible with the React block ecosystem. |
Beta Was this translation helpful? Give feedback.
-
My thoughts on the problem. The concept
The process
Pros
Cons
The questions this raisesCould the Is loading React on the frontend an option? Revision
The |
Beta Was this translation helpful? Give feedback.
-
Correct me if I'm wrong. But I haven't seen a detailed Make blog post about this issue. Or detailed explanations of the proposed solutions. Do you think there is a more discoverable way to post this, rather than it being mostly contained in GitHub Issues and Discussions? |
Beta Was this translation helpful? Give feedback.
-
Revision The solution needs to render in PHP, remain modifiable by WordPress filters on the server, hydrate in JS, and remain compatible with the React block ecosystem. |
Beta Was this translation helpful? Give feedback.
-
Functional views are not a good fit for the use case of content that can be modified by a filter. React is the opposite concept. When the same things go in, same thing comes out. The system is broken if the output of the function can be modified. The same inputs no longer provide the same output.
I missed that on the first read through. I think I get it now. |
Beta Was this translation helpful? Give feedback.
-
It looks like people keep (re)discovering this discussion, so I'd like to clarify how my thinking about Universal blocks has evolved over the last two years: A group of contributors has been working on the Interactivity API over the last year which is available in Wordpress 6.5. This API is a direct result of our thinking about how to make it easier to create interactive blocks (Challange 2. in the OP) This API is available today, is compatible with all PHP filters, and works great with server-rendered content, unlike all of the React solutions suggested in this discussion. That said, there is still a possible path to a universal block compiler in the long term. Such a compiler could, in principle, take as input something like
However, no team is working on this at the moment and it might never happen. It largely depends on the adoption of the Interactivity API and the community feedback. |
Beta Was this translation helpful? Give feedback.
-
The challenges
1. Duplicate logic in PHP and JS when writing dynamic blocks (universal blocks)
When writing dynamic blocks, there is often necessary duplication of logic between the
edit
function of the block (written in javascript) and the function called by the render_callback on the server side, typically located in theindex.php
file of the block. Post Date core block is a simple example of a block with such duplication.Unsurprisingly, everyone agrees that this duplication is bad. The users would like to see some API which would allow them to write a single template that could be rendered both on the client and the server. I'll refer to this idea as "universal blocks" throughout the post 🙂
There has already been a fair bit of discussion about this problem in many other places:
block.json
file. AFAIK, this is also where he coined the term "universal blocks"! 😃2. Make it easier to create interactive blocks (block hydration)
Users would like to create blocks that use JS to create interactive experiences.
Woocommerce has been able to work with existing constraints to create interactive blocks that are being rendered only on the client using React, lazy-loading and some clever techniques. This is a great solution, but we should find a way to support their use case with an even better out-of-the-box experience.
Finding a solution to this challenge should involve figuring out how to hydrate the blocks generated in PHP. Why? For many blocks, only rendering on the client is not a good solution because it makes for a less-than-ideal UX (you have to wait for all client-side JS to load in order to render your block). So, ideally, we'd like to render the content on the server and then hydrate it a.k.a. "make interactive" using JS on the client. How? Well... that's the challenge 🙂.
Define dynamic blocks using a high-level block format and compile them to a format that Gutenberg understands
This is a solution proposed in blocky. I think this idea is worth exploring more. To recap: Riad proposed using plain JSON to define blocks, but we could push this idea even further and use a more sophisticated block format and a compiler!
But first, we gotta ask ourselves: "What are our constraints"?
node
on the server. We can only assume that the block creator has node installed and can use it only at build time (when authoring blocks).Given those constraints, I've also briefly considered leveraging WebAssembly to compile the QuickJS javascript engine to WASM and run it using wasmer-php runtime. However, this turns out to also require a custom PHP extension so is a non-starter at the moment.
The demo
https://github.com/michalczaplinski/hydration-mitosis-demo
I've created a demo here which allows you to create Gutenberg blocks using Mitosis format and compile them to both a Liquid template AND a React component that hydrates the server-rendered HTML.
There is more information in the repo, including what I'd consider are limitations and weaknesses of this approach 🙂
Other ideas
There have been other ideas floated around with respect to "universal" blocks so I'd like to give them credit here as well.
Render blocks on the client only
I think this idea is probably a non-starter for SEO, performance and UX reasons, but I include it here for completeness as it does technically solve some challenges. It was proposed here.
Define blocks in PHP
This is the approach proposed by @markjaquith in https://github.com/markjaquith/BlockStub. I think this is an interesting idea, especially as it might be more approachable to PHP developers over more JS-focused approaches, but I believe it might be hard to scale beyond a "hello world" - I feel that writing JS inside of PHP might be the worst of both worlds though... You still have to write some JS for the interactive parts of your app but you forgo all the DX goodness that come with modern JS dev environment and just concatenate strings in PHP. On the plus side though, this approach would require no build step.
Conclusion and open questions
We are still very early with ideas for "universal blocks" and block hydration so I would 💕 some feedback here and any ideas that might have occurred to anyone. I also want to stress that the idea to use Mitosis is only an experiment and it is meant to serve as an example of the kind of direction that we could take here and NOT a suggestion to adopt it inside Gutenberg.
Those are some of the questions that I still have lingering in my mind:
Beta Was this translation helpful? Give feedback.
All reactions