GithubHelp home page GithubHelp logo

wordpress / block-interactivity-experiments Goto Github PK

View Code? Open in Web Editor NEW
110.0 110.0 10.0 16.2 MB

⚠️ WARNING: This repository is no longer active. The development and discussions have been moved to the Gutenberg repository.

Home Page: https://github.com/WordPress/gutenberg/discussions/categories/interactivity-api

PHP 40.72% JavaScript 22.39% HTML 15.34% TypeScript 21.48% CSS 0.07%

block-interactivity-experiments's Introduction

<!DOCTYPE html>
<html lang="en">
<head>
	<meta name="viewport" content="width=device-width" />
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<title>WordPress &#8250; ReadMe</title>
	<link rel="stylesheet" href="wp-admin/css/install.css?ver=20100228" type="text/css" />
</head>
<body>
<h1 id="logo">
	<a href="https://wordpress.org/"><img alt="WordPress" src="wp-admin/images/wordpress-logo.png" /></a>
</h1>
<p style="text-align: center">Semantic Personal Publishing Platform</p>

<h2>First Things First</h2>
<p>Welcome. WordPress is a very special project to me. Every developer and contributor adds something unique to the mix, and together we create something beautiful that I am proud to be a part of. Thousands of hours have gone into WordPress, and we are dedicated to making it better every day. Thank you for making it part of your world.</p>
<p style="text-align: right">&#8212; Matt Mullenweg</p>

<h2>Installation: Famous 5-minute install</h2>
<ol>
	<li>Unzip the package in an empty directory and upload everything.</li>
	<li>Open <span class="file"><a href="wp-admin/install.php">wp-admin/install.php</a></span> in your browser. It will take you through the process to set up a <code>wp-config.php</code> file with your database connection details.
		<ol>
			<li>If for some reason this does not work, do not worry. It may not work on all web hosts. Open up <code>wp-config-sample.php</code> with a text editor like WordPad or similar and fill in your database connection details.</li>
			<li>Save the file as <code>wp-config.php</code> and upload it.</li>
			<li>Open <span class="file"><a href="wp-admin/install.php">wp-admin/install.php</a></span> in your browser.</li>
		</ol>
	</li>
	<li>Once the configuration file is set up, the installer will set up the tables needed for your site. If there is an error, double check your <code>wp-config.php</code> file, and try again. If it fails again, please go to the <a href="https://wordpress.org/support/forums/">WordPress support forums</a> with as much data as you can gather.</li>
	<li><strong>If you did not enter a password, note the password given to you.</strong> If you did not provide a username, it will be <code>admin</code>.</li>
	<li>The installer should then send you to the <a href="wp-login.php">login page</a>. Sign in with the username and password you chose during the installation. If a password was generated for you, you can then click on &#8220;Profile&#8221; to change the password.</li>
</ol>

<h2>Updating</h2>
<h3>Using the Automatic Updater</h3>
<ol>
	<li>Open <span class="file"><a href="wp-admin/update-core.php">wp-admin/update-core.php</a></span> in your browser and follow the instructions.</li>
	<li>You wanted more, perhaps? That&#8217;s it!</li>
</ol>

<h3>Updating Manually</h3>
<ol>
	<li>Before you update anything, make sure you have backup copies of any files you may have modified such as <code>index.php</code>.</li>
	<li>Delete your old WordPress files, saving ones you&#8217;ve modified.</li>
	<li>Upload the new files.</li>
	<li>Point your browser to <span class="file"><a href="wp-admin/upgrade.php">/wp-admin/upgrade.php</a>.</span></li>
</ol>

<h2>Migrating from other systems</h2>
<p>WordPress can <a href="https://developer.wordpress.org/advanced-administration/wordpress/import/">import from a number of systems</a>. First you need to get WordPress installed and working as described above, before using <a href="wp-admin/import.php">our import tools</a>.</p>

<h2>System Requirements</h2>
<ul>
	<li><a href="https://secure.php.net/">PHP</a> version <strong>7.2.24</strong> or greater.</li>
	<li><a href="https://www.mysql.com/">MySQL</a> version <strong>5.5.5</strong> or greater.</li>
</ul>

<h3>Recommendations</h3>
<ul>
	<li><a href="https://secure.php.net/">PHP</a> version <strong>7.4</strong> or greater.</li>
	<li><a href="https://www.mysql.com/">MySQL</a> version <strong>8.0</strong> or greater OR <a href="https://mariadb.org/">MariaDB</a> version <strong>10.4</strong> or greater.</li>
	<li>The <a href="https://httpd.apache.org/docs/2.2/mod/mod_rewrite.html">mod_rewrite</a> Apache module.</li>
	<li><a href="https://wordpress.org/news/2016/12/moving-toward-ssl/">HTTPS</a> support.</li>
	<li>A link to <a href="https://wordpress.org/">wordpress.org</a> on your site.</li>
</ul>

<h2>Online Resources</h2>
<p>If you have any questions that are not addressed in this document, please take advantage of WordPress&#8217; numerous online resources:</p>
<dl>
	<dt><a href="https://wordpress.org/documentation/">HelpHub</a></dt>
		<dd>HelpHub is the encyclopedia of all things WordPress. It is the most comprehensive source of information for WordPress available.</dd>
	<dt><a href="https://wordpress.org/news/">The WordPress Blog</a></dt>
		<dd>This is where you&#8217;ll find the latest updates and news related to WordPress. Recent WordPress news appears in your administrative dashboard by default.</dd>
	<dt><a href="https://planet.wordpress.org/">WordPress Planet</a></dt>
		<dd>The WordPress Planet is a news aggregator that brings together posts from WordPress blogs around the web.</dd>
	<dt><a href="https://wordpress.org/support/forums/">WordPress Support Forums</a></dt>
		<dd>If you&#8217;ve looked everywhere and still cannot find an answer, the support forums are very active and have a large community ready to help. To help them help you be sure to use a descriptive thread title and describe your question in as much detail as possible.</dd>
	<dt><a href="https://make.wordpress.org/support/handbook/appendix/other-support-locations/introduction-to-irc/">WordPress <abbr>IRC</abbr> (Internet Relay Chat) Channel</a></dt>
		<dd>There is an online chat channel that is used for discussion among people who use WordPress and occasionally support topics. The above wiki page should point you in the right direction. (<a href="https://web.libera.chat/#wordpress">irc.libera.chat #wordpress</a>)</dd>
</dl>

<h2>Final Notes</h2>
<ul>
	<li>If you have any suggestions, ideas, or comments, or if you (gasp!) found a bug, join us in the <a href="https://wordpress.org/support/forums/">Support Forums</a>.</li>
	<li>WordPress has a robust plugin <abbr>API</abbr> (Application Programming Interface) that makes extending the code easy. If you are a developer interested in utilizing this, see the <a href="https://developer.wordpress.org/plugins/">Plugin Developer Handbook</a>. You shouldn&#8217;t modify any of the core code.</li>
</ul>

<h2>Share the Love</h2>
<p>WordPress has no multi-million dollar marketing campaign or celebrity sponsors, but we do have something even better&#8212;you. If you enjoy WordPress please consider telling a friend, setting it up for someone less knowledgeable than yourself, or writing the author of a media article that overlooks us.</p>

<p>WordPress is the official continuation of <a href="https://cafelog.com/">b2/caf&#233;log</a>, which came from Michel V. The work has been continued by the <a href="https://wordpress.org/about/">WordPress developers</a>. If you would like to support WordPress, please consider <a href="https://wordpress.org/donate/">donating</a>.</p>

<h2>License</h2>
<p>WordPress is free software, and is released under the terms of the <abbr>GPL</abbr> (GNU General Public License) version 2 or (at your option) any later version. See <a href="license.txt">license.txt</a>.</p>

</body>
</html>

block-interactivity-experiments's People

Contributors

c4rl0sbr4v0 avatar darerodz avatar fabiankaegy avatar github-actions[bot] avatar luisherranz avatar michalczaplinski avatar ockham avatar santosguillamot avatar westonruter avatar yscik avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

block-interactivity-experiments's Issues

Remove directive tags (components) from the proposal

I'd like to propose removing the directives tags (also referred to as components) from the code and the proposal.

The reasons are:

  • During these months, we haven't been able to find a single use case, even a remote one, where using a directive tag is superior to using a directive attribute.
  • If we publish the directive tags as they are intended today, we may lose the opportunity to use them in the future for other uses.

Should we follow the HTML specification to the letter?

Hi everyone,

I am wondering if you have considered the fact, that the directives introduced here are not valid html when they are used as an attribute?

On regular, built in html elements you are not allowed to define arbitrary attributes. In these cases, you have to use a data- attribute to keep the markup valid.

For custom elements you are basically free to use whatever you want.

Although most JS frameworks use custom attributes when coding, will compile them away / remove these attributes when rendering to the DOM or on the server.

If you are aware of this issue, was it a conscious choice to still use those directives as attributes? If so, what was the reasoning? While there is an ongoing discussion about how important valid markup is, I think this will be a sensible topic in the WordPress ecosystem.

If you do not intend to use the directives as attributes, I apologise for raising this issue. It is still not so easy to get all of the details right for this project from the outside.

Inner block duplication on initially shown inner blocks

There's a problem with the user-land implementation of <template> done in #8 to support initially hidden inner blocks, and exacerbated in #26 to render multiple inner blocks in the save wrapper, which duplicates the inner blocks once you refresh the page in the Editor if the inner blocks are initially shown:

Screen.Capture.on.2022-05-25.at.15-00-18.mp4

There's no point in fixing this because Gutenberg won't render the inner blocks more than once if/when we propose this.

I'm not sure if we should leave it like this or revert #26.

This only happens if the inner blocks are initially shown, so to prevent this issue keep them initially hidden, like this:

const Frontend = ( { blockProps, attributes, children } ) => {
	const [ show, setShow ] = useState( false );

Wrapperless hydration alternative: Directives hydration

I've been thinking about how to get rid of wrappers because the display: contents solution doesn't seem to be compatible with classic themes: #49.

One solution could be to iterate over the DOM and build a "static virtual DOM" of the full page. Something like this:

import { createElement, hydrate } from "react";

// convert a DOM node to a static virtual DOM node:
function toVdom(node) {
  if (node.nodeType === 3) return node.data; // Text nodes --> strings
  let props = {},
    a = node.attributes; // attributes --> props
  for (let i = 0; i < a.length; i++) props[a[i].name] = a[i].value;
  return createElement(
    node.localName,
    props,
    [].map.call(node.childNodes, toVdom)
  ); // recurse children
}

const vdom = toVdom(document.body);

hydrate(vdom, document.body);

As we iterate the DOM, we could check for the View components, and add them to the virtual DOM:

function toVdom(node) {
  if (node.nodeType === 3) return node.data;
  let props = {},
    a = node.attributes;
  for (let i = 0; i < a.length; i++) props[a[i].name] = a[i].value;
  const type = props["wp-block-view"]
    ? blockViews.get(props["wp-block-view"]) // replace with View component
    : node.localName;
  return createElement(type, props, [].map.call(node.childNodes, convert));
}

Although for this to work properly, we need to avoid adding the nodes that belong to the View component as static virtual nodes or they would appear duplicated. A simple way to do that would be to find the children of the View component and ignore the rest.

This approach could support hydration techniques. It could work by checking the value of a prop (i.e., wp-block-hydration) and creating static virtual nodes if the conditions are not met yet. Then, once the conditions are met, replace the static virtual nodes with the virtual nodes generated by the View component.

This method would have some advantages over the current one:

  • It doesn't require wrappers.
  • Things that currently require wiring between islands (context, suspense, error boundaries, etc.) would work out of the box. Also, there won't be related issues due to the wiring, nor inconsistencies due to the setTimeouts.
  • But the main advantage that I see is: thanks to the virtual DOM, it'd be ready for client-side navigations that preserve the initialized components and minimize DOM manipulations. This is one of the goals of these experiments and is sometimes referred to as the "React Server Components pattern", although in this case, this would work with plain HTML.
  • Full support for all React libraries. Libraries that require you to add a custom Provider may not work with one Provider per island, like for example Recoil.

And one disadvantage:

  • The initial cost of turning the DOM into a static virtual DOM. I haven't yet explored how expensive it would be.

The preact-markup package is doing something similar to this approach. I've used it to emulate this, including the client-side navigations:

https://www.loom.com/share/35d882062e2447f3b2b9cae78cb75127

You can play with that codesanbox here: https://codesandbox.io/s/preact-markup-interactive-blocks-6knev6?file=/src/index.js

Credits to Jason Miller and preact-markup for a big part of the inspiration.


I would start trying this with Preact, and doing so in two steps:

  1. Generate the static virtual DOM injecting the View components.
  2. Hydrate the virtual DOM using Preact's hydrate.

If it works, we could try doing the hydration during the generation of the static virtual DOM to optimize the performance because, at that moment, you already know the vNode <-> DOM node relation.

Add lazy hydration techniques: idle and view

We want to add different hydration techniques for blocks as we did for the Island Hydration approach in #12.

At this moment, we can start by adding view and idle and keep adding more in the future.

Add the interactive block attributes during server-side rendering

Ok, so we've ended up serializing a lot of stuff in the markup:

<gutenberg-interactive-block
  data-gunteberg-block-type="..."
  data-gunteberg-context-used="..."
  data-gunteberg-context-provided="..."
  data-gunteberg-context-attributes="..."
  data-gunteberg-context-sourced-attributes="..."
  data-gunteberg-context-block-props="..."
  data-gunteberg-context-hydrate="..."
>
  <!-- The real block -->
  <div class="...">...</div>
</gutenberg-interactive-block>

The saved markup doesn't seem the best place for this, so I wonder if we should add all those attributes dynamically during the server-side rendering.

Any thoughts? 🙂

Connect React context provider if it is created after a child using that context has been already hydrated

If a block that provides a React context is hydrated after a block child that uses that context, the context is not connected between the two.

That works correctly in React: https://codesandbox.io/s/romantic-mclean-fc26hm?file=/src/App.js

You can test it out by adding a setTimeout with a bigger value to the Interactive Parent block so it is hydrated after the Interactive Child block.

Related to #57.

How to deal with `ref` attributes?

I was running the stress tests on some sites, and I found the same hydrationError in all of them: TypeError: Cannot create property 'current' on string 'MobileMenu' (the string varies between them).

I've been triaging it, and I believe this error occurs because they have a ref attribute in some HTML elements. As ref is also used by Preact, I assume at some point it tries to do something like ref.current === 'string' and produces this error.

Some sites are adding ref because they use other libraries like Vuejs or plugins like Curator.io, which also need the ref attribute.

This only happens when I run the stress testing script. If I paste the manual script into the browser console, I cannot reproduce it. I've checked the vDom with @DAreRodz, and the ref property is there, but it doesn't throw any error.

I tested 4000 sites and 8 of them had this issue:

  • kxbox.com: TypeError: Cannot create property 'current' on string 'pc.kx.idx.banner1.ljxz'
  • jhamiba.com: TypeError: Cannot create property 'current' on string 'MobileMenu'
  • meidouya.com: TypeError: Cannot create property 'current' on string 'MobileMenu'
  • wpdaxue.com: TypeError: Cannot create property 'current' on string 'MobileMenu'
  • 22vd.com: TypeError: Cannot create property 'current' on string 'nofollow'
  • knewsmart.com: TypeError: Cannot create property 'current' on string 'MobileMenu'
  • mfisp.com: TypeError: Cannot create property 'current' on string 'MobileMenu'
  • pro-q.it: TypeError: Cannot create property 'current' on string 'image'

Any idea about how should we approach this?

Limit the hydration of the Directives Hydration when the site doesn't support client-side navigations

I've been reviewing the Directives Hydration approach with @mtias, and he suggested limiting the hydration to the smallest area possible when the site won't do client-side navigations.

Even though our initial tests suggest that the toVdom function is faster than we initially thought, I agree that we should try it because it will improve performance and increase backward compatibility.

I've been thinking about it, and I believe it shouldn't add much complexity to the system. I've recorded a video to explain how I would approach it, which is basically:

  • Use blocks as the "hydratable" unit (not directives or client components).
  • Use block.json for the hydration opt-in.
  • Blocks need to declare the type of hydration they require, depending on if the block interacts with their inner blocks or not (through context, error boundaries, suspense, etc.):
    • Isolated:
      • If the block doesn't interact with its inner blocks
      • The toVdom algorithm stops when it finds the wp-inner-block attributes.
    • Top-down:
      • If the block interacts with its inner blocks (context, error boundaries, suspense, etc.)
      • The toVdom algorithm doesn't stop: everything in the tree below that block belongs to the same "app", including all their inner blocks. No interblock-sync is required (as opposed to the custom elements approach).

https://www.loom.com/share/3b4cb16c5b6c41d69f707b196cffb7f7

https://excalidraw.com/#json=sk6HaigLP3YGxVq8tTo-H,HeYfhO2lmY4zC1uwdF5H6g


I've also been exploring if there would be a way to "upgrade" a page that was hydrated using this partial vDOM hydration to directives hydration by reusing the vnodes created by the initial islands in the full app, but it requires manual mutation of the vnodes, and it feels to hacky for the moment. I also wasn't able to reuse the useEffect hooks (they are triggered again). I mentioned it in Preact's Slack, and Marvin said they are exploring islands in Preact, so maybe we can collaborate with them in the future. For now, I would discard that path.

SSR: Audit usage of `wp_process_directives`

@DAreRodz shared the following feedback with me in Slack:

I have a couple of questions regarding directives evaluation and wp-context:

I see that directives run inside wp_process_directives(), a callback added to the render_block hook—dispatched every time a block has rendered—, and uses WP_HTML_Tag_Processor to iterate over the HTML content, find the directives, and execute them.

The questions are:

  1. Isn’t that callback running several times for the same HTML?. E.g., if you have a block A and a block B inside A, both blocks will dispatch render_block when rendered. Won’t B's directives run twice, one when render_block is dispatched for B and a second one for A (that would include B in its $block_content)?
  2. WP_Directive_Context is instantiated for each render_block execution. Doesn’t it create a different context stack for each block? I guess it works in the end because we run directives inside inner blocks again (as mentioned in question 1.)?

Tracking Issue: Stress-testing Directives Hydration 🧪

This issue tracks the work done to test the performance and resilience of the Directives Hydration mechanism. As this approach requires building up a representation of the DOM of the whole page, we have to make sure that it performs well and does not interfere with third party scripts and styles.

The main branch of this experiment is main-wp-directives-plugin. Make sure you select it when opening a pull request if it is related to this experiment. Note that this is the same branch that we're using to develop the WordPress Directives Plugin!

Please proactively comment on this issue whenever you start working on a new task, get blocked, or finish a task, sharing as much detail and information as possible. Thanks!!

Export different code depending on the context (Edit, Save, or View)

I've been thinking a bit about this, and the best idea that comes to my mind is to use suffixes: file.env.js notation, like the one used by React Native (file.native.js, file.ios.js and file.android.js) or React Server Components (file.client.js and file.server.js).

For example, if a person imports RichText like this:

import { RichText } from "@wordpress/block-editor";

Webpack would bundle this:

  • Edit: @wordpress/block-editor/src/components/rich-text/index.edit.js
  • Save: @wordpress/block-editor/src/components/rich-text/index.save.js
  • Frontend/View: @wordpress/block-editor/src/components/rich-text/index.view.js

We would need to figure out:

  • How can we combine it with other suffixes (like file.native.js)? Is there a precedent?
  • How can we differentiate between Edit and Save (they share the same bundle right now)? Or how to use separate bundles for Edit and Save.

Apart from that, does anyone have any other idea about doing this?

Make sure View components are automatically hydrated even if their component is registered after the connectedCallback execution

In order to make this hydration technique as solid as possible, it is important that it doesn't rely on any execution order. One of our goals is to support client-side navigations where both the new HTML (containing wp-block tags) and the JS files associated with those blocks can come in any other, including the DOM modification first (and therefore the connectedCallback execution) and the frontend component (JS file) later. We need to make sure that hydration works even if the frontend component is registered after the connectedCallback execution.

This doesn't need to be complicated. I think that if some blockType is missing from the registry, the connectedCallback can just annotate it somehow so the registry registerBlockType() can trigger the hydration manually when it receives that blockType.

Something like this:

connectedCallback() {
  if (blockTypes.has(blockType)) {
    hydrate({ blockType, element: this, ...});
  } else {
    // ... anotate for latter hydration
    blockTypesToHydrate.set(name, { element, ... });
  };
}
const registerBlockType = (name, Component, options) => {
  blockTypes.set(name, { Component, options });

  // Hydrate annotated blocks
  if (blockTypesToHydrate.has(name)) {
    const { element, ... } = blockTypesToHydrate.get(name);
    hydrate({ blockType, element, ...});
  }
};

Another thing that could cause a race condition could be the JS containing registerBlockType() and the components using it, but we can figure out if we need something else for that later.

`<wp-inner-blocks>` causes DOM differences between edit and frontend

The <wp-inner-blocks> wrapper being added implicitly on the frontend is something block authors might not expect. It's not added in the editor right now, so anything relying on the DOM tree needs to handle two versions. Most notably this is styles with relationship selectors.

For example, Sensei's Flashcard block uses a structure where the first and second child blocks are considered the front and back of the card, respectively:

&--flipped-front > *:nth-child(1),
&--flipped-back > *:nth-child(2) { ... }

This is likely an edge case and can be worked around, but making it consistent across editor and frontend could improve the developer experience.

Code Editor crash

The Code Editor is crashing because of this throw:

export const useBlockEnvironment = () => {
  try {
    const env = useReactContext(EnvContext); // Throws in save
    if (env === "frontend") return "frontend";
    return "edit";
  } catch (e) {
  return "save";
  }
};

That's a temporary hack to distinguish between the different contexts and obviously we won't try to push it to Gutenberg, so no need to fix. I've just opened the issue so other people finding this problem knows what it is.

Unmount interactive blocks before removing them from the DOM

While working on the React Context API, @c4rl0sbr4v0 discovered that with the current implementation, the unmount callbacks (mainly returns of useEffect) won't run when an interactive block is removed from the DOM because a parent interactive block stops showing children. This is normal because they are independent React apps, but can lead to memory leaks and inconsistencies.

I've taken a look at Astro, and it doesn't support this. I couldn't find any reference to this problem in their repository either. I haven't looked into other island-based frameworks.

I've been playing around with this, and the callback of useLayoutEffect runs before the DOM nodes are removed, so maybe we could use that to unmount the children interactive blocks before they are destroyed.

const Children = ({ value, providedContext }) => {
  const ref = useRef(null);

  useLayoutEffect(
    () => () => {
      ref.current
        .querySelectorAll("gutenberg-interactive-block")
        .map((node) => {
          // These interactive blocks are about to be destroyed. We can send them a
          // signal so they can switch to `null` internally and force React to run
          // their unmount callbacks.
          const setIsMounted = isMountedMap.get(node);
          setIsMounted(false);
        });
    },
    []
  );

  return (
    <gutenberg-inner-blocks
      ref={ref}
      suppressHydrationWarning={true}
      dangerouslySetInnerHTML={{ __html: value }}
    />
  );
};

The other part is to switch to null if a node receives that signal. We can use a similar approach to the one we used in the React Context.

const InteractiveBlock = () => {
  const [isMounted, setIsMounted] = useState(true);

  useEffect(() => {
    // Expose the setIsMounted so it can be accessed by the parent.
    isMountedMap.set(this, setIsMounted);
  })

  return isMounted ? <Comp ... /> : null;
};

hydrate(InteractiveBlock, this);

I'm not 100% confident that React will successfully run all the callbacks with this approach, but I don't consider this a priority, especially considering that even Astro doesn't have a solution for this. It'd be great to keep an eye on it, though.

EDIT: Client-side navigations could be another source of external unmounting. There could be more. Maybe the API should be part of the HTML nodes? Something like Bento Component APIs:

Bento:

const accordion = document.querySelector("#myAccordion");
await customElements.whenDefined("bento-accordion");
const api = await accordion.getApi();

// set up button actions
document.querySelector("#button1").onclick = () => {
  api.toggle();
};
document.querySelector("#button2").onclick = () => {
  api.toggle("section1");
};

Interactive Blocks:

ref.current.querySelectorAll("gutenberg-interactive-block").map((block) => {
  block.unmount();
});

It could be nice 🙂

EDIT 2: I'm not super familiar with the Mutation Observer, but if it can be used to execute code right before a <gutenberg-interactive-block> is about to be removed, it could be a more robust solution.

Rename `wp_store` to something else

We've wanted to find a better name for what we've been calling wpx() for a while, and @michalczaplinski just suggested using store():

// render.php
<?php
store([
  "state" => [
    "isOpen" => false
  ]
]);
?>

<div>
  <button data-wp-on:click="actions.toggle">Toggle</button>
  <div data-wp-show="state.isOpen">
    Some text...
  </div>
</div>
// view.js
import { store } from '@wordpress/interactivity';

store({
  actions: {
    toggle: ({ context }) => {
      context.isOpen = !context.isOpen
    },
  },
});

I'm not sure if the WordPress community would accept such a generic name, but I like it because it is explicit, and if we can, I'd prefer to avoid more than one word to avoid the camelcase vs snakecase confusion. I.e., addStore vs add_store.

Any thoughts? Other ideas?

Nomenclature suggestions

Great to see all the explorations and progress. Even though nothing is finalized yet, it seems timely to look at the API surface and some of the naming conventions to ensure we are putting forward the best possible set of tools.

Can we consider whether View is a better nomenclature to use than Frontend?

registerBlockType( 'block-name', {
	edit: Edit,
	// The Save component is derived from the View component.
	view: View,
} );

and

import View from './view';
registerBlockType( 'block-name', View );

Registration

// Before
class GutenbergBlock extends HTMLElement {}

// After
class WpBlock extends HTMLElement {}

Same goes for other APIs like events and attributes:

// Before
new CustomEvent( 'gutenberg-context', {} )

// After
new CustomEvent( 'block-context', {} )
// or
new CustomEvent( 'wp-block-context', {} )

Similarly data- attributes should use block or wp-block instead of gutenberg namespace.


Custom Elements

Super interesting to see the use of custom elements, I think it's a good path forward. I'd suggest improving the names as well:

<!-- before -->
<gutenberg-interactive-block>

<!-- after -->
<wp-block>

Can we assume interactivity if it's declared as a custom element?

<!-- before -->
<gutenberg-inner-blocks>

<!-- after -->
<wp-inner-blocks>

Child using React context throws when there is no parent that provides that context

If you add a child block that is using a React context, but there is no parent block present to provide it, the hydration crashes.

At first, I thought it should be a matter of just checking if the provider is there and avoiding the error, but it's true that useContext needs to return something. So I'm not sure anymore what should happen here:

  • At least, we should provide a better error.
  • Maybe instead of erroring, we should pass null to useContext, although that may lead to errors in React that may be hard to debug.

I'm open to ideas 🙂🤷‍♂️

https://www.loom.com/share/8e965b18897a4fe58121a461898c6b42

Proposal: A JSX layer for directives

I did some thinking on what the best developer experience could be using the directives approach on hydration, and
started exploring a solution where the frontend component can still be authored like regular JSX.

The idea is to build upon the special save function of the blocks, which is not a live React component, but returns JSX that's serialized into a HTML string and saved in the post. This could be a good place to connect the markup with the context, adding the directives in the serialization step.

Here is a rough draft of how it would look like:

// Editor → save.js

export const save = ( { attributes } ) => {
	const { reset, time, isFinished } = useFrontendContext.save( 'example/coundown', ExampleCountdown( { attributes } ) );

	return <div { ...useBlockProps.save() }>
		<div className={ { 'is-finished': isFinished } }>{ time }</div>
		<button onClick={ reset }>{ __( 'Reset', 'text-domain' ) }</button>
	</div>
}

There are two steps to achieve this:

  1. useFrontendContext.save wraps the value in a proxy that tracks how the context is used, returning a data object for each property: { context: 'example/countdown', prop: 'isFinished', value: false }
useFrontendContext.save = ( namespace, context ) => new Proxy( context, () => ( {
	get( target, prop, receiver ) {
		return { context: namespace, prop: prop, value: Reflect.get( ...arguments ) };
	},
} ) )
  1. The serializer outputs directive attributes, eg. turns onClick to wp:click, and uses the data from the proxy.

The serializer would see this:

createElement( 'div', { blockProps }, [
	createElement( 'div', {
		className: { 'is-finished': { context: 'example/countdown', prop: 'isFinished', value: false } },
	}, [ { context: 'example/countdown', prop: 'time', value: '00:30' } ] ),
	createElement( 'button', {
		onClick: { context: 'example/countdown', prop: 'reset', value: Function },
	}, [ 'Reset' ] ),
] )

And generate this HTML:

<div class="wp-block-example-countdown aligncenter is-style-large">
	<div class="" wp-class:is-finished="example/countdown.isFinished" wp-bind="example/countdown.time">00:30</div>
	<button wp-on:click="example/countdown.reset">Reset</button>
</div>

The context can be defined in a flexible way. I looked at doing it component-like, with a constructor function that can contain state, set up side effects or other integrations:

// frontend.js

import { formatTime } from 'external-library';

export const ExampleCountdown = ( { attributes: { initial } } ) => {
	let count = initial;

	// Stuff to run on the frontend only.
	useEffect( () => {
		const tick  = () => {
			count--;
			if ( count === 0 ) {
				clearInterval( timer );
			}
		}
		const timer = setInterval( tick, 1000 );
	}, [] );

	return {
		get time() {
			return formatTime( count, 'mm:ss' )
		},
		reset() {
			count = initial;
		},
		get isFinished() {
			return count === 0;
		},
		get count() {
			return count;
		},
	}
}

registerContext( 'example/coundown', ExampleCountdown );

(The save function above doesn't have everything for this, the arguments this component takes still need to be saved and passed with something like the wp-context directive)

PHP, dynamic blocks

For dynamic blocks, the same serializer could be run as a build-time step instead, and output PHP template strings for the initial values:

Generated for PHP, saved to blocks/example-countdown.php:

<?php ?>
<div class="wp-block-example-countdown aligncenter is-style-large" wp-context="{'example/countdown': <?= wp_json_encode( $context['example/countdown']['attributes'] ); ?> }">
	<div class="<?= $context['example/countdown']['value']['isFinished']; ?>" wp-class:is-finished="example/countdown.isFinished" wp-bind="example/countdown.time"><?= $context['example/countdown']['time']; ?></div>
	<button wp-on:click="example/countdown.reset">Reset</button>
</div>

Using it in PHP:

<?php
	$context = [
		'example/countdown' => [
			'attributes' => [
				'initial' => 30,
			],
			'value' => [
				'time' => "00:30",
				'isFinished' => "",
			]
		]
	];
	return render_component_template( 'blocks/example-countdown.php', $context );
?>

Custom directives

The above example maps JSX attributes to directives ( eg onClickwp-on:click) when there is already an existing practice that makes sense. For other cases, the directives can be written directly:

<button wp-modal-open={ context.id }>Open</button>

Loops, conditionals

For dynamic fragments, helper components could collect the bindings and output the initial markup & template elements.

<Template foreach={ items }>{ item => <li>{ item.label }</li> } </Template>
<Template if={ ! items.count }> <span>No results</span> </Template>

Benefits and limitations

For blocks, this would provide a way to author the frontend component very much like the rest of the block. It also lets the developer reference the context as a real JS object, not as strings.
For static blocks, a compiler is not needed, this can be part of the existing renderToString function in @wordpress/element that turns a React element into a HTML string.

As a drawback on the developer experience, it can be hard to explain that the code using the context proxy cannot contain expressions, and they are working with references, not the values of those props. (Though there might be a way with a compiler extension that takes expressions from the JSX and creates context props from them.)

It also probably requires chaining proxies to track deeper level references of the context props.

Layout issues around `<wp-block>` and `display: contents`

Themes commonly style the top-level content elements, like .entry-content > *, for layout and spacing, eg to allow various alignments. Example:

.entry-content > :not(.alignwide):not(.alignfull):not(.alignleft):not(.alignright):not(.is-style-wide) {
  max-width: 58rem;
  width: calc(100% - 4rem);
	}
.entry-content > * {
  margin-left: auto;
  margin-right: auto;
  margin-bottom: 1.25em;
}

This doesn't really work with display: contents on the <wp-block> wrapper:

Current Expected
image image

One option is inheriting these spacing styles that would apply to the wrapper:

wp-block > * {
	margin: inherit;
	max-width: inherit;
	width: inherit;
}

I'm not sure that's enough for every theme, this would need wider testing and some strategy to keep compatibility with existing theme and possibly plugin styles.

Tracking issue: Directives Hydration ⚛️

We are now actively working on the WordPress Directives Plugin Tracking issue instead ➡️


This issue aims to list and keep track of the tasks related to the Directives Hydration experiment.

For the Custom Elements Hydration experiment, please check the Tracking Issue: Custom Elements Hydration 🧩.

The main branch of this experiment is main-directives-hydration. Make sure you select it when opening a pull request if it is related to this experiment.

Please, proactively comment on this issue whenever you start working on a new task, get blocked, or finish a task, sharing as much detail and information as possible. Thanks!!

Experiments

Done

Next

On hold

Prettier PHP

As part of our research of formatting tools, I'd like to test prettier-php next. If you're working with this repository, please help us test it.

It should be as simple as doing npm install again to install it, and then restarting your IDE if you are already using a prettier extension.

If you have any problem, please report it here.

If everything goes well, we can investigate if it could run before PHP-CS. It has an integration with PHP-CS-Fixer.

Cannot render `InnerBlocks.Content` more than once

Duplicated registerBlockType function

Right now, we are using the same name for two different functions that do different things, this may cause understanding errors.

In wordpress-blocks.js file:

export const registerBlockType = (name, { edit, view, ...rest }) => {
	gutenbergRegisterBlockType(name, {
		edit,
		save: Wrapper(view),
		...rest,
	});
};

In gutenberg-packages/frontend.js file

export const registerBlockType = (name, Component, options) => {
	blockTypes.set(name, { Component, options });
};

Would fit to rename the last one to setBlockType instead of registerBlockType?
cc @ockham @DAreRodz

Tracking Issue: Server-side rendering of directives

Based on #118 (comment) and #125.

✅ - Implemented
🟡 - Waiting for an upcoming Gutenberg release to include a missing feature
❌ - Blocked by missing feature in WP_HTML_Tag_Processor

Name Directive Missing feature
wp-context ✅ (#143)
wp-show 🚧 (#141) Wrapping content inside HTML tags with extra tags. E.g. get_content_inside_balanced_tags() from WordPress/gutenberg#46345, and a matching set_content_inside_balanced_tags() (see WordPress/gutenberg#47036).
wp-bind ✅ (#133) get_attribute_names_with_prefix(); landed in GB 15.0
wp-class ✅ (#133) See wp-bind.
wp-style ✅ (#133) See wp-bind. Furthermore, see WordPress/gutenberg#46887. (The latter isn't a blocker, though.)
wp-text ✅ (#170) Replace content between HTML tags. See wp-show.
wp-for / wp-each 🚧 (#166)
wp-html ✅ (#170) Replace content between HTML tags. See wp-show.
wp-slot & wp-fill

TODO:

  • Properly parse expressions in attributes like when.
    • Includes nested JS-style objects (e.g. context.myblock.open).
    • Basic logic operations (negation, and, or)? Comparison?
  • Add some directive/components registration mechanism.
    • Maybe similar to blocks, patterns, etc? register_directive, takes a directory with a directive.json which includes information such as attributes, render, context, ...?

⚛️ Directives don't work if they are used inside of a component

Because we're extracting the wp- attributes only on the server-side rendered HTML, if a component contains a directive, it won't work:

const View = ({ ... }) => (
  <div {...}>
    <span wp-some-directive>...</span>
  </div>
);

I think we need to move everything to options.vnode. I'll open a PR to investigate.

How to process `xpacket` in `toVdom`

We've found a couple of sites that have xpacket sections: talsunovads.lv and hlz.de.

Checking the nodeType, it seems they are considered nodeType 7. As we are not handling that use case, it returns an error: TypeError: Cannot read properties of undefined (reading 'length').

We have to decide what to do with these nodes. Should we do the same as with CDATA nodes? It seems a similar case, as they are included inside the SVGs.

Support definition of public frontend attributes and only serialize those

Use a new attribute property to whitelist the attributes that are sent to the client:

{
  "attributes": {
    "message": {
      "type": "string",
      "source": "text",
      "selector": ".title",
      "frontend": true
    }
  }
}

We can use getBlockType (from the "core/blocks" store) in the save component wrapper to access the block.json and filter the data-gutenberg-attributes.

There are two things we should make decisions:

  • Name: it could be view, frontend, or something else. I guess we need to decide the final name we are going to propose and be consistent.
  • Opt-in or opt-out: whether this would be true or false by default. If it's opt-out, the block should opt-in to attribute serialization in some other way to avoid security issues.

How to process CDATA in `toVdom`

We've found some websites that have CDATA sections. One of them is https://www.tiens-donc.com/. I think @SantosGuillamot has more.

We need to decide what to do with those nodes. Maybe the first thing to investigate is what does Preact with them. Does it delete them? (as it does with the HTML comments)

I'm just opening the issue, so this is documented, but happy to see someone else take over on this 🙂

Documentation: Nobody ever says what directives are

I've come here from the Make WP post, but both that post, this repo, and all the issues have this problem.

The goal of the project to make building interactive blocks easier is well stated, but none of the experiments or attempts to achieve that are explained, and they're all referred to by their terminology which is never explained.

For example, neither the Make post, the readme, or the main tracking issue ever says what directives are. This is the closest anybody gets to explaining what directives are:

The goal is to create an installable plugin that adds a set of basic directives and client-side transitions to any site.

But what are directives? And thanks to anybody who tries to explain in this issue, but it needs adding to the readme.

The repo is named after hydration experiments, and that gives some clues based on some articles I read a long time ago about React server side rendering and rehydrating components in the browser, but that too is unclear in the context of this project and blocks. I might interpret this as allowing you to build both the editor and frontend interfaces in React, but what I've read elsewhere doesn't fit this interpretation.

My fear is that this project is doing great work but has failed to explain what it's doing, and couched itself in opaque terminology. There are no examples or use cases in the readme, no screenshots or videos, no glossary, etc. I should have been able to tell what these things were within 30 seconds via the readme.

`setTimeout` callback doesn't seem 100 % reliable

As discussed here, it looks like the setTimeout callback we are using inside connectedCallback is not 100% reliable. It seems to be running before the HTML parsing has finished. Here you have one example of when this is a problem:

It seems that if the page is loaded quickly, the state defined in PHP and sent as an attribute is undefined in the view.js files. These are the two examples I'm talking about: Title & Date.

As Luis shared here, Jake Archibald talks about this problem in this video. He proposes:

  • A weird custom element hack that can inform the parent when it's rendered, because at that moment, all the children should be rendered as well:
<wp-block>
  <!-- children is processed first -->
  children...
  <!-- then this final element is processed -->
  <children-finished-processing></children-finished-processing>
</wp-block>
  • Use mutation observers.

Merge inherited `wp-context` properties from a parent instance

If a wp-context directive is nested under another wp-context instance, the properties defined in the parent context should also be available inside the nested context.

const WpContextExample = () => {
  const contextA = { a: 'foo' };
  const contextB = { b: 'bar' };

  return (
    <div wp-context={JSON.stringify(contextA)}>
      <div wp-context={JSON.stringify(contextB)}>
        {/* `context.a` and `context.b` should be accessible here */}
      </div>
    <div>
  );
}

Right now, only context.b is accessible in that scope because contextB replaces contextA entirely.

We need to implement wp-context so that all the previous properties that don't collide are preserved, even when they are nested.

In addition, we need to agree on how Array objects would be merged. We can either append the elements or replace them according to their index.

Children slots in client components

Moved from #60.

EDIT: Although this issue started as a way to figure out how to identify children in islands and client components, let's use this use to figure out how to fully hydrate client components, including nested components, multiple slots and out-of-order hydration.


Let's analyze what options we have to identify children:

  1. Use HTML comments, like <!-- slot --><div>...</div><!-- slot -->
  2. Use a wrapper, like <slot>
  3. Use an attribute, like <div wp-children>
  4. Figure out by comparing the DOM with the component output

1. Use HTML comments

<h1>Some title</h1>
<h3>Some description</h3>
<!-- slot -->
<div>I'm a child content</div>
<img src="with-an-image.png" />
<!-- slot -->

Many WordPress cache and optimization plugins and CDNs (including the popular Cloudflare) remove the HTML comments, so I'd try to avoid this option.

As reference, Fresh is using comments to find its islands, although it's a bit weird because they don't hydrate them and they don't support children.

2. Use a wrapper

A wrapper is reliable, but as we've seen with wp-block, display: contents is not the holy grail and they need to be taken into account because they affect CSS.

For interactive blocks, we could use the same <wp-inner-blocks> approach.

For client components (smaller than blocks), we could abstract it in the creation of the component, but the SSR should add it. Imagine this client component:

const WpHero = ({ title, description, children }) => (
  <>
    <h1>{title}</h1>
    <h3>{description}</h3>
    {children}
  </>
);

The SSR should be:

<wp-hero title="Some title" description="Some description">
  <h1>Some title</h1>
  <h3>Some description</h3>
  <slot name="children">
    <div>I'm a child content</div>
    <img src="with-an-image.png" />
  </slot>
</wp-hero>

So maybe we should not abstract it in the JSX, and make it explicit:

const WpHero = ({ title, description, children }) => (
  <>
    <h1>{title}</h1>
    <h3>{description}</h3>
    <slot name="children">{children}</slot>
  </>
);

We could add slot[name=children] { display: contents } by default, but again, other CSS like *:nth-child() requires taking them into account.

3. Use an attribute

We would need to add a special attribute to all the parent nodes:

<h1>Some title</h1>
<h3>Some description</h3>
<div wp-children>I'm a child content</div>
<img wp-children src="with-an-image.png" />

This would not need to be as explicit because it doesn't affect CSS so I think it could be nicely abstracted.

The have two problems:

  1. Attributes need an HTML node

    <h1>Some title</h1>
    <h3>Some description</h3>
    I'm a child content
    <img wp-children src="with-an-image.png" />

    So we would have to detect texts and wrap them with <span>s:

    <h1>Some title</h1>
    <h3>Some description</h3>
    <span wp-children>I'm a child content</span>
    <img wp-children src="with-an-image.png" />
  2. We can't add them on the fly using PHP

    Imagine you have these $children, and you want to add the attributes:

    <div class="title">
      <h1>Some title</h1>
    </div>
    <div class="description">
      <h3>Some description</h3>
      <div>
        <div class="extra">
          <div>More content</div>
        </div>
      </div>
    </div>

    We would need proper HTML parsing to know that the top-level tags are only those with the classes title, description and extra.

    By the conversation on the WP_HTML_Walker pull request, I think we can assume that the API of the WP_HTML_Walker is as closer as we will ever get to HTML parsing. And that API is not capable of adding these attributes (it just finds tags; it doesn't know which tags are in the top-level).

    On the other hand, wrapping $children with <slot> it's a simple as:

    $wrapped_children = sprintf( '<slot name="children">%1$s</slot>', $children );

4. Figure out by comparing the DOM with the component output

That'd be similar to what the hydrate algorithm does: compare the DOM with the vDOM generated by the app and try to match it. In this case, instead of removing what doesn't match, it could treat it as children.

This solution is more complex, requires more computation, and a bigger initial bundle, so I'd try to avoid it if we can.


Any other ideas? 🙂

Tracking issue: WordPress Directives Plugin 🎨

This issue aims to list and keep track of the tasks related to the WordPress Directives Plugin experiment. The goal is to create an installable plugin that adds a set of basic directives and client-side navigations to any site.

For this experiment's stress-testing, please check the Tracking Issue: Performance and stress-testing vDOM Hydration 🧪

For the Custom Elements or Directives Hydration experiment, please check their corresponding Tracking Issues.

The main branch of this experiment is main-wp-directives-plugin. Make sure you select it when opening a pull request if it is related to this experiment.

Please proactively comment on this issue whenever you start working on a new task, get blocked, or finish a task, sharing as much detail and information as possible. Thanks!!

High-level overview

  • Directives

    • Finish the directives of the initial list - In progress 🚧

      Work on the directives is still reactive as we only work on the ones that we need for our experiments, demos, etc. The work on the Movies demo and Woo blocks should bring some focus to this task.

    • Add SSR for the ones that need it - In progress 🚧\

      This is led by @ockham and @dmsnell.

      There's already initial support for the SSR of basic directive attributes and good progress on wp-context. The next challenge to solve is to figure out the APIs to access the inner HTML with the HTML (Tag) Processor.

    • Decide final names - In progress 🚧

      We are keeping a list of the decisions that need to be made here in the Tracking Issue.

  • Components

    There hasn't been any progress on this, mostly due to the lack of need for components (directive tags). @luisherranz opened an issue proposing their removal, as we couldn't find a single case where they add meaningful value and we might want to use them for other purposes in the future.

  • State Management

    • Finish the Deep Signals implementation - Finished
    • Decide on the recommended shape - In progress 🚧
      Same answer as in the "Directives - Decide final names".
    • TypeScript support - Not started
    • Figure out if we want to expose some parts of the router and/or other configuration in the state (example) - Not started
  • Server-side rendering

    • API to do SSRing - In progress 🚧
      Same answer as in the "Directives - Add SSR for the ones that need it".
    • Serialize initial state - Finished
    • Sort directives by priority - Not started
  • Client runtime

    • Limit hydration when CSN is not possible Finished
    • Deserialize state Finished ✅ (mostly)
    • Sort directives by priority - In progress 🚧
  • Bundling

    • API to register directives/components - Not started
    • Enqueue directives/components present in the HTML - Not started
    • Ensure script execution order, deferring and prefetch - Not started
    • Choose final package name and shape - In progress 🚧
      Same answer as in the "Directives - Decide final names".
    • Choose and implement dependency management - In progress 🚧
      Barely started, just preliminary conversations between @luisherranz, @gziolo, @dmsnell and @youknowriad.
  • Client-side navigation

    • Manual (Finished ✅) and automatic opt-in (Not started ⏸)
    • Router
      • Fetch directives/components scripts - Not started
      • Global state (state.router.url) - Not started
    • API to trigger virtual pageviews - Not started
    • Prefetching strategy - Not started

Done

In progress

Next

Decisions that need to be made

  • Find a better name for wpx() (needed for the announcement blog post)
  • #152 (needed for the announcement blog post)
  • Decide on whether to follow the HTML spec or not (needed for the announcement blog post)
  • Decide the name of the client-side navigation meta tag.
  • Find a way to use Preact's JSX pragma in the view.js files with backward compatibility.
  • Decide whether "interactive: true" should be optional or not in the block.json.
  • #138 (needed for the announcement blog post)
  • #146
  • Figure out how the store will work in static blocks: it won't.
  • Figure out how we can pass objects to wp-context instead of a stringified JSON (in PHP and JS).
  • Decide if wp-bind:value should be bidirectional in inputs.
  • Find names for the full/partial/isolated hydration types.
  • Decide if we should use client-side transitions or client-side navigation: we'll use client-side navigation from now on
  • Figure out how to use context inside the derived state: we'll use selectors
  • Decide on whether to use components or not: we won't
  • Decide on the name of this repo. Something like interactivity-api-proposal. (needed for the announcement blog post)
  • Is registering the block.json in the server a requirement?
  • Is a single top-level node a requirement?
  • #159
  • Decide how to name the DOM element that is passed down to the references (right now it's called ref)
  • #160 (needed for the announcement blog post)
  • Decide what pattern we should use for "multiple render path" directives like wp-error-boundary or wp-suspense
  • Decide if we need to name wp-html with a word the warns about its risks, like wp-dangerous-html

Experiments

Tracking issue: Custom Elements Hydration 🧩

This issue aims to list and keep track of the tasks related to the Custom Elements Hydration experiment.

For the Directives Hydration experiment, please check the Tracking Issue: Directives Hydration ⚛️.

The main branch of this experiment is main-custom-elements-hydration. Make sure you select it when opening a pull request if it is related to this experiment.

Please proactively comment on this issue whenever you start working on a new task, get blocked, or finish a task, sharing as much detail and information as possible. Thanks!!

Done

  • Hydrate Frontend components
  • Make sure Frontend components are automatically rehydrated if they appear in the DOM at any point (not only on page load)
  • Support partial hydration with Inner blocks (children raw HTML)
  • Serialize attributes and pass them down to the View component
  • Use children instead of in the Save component to be able to reuse the same component in the View
  • #15
  • #8
  • #3
  • #2
  • #3
  • #14
  • #7
  • #34
  • #30
  • #40
  • #28
  • #41
  • #45
  • #47
  • #57

Required to expose as experimental API in Gutenberg

Future improvements

  • #62
  • #58
  • Add support for frontend-side i18n
  • Research how to hydrate different block variations
  • Change hydration technique based on block attributes
  • Add an onClick hydration technique (like Qwik or Rocket)
  • Support for Suspense and Error boundaries between different blocks
  • Search for alternative bundling strategies to wp globals that can support tree-shaking and code deduplication

⚛️ Share block context between blocks

Right now, blocks have two properties called data-wp-block-provides-block-context and data-wp-block-uses-block-context, containing the information of those attributes that are exposed and consumed.

The idea is to use the Option Hooks API from Preact to handle those properties as directives.

Support block support class names and inline styles on the block wrapper and make `blockProps` optional

In #3, @luisherranz wrote

Block supports (only in the wrapper node, no skip-serialization): I'm serializing the block props (which include the has-text-color... class names) because I thought React would remove them, but it doesn't seem to do so. I need to investigate more.

"Serializing the block props" means that they're passed as a data-gutenberg-block-props attribute, and rehydrated based on that, rather than relying on React to take care of the rehydration.

Diff to disable this "manual" rehydration.
diff --git a/src/gutenberg-packages/frontend.js b/src/gutenberg-packages/frontend.js
index 4446742..e0fbe42 100644
--- a/src/gutenberg-packages/frontend.js
+++ b/src/gutenberg-packages/frontend.js
@@ -92,7 +92,7 @@ class GutenbergBlock extends HTMLElement {
                                <EnvContext.Provider value='frontend'>
                                        <Comp
                                                attributes={attributes}
-                                               blockProps={blockProps}
+                                               //blockProps={blockProps}
                                                suppressHydrationWarning={true}
                                                context={context}
                                        >

During our pair-programming session today, we actually found that having props of the same name react to interaction causes React to remove the original block props:

--- a/src/blocks/block-hydration-experiments-parent/frontend/index.js
+++ b/src/blocks/block-hydration-experiments-parent/frontend/index.js
@@ -9,7 +9,7 @@ const Frontend = (
        const [ counter, setCounter ] = useState( initialCounter );
 
        return (
-               <div {...blockProps}>
+               <div className={ show ? 'shown' : 'hidden' } {...blockProps}>
                        <Title message={message} />
                        <Button handler={() => setShow( !show )} />
                        <button onClick={() => setCounter( counter + 1 )}>{counter}</button>

Thus, with both diffs applied:

broken-hydration

Help test `dprint`

@gziolo proposed fully adopting prettier (and dropping the WordPress spacing standard) in January: https://make.wordpress.org/core/2022/01/05/proposal-changes-to-javascript-coding-standards-for-full-prettier-compatibility/

Even though the proposal received a lot of support, @ntwb said in Slack that the consensus at the moment is a "No" from the core team. We've been unable to locate where that core team conversation took place, and I'm not sure why Gutenberg contributors are not considered Core contributors, but the proposal is currently stuck because of that.

I'll keep trying to contact @ntwb and see if he can finally share more information about how, where and why that decision was made, but until then, I was testing dprint, as an alternative, just in case prettier doesn't finally work out.

As I'm not the only one using this repository anymore, it'd be great to know your impressions with dprint. My main issue right now is that you cannot use a local node_modules installation, and therefore people need to figure out how to install it globally. Also, I was unable to make the VS Code extension work by default with the global installation of dprint and I had to manually add a dprint.path in the VS Code settings.

If you've also tested it out while working on this repository, please share your impressions 🙂

Decide how to use namespaces in the store

I have been talking to @SantosGuillamot and we thought that just using the namespace after state, actions, effects, etc, should be enough to avoid collisions.

The namespace should be the same as the one used in the blocks, defined in the documentation as:

The name for a block is a unique string that identifies a block. Names have to be structured as namespace/block-name, where namespace is the name of your plugin or theme.

Within the namespace, developers can do whatever they want. If they define values in the root, they will be as global values to their namespace, and if they have several blocks with properties that may conflict, they can also use the names of each block to further subdivide the state.

The definition of a single store would look like this:

store({
  state: {
    myPlugin: {
      // Inside state.myPlugin, the creator of my-plugin can organize whatever they want.
      value: 123,
      someBlock: {
        value: 456,
      },
      otherBlock: {
        value: 789,
      },
    },
  },
  actions: {
    myPlugin: {
      // Inside actions.myPlugin, the creator of my-plugin can organize whatever they want.
    }
  }
});

And a complete store would look like this:

{
  state: {
    woo: {
      value: 123,
      cart: {
        // ...
      },
      filters: {
        // ...
      },
    },
    myPlugin: {
      value: 123,
      someBlock: {
        value: 456,
      },
      otherBlock: {
        value: 789,
      },
    },
  },
  actions: {
    woo: {
      // ...
    },
    myPlugin: {
      // ...
    },
  },
};

Thoughts?

cc: @WordPress/frontend-dx

Implement different hydration techniques

This one should be pretty straightforward.

We could use a data-gutenberg-hydrate="technique" with values load, idle and view for now.

Something like:

const technique = this.getAttribute("data-gutenberg-hydrate");

const hydrate = () => {...};

switch (technique) {
  case "idle":
    requestIdleCallback(hydrate)
    break;
  case "view":
    onInteresect(this, hydrate)
    break;
  default:
    hydrate();
}

For reference, Astro's code is here: https://github.com/withastro/astro/tree/main/packages/astro/src/runtime/client.

⚛️ Remove the `wp-inner-blocks` wrapper

We need to get rid of the <wp-inner-blocks> element to avoid CSS issues like #50.

As @luisherranz proposed in #50 (comment), we can append the wp-inner-block attribute to those blocks that are the children of an interactive block.

<div class="wp-block-parent">
  <div>Some content</div>
  <div class="wp-block-child-1" wp-inner-block>Some child content</div>
  <div class="wp-block-child-2" wp-inner-block>More child content</div>
</div>

Note that we need a new attribute because:

  • using a regexp to find nodes with their class starting with wp-block-" could be not reliable enough
  • using the wp-block-type attribute would work only if children are interactive blocks as well

Support React's Context API between different blocks

What

When we asked plugin developers what we should include in our experiments, they answered React Context support.

Context provides a way to pass data through the component tree without having to pass props down manually at every level, this means easy data communication between different components.

The problem

In this experiment, we are taking the Island's architecture as the main approach. We use custom elements so we hydrate the block automatically when joins the tree (you can copy-paste a block, and will be hydrated), and also allow us cool things like defining the way we want to hydrate it.

The problem with this approach is, that, usually when you use React, you only have one application where you just add Providers components in the tree at the highest level possible, so that way any child component can access the Context data.

In our case, we have multiple React Apps, so we have to find a process to share that context between different apps while reusing the React Context API as much as possible. We don't want the developer to learn anything new if they already know how to work with React Context.

One possible exploration

Screenshot 2022-06-28 at 10 29 49

https://excalidraw.com/#json=x8cpUygy5LrBMMzhDmS8E,s5RQCGHTZXZStvCFPDPfwA

On the block registration, we should be able to import the Context, create a wrapper with that Context so our Child Component (the block itself) could access it, and then set a Consumer that can sync the value with the parent Provider, so the Context is updated when needed in each parent.

We thought about the idea of creating a subscriber to a Context, but, in this case, if we have more than one context, is really difficult to select the Context to subscribe to. Instead, we want to try the default behavior of the Context of subscribing to the closest parent Provider, by using a custom event, similar to what we do for the UsesContext and ProvidesContext in default blocks.

We created a video to try to explain everything, recommended playback speed is 1.5x, as I talked toooo slow 😓

video2535897202.mp4

Preact: Hydration fails when there are HTML comments

We found what I think is a bug in Preact's hydration. By default, if Preact finds HTML comments during the hydration, it removes them. The problem is that while doing so, it also recreates (removes and adds again) the next DOM node.

I've reproduced the issue in this repository: https://github.com/luisherranz/preact-comments-hydration-bug

I've also checked it in Preact's own tests, and it's also happening there: luisherranz/preact@004d25b

I've recorded a video to show the repository and tests:

https://www.loom.com/share/89dbe2ac14164bb9a35457cd27e10cce

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.