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

⚛️ Refactor directives and make them work inside client components #74

Merged
14 changes: 7 additions & 7 deletions block-hydration-experiments.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,13 @@ function bhe_block_wrapper($block_content, $block, $instance)

// Generate all required wrapper attributes.
$block_wrapper_attributes = sprintf(
'data-wp-block-type="%1$s" ' .
'data-wp-block-uses-block-context="%2$s" ' .
'data-wp-block-provides-block-context="%3$s" ' .
'data-wp-block-attributes="%4$s" ' .
'data-wp-block-sourced-attributes="%5$s" ' .
'data-wp-block-props="%6$s" ' .
'data-wp-block-hydration="%7$s"',
'wp-block-type="%1$s" ' .
'wp-block-uses-block-context="%2$s" ' .
'wp-block-provides-block-context="%3$s" ' .
'wp-block-attributes="%4$s" ' .
'wp-block-sourced-attributes="%5$s" ' .
'wp-block-props="%6$s" ' .
'wp-block-hydration="%7$s"',
esc_attr($block['blockName']),
esc_attr(json_encode($block_type->uses_context)),
esc_attr(json_encode($block_type->provides_context)),
Expand Down
9 changes: 6 additions & 3 deletions src/blocks/interactive-child/view.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import CounterContext from '../../context/counter';
import ThemeContext from '../../context/theme';
import { useContext, useState } from '../../gutenberg-packages/wordpress-element';
import {
useContext,
useState,
} from '../../gutenberg-packages/wordpress-element';

const View = ({ blockProps, context }) => {
const InteractiveChild = ({ blockProps, context }) => {
const theme = useContext(ThemeContext);
const counter = useContext(CounterContext);

Expand All @@ -29,4 +32,4 @@ const View = ({ blockProps, context }) => {
);
};

export default View;
export default InteractiveChild;
11 changes: 8 additions & 3 deletions src/blocks/interactive-parent/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
useErrorBoundary,
} from '../../gutenberg-packages/wordpress-element';

const View = ({
const InteractiveParent = ({
blockProps: {
className,
style: { fontWeight, ...style },
Expand All @@ -29,7 +29,12 @@ const View = ({
fontWeight: bold ? 900 : fontWeight,
}}
>
<h2 className="title">{title}</h2>
<h2
wp-log="the header of interactive-parent was rendered"
className="title"
>
{title}
</h2>
<button onClick={() => setShow(!show)}>Show</button>
<button onClick={() => setBold(!bold)}>Bold</button>
<button onClick={() => setCounter(counter + 1)}>
Expand All @@ -50,4 +55,4 @@ const View = ({
);
};

export default View;
export default InteractiveParent;
11 changes: 8 additions & 3 deletions src/blocks/non-interactive-parent/view.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
const View = ({ attributes, blockProps, children }) => (
const NonInteractiveParent = ({ attributes, blockProps, children }) => (
<div {...blockProps}>
<h4 className="title">{attributes.title}</h4>
<h4
wp-log="the header of non-interactive-parent was rendered"
className="title"
>
{attributes.title}
</h4>
{children}
</div>
);

export default View;
export default NonInteractiveParent;
2 changes: 1 addition & 1 deletion src/context/counter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext } from 'preact/compat';
import { createContext } from 'preact';

if (typeof window.counterContext === 'undefined') {
window.counterContext = window.wp.element
Expand Down
2 changes: 1 addition & 1 deletion src/context/theme.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext } from 'preact/compat';
import { createContext } from 'preact';

if (typeof window.themeContext === 'undefined') {
window.themeContext = window.wp.element
Expand Down
7 changes: 7 additions & 0 deletions src/directives/block-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createContext } from 'preact';

// Single context reused by all providers.
const blockContext = createContext({});
blockContext.displayName = 'BlockContext';

export default blockContext;
91 changes: 0 additions & 91 deletions src/directives/wp-block-context.js

This file was deleted.

32 changes: 32 additions & 0 deletions src/directives/wp-block-provides-block-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import blockContext from './block-context';
import { useContext } from 'preact/hooks';
import { directive } from '../gutenberg-packages/directives';

directive(
'blockProvidesBlockContext',
({ blockProvidesBlockContext }, { children, attributes }) => {
// Do nothing if it doesn't provides context.
if (
!blockProvidesBlockContext ||
!Object.keys(blockProvidesBlockContext).length
)
return;

// Get previous context.
const allContexts = useContext(blockContext);

// Get provided context from attributes.
const context = {};
for (const key in blockProvidesBlockContext) {
const value = attributes[blockProvidesBlockContext[key]];
if (value) context[key] = value;
}

// Provide merged contexts.
return (
<blockContext.Provider value={{ ...allContexts, ...context }}>
{children}
</blockContext.Provider>
);
}
);
86 changes: 86 additions & 0 deletions src/directives/wp-block-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useMemo, useContext } from 'preact/hooks';
import blockContext from './block-context';
import { createGlobal, matcherFromSource } from '../gutenberg-packages/utils';
import { directive } from '../gutenberg-packages/directives';

const blockViews = createGlobal('blockViews', new Map());

// Return an object of camelCased CSS properties.
const cssObject = (cssText) => {
if (!cssText) return {};

const el = document.createElement('div');
const { style } = el;
style.cssText = cssText;

const output = {};
for (let i = 0; i < style.length; i += 1) {
const key = style.item(0);
output[toCamelCase(key)] = style.getPropertyValue(key);
}

el.remove();
return output;
};

const toCamelCase = (str) =>
str.replace(/-(.)/g, (_, initial) => initial.toUpperCase());

// Handle block components.
directive('blockType', (wp, props, { vnode }) => {
const {
blockType,
blockAttributes,
blockSourcedAttributes,
blockUsesBlockContext,
blockProps,
innerBlocks,
initialRef,
} = wp;

props.blockProps = useMemo(
() => ({
style: cssObject(blockProps.style),
className: blockProps.class,
}),
[blockProps.class, blockProps.style]
);

// We using `vnode` as a storing medium here because it's the only thing that
// is stable when a parent block hides and then shows this part of the DOM.
// If we don't store the source attributes, we won't have access to them when
// this is shown again, because the vnodes are created before the real DOM
// nodes.
if (!vnode._blockAttributes) {
vnode._blockAttributes = blockAttributes || {};
for (const attr in blockSourcedAttributes) {
vnode._blockAttributes[attr] = matcherFromSource(
blockSourcedAttributes[attr]
)(initialRef);
}
}
props.attributes = vnode._blockAttributes;

// TODO: Replace with `lazy()` to pause hydration until the component is
// downloaded.
if (!blockViews.has(blockType)) return;

const { Component } = blockViews.get(blockType);

if (blockUsesBlockContext.length) {
const allContexts = useContext(blockContext);

// Filter and memoize the attributes that are needed.
props.context = useMemo(
() =>
blockUsesBlockContext.reduce((ctx, attribute) => {
const value = allContexts[attribute];
if (value) ctx[attribute] = value;
return ctx;
}, {}),
blockUsesBlockContext.map((attribute) => allContexts[attribute])
);
}

return <Component {...props}>{innerBlocks}</Component>;
});
29 changes: 0 additions & 29 deletions src/directives/wp-block.js

This file was deleted.

8 changes: 8 additions & 0 deletions src/directives/wp-log.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { useEffect } from 'preact/hooks';
import { directive } from '../gutenberg-packages/directives';

directive('log', ({ log }) => {
useEffect(() => {
console.log(log);
}, [log]);
});
Loading