Skip to content
This repository was archived by the owner on Jul 28, 2023. It is now read-only.
This repository was archived by the owner on Jul 28, 2023. It is now read-only.

Wrapperless hydration alternative: Directives hydration #60

@luisherranz

Description

@luisherranz

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions