From aa164e1fa25c179c3ddec99c92bcc1177ca3e886 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Wed, 7 Sep 2022 18:25:04 +0200 Subject: [PATCH 01/20] Add modified Markup component --- package-lock.json | 5 ++ package.json | 3 +- src/gutenberg-packages/hydration.js | 18 ++++ src/gutenberg-packages/markup/index.js | 89 +++++++++++++++++++ .../markup/markup-to-vdom.js | 83 +++++++++++++++++ 5 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 src/gutenberg-packages/markup/index.js create mode 100644 src/gutenberg-packages/markup/markup-to-vdom.js diff --git a/package-lock.json b/package-lock.json index 940f8aa3..7a19a5a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13166,6 +13166,11 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.10.6.tgz", "integrity": "sha512-w0mCL5vICUAZrh1DuHEdOWBjxdO62lvcO++jbzr8UhhYcTbFkpegLH9XX+7MadjTl/y0feoqwQ/zAnzkc/EGog==" }, + "preact-markup": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/preact-markup/-/preact-markup-2.1.1.tgz", + "integrity": "sha512-8JL2p36mzK8XkspOyhBxUSPjYwMxDM0L5BWBZWxsZMVW8WsGQrYQDgVuDKkRspt2hwrle+Cxr/053hpc9BJwfw==" + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/package.json b/package.json index b68183ef..690fdc9e 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ }, "dependencies": { "hpq": "^1.3.0", - "preact": "^10.10.6" + "preact": "^10.10.6", + "preact-markup": "^2.1.1" } } diff --git a/src/gutenberg-packages/hydration.js b/src/gutenberg-packages/hydration.js index 0402b02f..e25266c3 100644 --- a/src/gutenberg-packages/hydration.js +++ b/src/gutenberg-packages/hydration.js @@ -1,3 +1,21 @@ +import { hydrate } from 'preact'; import { createGlobal } from './utils'; +import Markup from './markup'; const blockViews = createGlobal('blockViews', new Map()); +const container = document.querySelector('.wp-site-blocks'); + +console.log(`Before render:`, blockViews); + +hydrate( + [key, value.Component]) + )} + />, + container +); diff --git a/src/gutenberg-packages/markup/index.js b/src/gutenberg-packages/markup/index.js new file mode 100644 index 00000000..57628629 --- /dev/null +++ b/src/gutenberg-packages/markup/index.js @@ -0,0 +1,89 @@ +import { h as defaultReviver, Component } from "preact"; +import markupToVdom from "./markup-to-vdom"; + +let customReviver; + +export default class Markup extends Component { + static setReviver(h) { + customReviver = h; + } + + shouldComponentUpdate({ wrap, type, markup }) { + let p = this.props; + return wrap !== p.wrap || type !== p.type || markup !== p.markup; + } + + setComponents(components) { + this.map = {}; + if (components) { + for (let i in components) { + // eslint-disable-next-line no-prototype-builtins + if (components.hasOwnProperty(i)) { + let name = i + .replace(/([A-Z]+)([A-Z][a-z0-9])|([a-z0-9]+)([A-Z])/g, "$1$3-$2$4") + .toLowerCase(); + this.map[name] = components[i]; + } + } + } + } + + render({ + wrap = true, + type, + markup, + components, + reviver, + onError, + "allow-scripts": allowScripts, + "allow-events": allowEvents, + trim, + ...props + }) { + let h = + reviver || + this.reviver || + this.constructor.prototype.reviver || + customReviver || + defaultReviver, + vdom; + + this.setComponents(components); + + let options = { + allowScripts, + allowEvents, + trim + }; + + try { + vdom = markupToVdom(markup, type, h, this.map, options); + } catch (error) { + if (onError) { + onError({ error }); + } else if (typeof console !== "undefined" && console.error) { + console.error(`preact-markup: ${error}`); + } + } + + if (wrap === false) { + const result = vdom || null; + console.log(vdom); + return result; + } + + // eslint-disable-next-line no-prototype-builtins + let c = props.hasOwnProperty("className") ? "className" : "class", + cl = props[c]; + if (!cl) props[c] = "markup"; + else if (cl.splice) cl.splice(0, 0, "markup"); + else if (typeof cl === "string") props[c] += " markup"; + else if (typeof cl === "object") cl.markup = true; + + const result = h("div", props, vdom || null); + + console.log(result); + + return result; + } +} diff --git a/src/gutenberg-packages/markup/markup-to-vdom.js b/src/gutenberg-packages/markup/markup-to-vdom.js new file mode 100644 index 00000000..ccf4bad2 --- /dev/null +++ b/src/gutenberg-packages/markup/markup-to-vdom.js @@ -0,0 +1,83 @@ +import parseMarkup from "preact-markup/src/parse-markup"; +import toVdom from "preact-markup/src/to-vdom"; + +const EMPTY_OBJ = {}; + +/** Convert markup into a virtual DOM. + * @param {String} markup HTML or XML markup (indicate via `type`) + * @param {String} [type=xml] A type to use when parsing `markup`. Either `xml` or `html`. + * @param {Function} reviver The JSX/hyperscript reviver (`h` function) to use. For example, Preact's `h` or `ReactDOM.createElement`. + * @param {Object} [map] Optional map of custom element names to Components or variant element names. + */ +export default function markupToVdom(markup, type, reviver, map, options) { + let dom = parseMarkup(markup, type); + + if (dom && dom.error) { + throw new Error(dom.error); + } + + let body = (dom && dom.body) || dom; + visitor.map = map || EMPTY_OBJ; + let vdom = body && toVdom(body, visitor, reviver, options); + visitor.map = null; + + return (vdom && vdom.props && vdom.props.children) || null; +} + +function toCamelCase(name) { + return name.replace(/-(.)/g, (match, letter) => letter.toUpperCase()); +} + +function visitor(node) { + let name = (node.type || "").toLowerCase(), + map = visitor.map; + + // eslint-disable-next-line no-prototype-builtins + if (name === 'wp-block' && map) { + processWpBlock(node, map); + node.props = Object.keys(node.props || {}).reduce((attrs, attrName) => { + attrs[toCamelCase(attrName)] = node.props[attrName]; + return attrs; + }, {}); + } else { + node.type = name.replace(/[^a-z0-9-]/i, ''); + } +} + +function processWpBlock(node, map) { + const componentName = node.props['data-wp-block-type']; + const attributes = JSON.parse(node.props['data-wp-block-attributes']) || {}; + const blockProps = JSON.parse(node.props['data-wp-block-props']) || {}; + + node.type = map[componentName]; + node.props = { + ...node.props, + attributes, + blockProps, + context: {}, + }; + + // Get component children from wrapper. + const children = getChildrenFromWrapper(node.props.children); + if (children) { + console.log(children); + node.props.children = children; + } +} + +function getChildrenFromWrapper(children) { + if (!children?.length) return null; + + for (const child of children) { + if (isChildrenWrapper(child)) return child.props?.children || []; + } + + // Try with the next nesting level. + return getChildrenFromWrapper( + [].concat(...children.map((child) => child?.props?.children || [])) + ); +} + +function isChildrenWrapper(node) { + return node.type === 'wp-inner-blocks'; +} \ No newline at end of file From 88a73ab5b32a4a4402c7becacfb3c3cacd13423a Mon Sep 17 00:00:00 2001 From: David Arenas Date: Wed, 7 Sep 2022 18:25:32 +0200 Subject: [PATCH 02/20] Remove unsupported blockProps & attributes --- src/blocks/interactive-parent/view.js | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/blocks/interactive-parent/view.js b/src/blocks/interactive-parent/view.js index 00e525dc..5847ddda 100644 --- a/src/blocks/interactive-parent/view.js +++ b/src/blocks/interactive-parent/view.js @@ -5,27 +5,16 @@ import Title from './shared/title'; const Counter = createContext(null); const Theme = createContext(null); -const View = ({ - blockProps: { - className, - style: { fontWeight, ...style }, - }, - attributes: { counter: initialCounter, title }, - children, -}) => { +const View = ({ children }) => { const [show, setShow] = useState(true); const [bold, setBold] = useState(true); - const [counter, setCounter] = useState(initialCounter); + const [counter, setCounter] = useState(0); return (
{title} From 64c8877c5f171386edaa297480b50ce9c0dafe32 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Thu, 8 Sep 2022 14:03:50 +0200 Subject: [PATCH 03/20] Try to fix preact erros with hooks --- block-hydration-experiments.php | 6 ++ src/blocks/interactive-child/register-view.js | 2 + .../interactive-parent/register-view.js | 20 +++++ src/blocks/interactive-parent/view.js | 31 +++---- src/gutenberg-packages/hydration.js | 15 ++-- .../markup/markup-to-vdom.js | 84 ++++++++++--------- webpack.config.js | 13 +++ 7 files changed, 108 insertions(+), 63 deletions(-) diff --git a/block-hydration-experiments.php b/block-hydration-experiments.php index 616e8120..960704ab 100644 --- a/block-hydration-experiments.php +++ b/block-hydration-experiments.php @@ -11,6 +11,12 @@ */ function block_hydration_experiments_init() { + wp_enqueue_script( + 'vendors', + plugin_dir_url(__FILE__) . + 'build/vendors.js' + ); + wp_register_script( 'hydration', plugin_dir_url(__FILE__) . 'build/gutenberg-packages/hydration.js', diff --git a/src/blocks/interactive-child/register-view.js b/src/blocks/interactive-child/register-view.js index 199d7e0e..8679c0f3 100644 --- a/src/blocks/interactive-child/register-view.js +++ b/src/blocks/interactive-child/register-view.js @@ -1,3 +1,5 @@ +import 'preact/debug'; + import registerBlockView from '../../gutenberg-packages/register-block-view'; import View from './view'; diff --git a/src/blocks/interactive-parent/register-view.js b/src/blocks/interactive-parent/register-view.js index 342215c7..b1d4246f 100644 --- a/src/blocks/interactive-parent/register-view.js +++ b/src/blocks/interactive-parent/register-view.js @@ -1,4 +1,24 @@ +import 'preact/debug'; +import { useState } from 'preact/compat'; +import { createGlobal } from '../../gutenberg-packages/utils'; import registerBlockView from '../../gutenberg-packages/register-block-view'; import View from './view'; registerBlockView('bhe/interactive-parent', View); + +const WpBlock = ({ children, ...props }) => { + const [count, setCount] = useState(0); + const onClick = () => { + console.log('hola'); + setCount(count + 1); + }; + return ( + + {children} + + ); +}; + +const blockViews = createGlobal('blockViewsHacked', {}); + +blockViews['wp-block'] = WpBlock; diff --git a/src/blocks/interactive-parent/view.js b/src/blocks/interactive-parent/view.js index 5847ddda..b1de8c3e 100644 --- a/src/blocks/interactive-parent/view.js +++ b/src/blocks/interactive-parent/view.js @@ -1,9 +1,8 @@ -import { createContext, useState } from 'preact/compat'; +import { useState } from 'preact/compat'; import Button from './shared/button'; import Title from './shared/title'; -const Counter = createContext(null); -const Theme = createContext(null); +// const useState = (initialValue) => [initialValue, () => {}]; const View = ({ children }) => { const [show, setShow] = useState(true); @@ -11,21 +10,17 @@ const View = ({ children }) => { const [counter, setCounter] = useState(0); return ( - - -
- {title} - - - - {show && children} -
-
-
+
+ Title + + + + {show && children} +
); }; diff --git a/src/gutenberg-packages/hydration.js b/src/gutenberg-packages/hydration.js index e25266c3..7dd0dfa8 100644 --- a/src/gutenberg-packages/hydration.js +++ b/src/gutenberg-packages/hydration.js @@ -1,21 +1,22 @@ -import { hydrate } from 'preact'; +import 'preact/debug'; +import { hydrate } from 'preact/compat'; import { createGlobal } from './utils'; import Markup from './markup'; -const blockViews = createGlobal('blockViews', new Map()); +// const blockViews = createGlobal('blockViews', new Map()); +const blockViews = createGlobal('blockViewsHacked', {}); const container = document.querySelector('.wp-site-blocks'); console.log(`Before render:`, blockViews); + hydrate( [key, value.Component]) - )} + components={blockViews} />, container ); diff --git a/src/gutenberg-packages/markup/markup-to-vdom.js b/src/gutenberg-packages/markup/markup-to-vdom.js index ccf4bad2..36e555cc 100644 --- a/src/gutenberg-packages/markup/markup-to-vdom.js +++ b/src/gutenberg-packages/markup/markup-to-vdom.js @@ -1,5 +1,5 @@ -import parseMarkup from "preact-markup/src/parse-markup"; -import toVdom from "preact-markup/src/to-vdom"; +import parseMarkup from 'preact-markup/src/parse-markup'; +import toVdom from 'preact-markup/src/to-vdom'; const EMPTY_OBJ = {}; @@ -10,59 +10,67 @@ const EMPTY_OBJ = {}; * @param {Object} [map] Optional map of custom element names to Components or variant element names. */ export default function markupToVdom(markup, type, reviver, map, options) { - let dom = parseMarkup(markup, type); + let dom = type === 'dom' ? markup : parseMarkup(markup, type); - if (dom && dom.error) { - throw new Error(dom.error); - } + if (dom && dom.error) { + throw new Error(dom.error); + } - let body = (dom && dom.body) || dom; - visitor.map = map || EMPTY_OBJ; - let vdom = body && toVdom(body, visitor, reviver, options); - visitor.map = null; + let body = (dom && dom.body) || dom; + visitor.map = map || EMPTY_OBJ; + let vdom = body && toVdom(body, visitor, reviver, options); + visitor.map = null; - return (vdom && vdom.props && vdom.props.children) || null; + return (vdom && vdom.props && vdom.props.children) || null; } function toCamelCase(name) { - return name.replace(/-(.)/g, (match, letter) => letter.toUpperCase()); + return name.replace(/-(.)/g, (match, letter) => letter.toUpperCase()); } function visitor(node) { - let name = (node.type || "").toLowerCase(), - map = visitor.map; + let name = (node.type || '').toLowerCase(), + map = visitor.map; - // eslint-disable-next-line no-prototype-builtins - if (name === 'wp-block' && map) { + // eslint-disable-next-line no-prototype-builtins + if (name === 'wp-block' && map) { processWpBlock(node, map); + // node.type = WpBlock; node.props = Object.keys(node.props || {}).reduce((attrs, attrName) => { - attrs[toCamelCase(attrName)] = node.props[attrName]; + const key = !attrName.startsWith('data-') + ? toCamelCase(attrName) + : attrName; + attrs[key] = node.props[attrName]; return attrs; }, {}); - } else { + } else { node.type = name.replace(/[^a-z0-9-]/i, ''); - } + } } function processWpBlock(node, map) { - const componentName = node.props['data-wp-block-type']; - const attributes = JSON.parse(node.props['data-wp-block-attributes']) || {}; - const blockProps = JSON.parse(node.props['data-wp-block-props']) || {}; - - node.type = map[componentName]; - node.props = { - ...node.props, - attributes, - blockProps, - context: {}, - }; - - // Get component children from wrapper. - const children = getChildrenFromWrapper(node.props.children); - if (children) { - console.log(children); - node.props.children = children; - } + // const componentName = node.props['data-wp-block-type']; + // const attributes = JSON.parse(node.props['data-wp-block-attributes']) || {}; + // const blockProps = JSON.parse(node.props['data-wp-block-props']) || {}; + + const Component = map['wp-block']; + + if (!Component) return node; + + node.type = Component; + // node.props = { + // ...node.props, + // attributes, + // blockProps, + // context: {}, + // }; + + // // Get component children from wrapper. + // const children = getChildrenFromWrapper(node.props.children); + // if (children) { + // console.log(children); + // node.props.children = children; + // } } function getChildrenFromWrapper(children) { @@ -80,4 +88,4 @@ function getChildrenFromWrapper(children) { function isChildrenWrapper(node) { return node.type === 'wp-inner-blocks'; -} \ No newline at end of file +} diff --git a/webpack.config.js b/webpack.config.js index f70789a4..58cc5208 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,7 @@ const defaultConfig = require('@wordpress/scripts/config/webpack.config'); +delete defaultConfig.optimization.minimizer; + module.exports = [ defaultConfig, { @@ -21,6 +23,17 @@ module.exports = [ 'blocks/interactive-parent/register-view': './src/blocks/interactive-parent/register-view.js', }, + optimization: { + splitChunks: { + cacheGroups: { + vendor: { + test: /[\\/]node_modules[\\/]/, + name: 'vendors', + chunks: 'all', + }, + }, + }, + }, module: { rules: [ ...defaultConfig.module.rules, From 99635c4c9e77d224bd46df41bf134858116c5dea Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Thu, 8 Sep 2022 17:12:07 +0200 Subject: [PATCH 04/20] Use a single runtime --- block-hydration-experiments.php | 6 +----- src/gutenberg-packages/hydration.js | 21 ++++++++------------- webpack.config.js | 12 +++--------- 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/block-hydration-experiments.php b/block-hydration-experiments.php index 960704ab..46c53de3 100644 --- a/block-hydration-experiments.php +++ b/block-hydration-experiments.php @@ -11,11 +11,7 @@ */ function block_hydration_experiments_init() { - wp_enqueue_script( - 'vendors', - plugin_dir_url(__FILE__) . - 'build/vendors.js' - ); + wp_enqueue_script('vendors', plugin_dir_url(__FILE__) . 'build/vendors.js'); wp_register_script( 'hydration', diff --git a/src/gutenberg-packages/hydration.js b/src/gutenberg-packages/hydration.js index 7dd0dfa8..3876eb94 100644 --- a/src/gutenberg-packages/hydration.js +++ b/src/gutenberg-packages/hydration.js @@ -1,7 +1,6 @@ -import 'preact/debug'; -import { hydrate } from 'preact/compat'; +import { render } from 'preact'; +import { useState } from 'preact/hooks'; import { createGlobal } from './utils'; -import Markup from './markup'; // const blockViews = createGlobal('blockViews', new Map()); const blockViews = createGlobal('blockViewsHacked', {}); @@ -9,14 +8,10 @@ const container = document.querySelector('.wp-site-blocks'); console.log(`Before render:`, blockViews); +const Comp = () => { + const [c, sC] = useState(0); + return
sC(c + 1)}>hydrate: {c}
; +}; +const Comp2 = blockViews['Comp']; -hydrate( - , - container -); +render(
hi?
, container); diff --git a/webpack.config.js b/webpack.config.js index 58cc5208..d6a0a9a4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,15 +6,6 @@ module.exports = [ defaultConfig, { ...defaultConfig, - resolve: { - alias: { - '@wordpress/element': 'preact/compat', - react: 'preact/compat', - 'react-dom/test-utils': 'preact/test-utils', - 'react-dom': 'preact/compat', // Must be below test-utils - 'react/jsx-runtime': 'preact/jsx-runtime', - }, - }, entry: { 'gutenberg-packages/hydration': './src/gutenberg-packages/hydration.js', @@ -24,6 +15,9 @@ module.exports = [ './src/blocks/interactive-parent/register-view.js', }, optimization: { + runtimeChunk: { + name: 'vendors', + }, splitChunks: { cacheGroups: { vendor: { From 087815ba92be9817a5a94e5e9aa992699689178e Mon Sep 17 00:00:00 2001 From: David Arenas Date: Thu, 8 Sep 2022 19:56:22 +0200 Subject: [PATCH 05/20] Use compiled components --- .../interactive-parent/register-view.js | 20 +---------- src/gutenberg-packages/hydration.js | 28 ++++++++------- .../markup/markup-to-vdom.js | 34 +++++++++---------- 3 files changed, 34 insertions(+), 48 deletions(-) diff --git a/src/blocks/interactive-parent/register-view.js b/src/blocks/interactive-parent/register-view.js index b1d4246f..2c89b518 100644 --- a/src/blocks/interactive-parent/register-view.js +++ b/src/blocks/interactive-parent/register-view.js @@ -1,24 +1,6 @@ import 'preact/debug'; -import { useState } from 'preact/compat'; -import { createGlobal } from '../../gutenberg-packages/utils'; + import registerBlockView from '../../gutenberg-packages/register-block-view'; import View from './view'; registerBlockView('bhe/interactive-parent', View); - -const WpBlock = ({ children, ...props }) => { - const [count, setCount] = useState(0); - const onClick = () => { - console.log('hola'); - setCount(count + 1); - }; - return ( - - {children} - - ); -}; - -const blockViews = createGlobal('blockViewsHacked', {}); - -blockViews['wp-block'] = WpBlock; diff --git a/src/gutenberg-packages/hydration.js b/src/gutenberg-packages/hydration.js index 3876eb94..75b1fa6f 100644 --- a/src/gutenberg-packages/hydration.js +++ b/src/gutenberg-packages/hydration.js @@ -1,17 +1,21 @@ -import { render } from 'preact'; -import { useState } from 'preact/hooks'; +import { hydrate } from 'preact'; import { createGlobal } from './utils'; +import Markup from './markup'; -// const blockViews = createGlobal('blockViews', new Map()); -const blockViews = createGlobal('blockViewsHacked', {}); +const blockViews = createGlobal('blockViews', new Map()); const container = document.querySelector('.wp-site-blocks'); -console.log(`Before render:`, blockViews); +const components = Object.fromEntries( + [...blockViews.entries()].map(([k, v]) => [k, v.Component]) +); -const Comp = () => { - const [c, sC] = useState(0); - return
sC(c + 1)}>hydrate: {c}
; -}; -const Comp2 = blockViews['Comp']; - -render(
hi?
, container); +hydrate( + , + container +); diff --git a/src/gutenberg-packages/markup/markup-to-vdom.js b/src/gutenberg-packages/markup/markup-to-vdom.js index 36e555cc..ff00a0c9 100644 --- a/src/gutenberg-packages/markup/markup-to-vdom.js +++ b/src/gutenberg-packages/markup/markup-to-vdom.js @@ -49,28 +49,28 @@ function visitor(node) { } function processWpBlock(node, map) { - // const componentName = node.props['data-wp-block-type']; - // const attributes = JSON.parse(node.props['data-wp-block-attributes']) || {}; - // const blockProps = JSON.parse(node.props['data-wp-block-props']) || {}; + const blockType = node.props['data-wp-block-type']; + const attributes = JSON.parse(node.props['data-wp-block-attributes']) || {}; + const blockProps = JSON.parse(node.props['data-wp-block-props']) || {}; - const Component = map['wp-block']; + const Component = map[blockType]; if (!Component) return node; node.type = Component; - // node.props = { - // ...node.props, - // attributes, - // blockProps, - // context: {}, - // }; - - // // Get component children from wrapper. - // const children = getChildrenFromWrapper(node.props.children); - // if (children) { - // console.log(children); - // node.props.children = children; - // } + node.props = { + ...node.props, + attributes, + blockProps, + context: {}, + }; + + // Get component children from wrapper. + const children = getChildrenFromWrapper(node.props.children); + if (children) { + console.log(children); + node.props.children = children; + } } function getChildrenFromWrapper(children) { From 81c6c56bddfa262b18ebc61d9f4024b7d0fc7859 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Fri, 9 Sep 2022 13:29:56 +0200 Subject: [PATCH 06/20] Modify wordpress-element to work with preact hooks --- src/blocks/interactive-parent/view.js | 2 +- src/gutenberg-packages/wordpress-element.js | 36 ++++++++++----------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/blocks/interactive-parent/view.js b/src/blocks/interactive-parent/view.js index b1de8c3e..7e5f8ee9 100644 --- a/src/blocks/interactive-parent/view.js +++ b/src/blocks/interactive-parent/view.js @@ -1,4 +1,4 @@ -import { useState } from 'preact/compat'; +import { useState } from '../../gutenberg-packages/wordpress-element'; import Button from './shared/button'; import Title from './shared/title'; diff --git a/src/gutenberg-packages/wordpress-element.js b/src/gutenberg-packages/wordpress-element.js index 17135d9a..0201d3a2 100644 --- a/src/gutenberg-packages/wordpress-element.js +++ b/src/gutenberg-packages/wordpress-element.js @@ -1,28 +1,26 @@ import { createContext, - useContext as useReactContext, - useEffect as useReactEffect, - useState as useReactState, -} from '@wordpress/element'; + useContext as usePreactContext, + useEffect as usePreactEffect, + useState as usePreactState, +} from 'preact/compat'; -export const EnvContext = createContext(null); +export const EnvContext = createContext('view'); /** * A React hook that returns the name of the environment. * - * This is still a bit hacky. Ideally, Save components should support React - * hooks and all the environments (Edit, Save and View) should populate a - * normal context. Also, more environments could be added in the future. + * Based on the workaround used for the Island Hydration approach, but only to differentiate between + * Save and View, so this function and related hooks cannot be used inside Edit. + * + * Note that the other approach was a bit hacky; this is a bit more hacky. * - * @returns {"edit" | "save" | "view"} + * @returns {"save" | "view"} */ export const useBlockEnvironment = () => { try { - const env = useReactContext(EnvContext); - if (env === 'view') { - return 'view'; - } - return 'edit'; + // This will fail if the hook runs inside something that's not a Preact component. + return usePreactContext(EnvContext); } catch (e) { return 'save'; } @@ -30,13 +28,13 @@ export const useBlockEnvironment = () => { const noop = () => {}; -export const useState = (init) => - useBlockEnvironment() !== 'save' ? useReactState(init) : [init, noop]; +export const useState = (init) => + useBlockEnvironment() !== 'save' ? usePreactState(init) : [init, noop]; export const useEffect = (...args) => - useBlockEnvironment() !== 'save' ? useReactEffect(...args) : noop; + useBlockEnvironment() !== 'save' ? usePreactEffect(...args) : noop; export const useContext = (Context) => useBlockEnvironment() !== 'save' - ? useReactContext(Context) - : Context._currentValue; + ? usePreactContext(Context) + : Context._currentValue; \ No newline at end of file From 4dc3cd2cae7d733ead1e19043e835190cc4da61f Mon Sep 17 00:00:00 2001 From: David Arenas Date: Fri, 9 Sep 2022 18:17:08 +0200 Subject: [PATCH 07/20] Support block props --- src/blocks/interactive-parent/view.js | 20 ++++-- src/gutenberg-packages/hydration.js | 2 +- .../markup/markup-to-vdom.js | 43 +------------ .../markup/process-wp-block.js | 64 +++++++++++++++++++ 4 files changed, 79 insertions(+), 50 deletions(-) create mode 100644 src/gutenberg-packages/markup/process-wp-block.js diff --git a/src/blocks/interactive-parent/view.js b/src/blocks/interactive-parent/view.js index 7e5f8ee9..efcbd8bc 100644 --- a/src/blocks/interactive-parent/view.js +++ b/src/blocks/interactive-parent/view.js @@ -2,23 +2,29 @@ import { useState } from '../../gutenberg-packages/wordpress-element'; import Button from './shared/button'; import Title from './shared/title'; -// const useState = (initialValue) => [initialValue, () => {}]; - -const View = ({ children }) => { +const View = ({ + blockProps: { + className, + style: { fontWeight, ...style }, + }, + children, +}) => { const [show, setShow] = useState(true); const [bold, setBold] = useState(true); const [counter, setCounter] = useState(0); return (
Title - + {show && children}
); diff --git a/src/gutenberg-packages/hydration.js b/src/gutenberg-packages/hydration.js index 75b1fa6f..a2221b4c 100644 --- a/src/gutenberg-packages/hydration.js +++ b/src/gutenberg-packages/hydration.js @@ -1,4 +1,4 @@ -import { hydrate } from 'preact'; +import { hydrate } from 'preact/compat'; import { createGlobal } from './utils'; import Markup from './markup'; diff --git a/src/gutenberg-packages/markup/markup-to-vdom.js b/src/gutenberg-packages/markup/markup-to-vdom.js index ff00a0c9..886ae5d2 100644 --- a/src/gutenberg-packages/markup/markup-to-vdom.js +++ b/src/gutenberg-packages/markup/markup-to-vdom.js @@ -1,5 +1,6 @@ import parseMarkup from 'preact-markup/src/parse-markup'; import toVdom from 'preact-markup/src/to-vdom'; +import processWpBlock from './process-wp-block'; const EMPTY_OBJ = {}; @@ -47,45 +48,3 @@ function visitor(node) { node.type = name.replace(/[^a-z0-9-]/i, ''); } } - -function processWpBlock(node, map) { - const blockType = node.props['data-wp-block-type']; - const attributes = JSON.parse(node.props['data-wp-block-attributes']) || {}; - const blockProps = JSON.parse(node.props['data-wp-block-props']) || {}; - - const Component = map[blockType]; - - if (!Component) return node; - - node.type = Component; - node.props = { - ...node.props, - attributes, - blockProps, - context: {}, - }; - - // Get component children from wrapper. - const children = getChildrenFromWrapper(node.props.children); - if (children) { - console.log(children); - node.props.children = children; - } -} - -function getChildrenFromWrapper(children) { - if (!children?.length) return null; - - for (const child of children) { - if (isChildrenWrapper(child)) return child.props?.children || []; - } - - // Try with the next nesting level. - return getChildrenFromWrapper( - [].concat(...children.map((child) => child?.props?.children || [])) - ); -} - -function isChildrenWrapper(node) { - return node.type === 'wp-inner-blocks'; -} diff --git a/src/gutenberg-packages/markup/process-wp-block.js b/src/gutenberg-packages/markup/process-wp-block.js new file mode 100644 index 00000000..26d9599a --- /dev/null +++ b/src/gutenberg-packages/markup/process-wp-block.js @@ -0,0 +1,64 @@ +export default function processWpBlock(node, map) { + const blockType = node.props['data-wp-block-type']; + const Component = map[blockType]; + + if (!Component) return node; + + node.type = Component; + node.props = { + // ...node.props, + attributes: {}, + context: {}, + blockProps: getBlockProps(node), + children: getChildren(node), + }; +} + +function getBlockProps(node) { + const { class: className, style } = JSON.parse( + node.props['data-wp-block-props'] + ); + return { className, style: getStyleProp(style) }; +} + +function getChildren(node) { + return getChildrenFromWrapper(node.props.children) || node.props.children; +} + +function getChildrenFromWrapper(children) { + if (!children?.length) return null; + + for (const child of children) { + if (isChildrenWrapper(child)) return child.props?.children || []; + } + + // Try with the next nesting level. + return getChildrenFromWrapper( + [].concat(...children.map((child) => child?.props?.children || [])) + ); +} + +function isChildrenWrapper(node) { + return node.type === 'wp-inner-blocks'; +} + +function toCamelCase(name) { + return name.replace(/-(.)/g, (match, letter) => letter.toUpperCase()); +} + +export function getStyleProp(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; +} \ No newline at end of file From e82e445707467f84b2ff87d80def2a5a73d04a1d Mon Sep 17 00:00:00 2001 From: David Arenas Date: Fri, 9 Sep 2022 19:01:51 +0200 Subject: [PATCH 08/20] Support attributes and sourced attributes --- src/blocks/interactive-parent/edit.js | 2 +- src/blocks/interactive-parent/view.js | 5 +- .../markup/markup-to-vdom.js | 16 ++--- .../markup/process-wp-block.js | 51 +++++++++---- src/gutenberg-packages/markup/to-vdom.js | 71 +++++++++++++++++++ 5 files changed, 119 insertions(+), 26 deletions(-) create mode 100644 src/gutenberg-packages/markup/to-vdom.js diff --git a/src/blocks/interactive-parent/edit.js b/src/blocks/interactive-parent/edit.js index 49a07e4b..421c2000 100644 --- a/src/blocks/interactive-parent/edit.js +++ b/src/blocks/interactive-parent/edit.js @@ -8,7 +8,7 @@ import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; import Button from './shared/button'; import Title from './shared/title'; -const Edit = ({ attributes: { counter, title, secret }, setAttributes }) => ( +const Edit = ({ attributes: { counter = 0, title, secret }, setAttributes }) => ( <>
{ const [show, setShow] = useState(true); const [bold, setBold] = useState(true); - const [counter, setCounter] = useState(0); + const [counter, setCounter] = useState(initialCounter); return ( <div @@ -21,7 +22,7 @@ const View = ({ fontWeight: bold ? 900 : fontWeight, }} > - <Title>Title + {title} diff --git a/src/gutenberg-packages/markup/markup-to-vdom.js b/src/gutenberg-packages/markup/markup-to-vdom.js index 886ae5d2..4d407c2a 100644 --- a/src/gutenberg-packages/markup/markup-to-vdom.js +++ b/src/gutenberg-packages/markup/markup-to-vdom.js @@ -1,5 +1,5 @@ import parseMarkup from 'preact-markup/src/parse-markup'; -import toVdom from 'preact-markup/src/to-vdom'; +import toVdom from './to-vdom'; import processWpBlock from './process-wp-block'; const EMPTY_OBJ = {}; @@ -29,22 +29,22 @@ function toCamelCase(name) { return name.replace(/-(.)/g, (match, letter) => letter.toUpperCase()); } -function visitor(node) { - let name = (node.type || '').toLowerCase(), +function visitor(vNode, domNode) { + let name = (vNode.type || '').toLowerCase(), map = visitor.map; // eslint-disable-next-line no-prototype-builtins if (name === 'wp-block' && map) { - processWpBlock(node, map); - // node.type = WpBlock; - node.props = Object.keys(node.props || {}).reduce((attrs, attrName) => { + processWpBlock({ vNode, domNode, map }); + // vNode.type = WpBlock; + vNode.props = Object.keys(vNode.props || {}).reduce((attrs, attrName) => { const key = !attrName.startsWith('data-') ? toCamelCase(attrName) : attrName; - attrs[key] = node.props[attrName]; + attrs[key] = vNode.props[attrName]; return attrs; }, {}); } else { - node.type = name.replace(/[^a-z0-9-]/i, ''); + vNode.type = name.replace(/[^a-z0-9-]/i, ''); } } diff --git a/src/gutenberg-packages/markup/process-wp-block.js b/src/gutenberg-packages/markup/process-wp-block.js index 26d9599a..e68b2abc 100644 --- a/src/gutenberg-packages/markup/process-wp-block.js +++ b/src/gutenberg-packages/markup/process-wp-block.js @@ -1,28 +1,49 @@ -export default function processWpBlock(node, map) { - const blockType = node.props['data-wp-block-type']; +import { matcherFromSource } from '../utils'; + +export default function processWpBlock({ vNode, domNode, map }) { + const blockType = vNode.props['data-wp-block-type']; const Component = map[blockType]; - if (!Component) return node; + if (!Component) return vNode; - node.type = Component; - node.props = { - // ...node.props, - attributes: {}, + vNode.type = Component; + vNode.props = { + // ...vNode.props, + attributes: getAttributes(vNode, domNode), context: {}, - blockProps: getBlockProps(node), - children: getChildren(node), + blockProps: getBlockProps(vNode), + children: getChildren(vNode), }; } -function getBlockProps(node) { +function getBlockProps(vNode) { const { class: className, style } = JSON.parse( - node.props['data-wp-block-props'] + vNode.props['data-wp-block-props'] ); return { className, style: getStyleProp(style) }; } -function getChildren(node) { - return getChildrenFromWrapper(node.props.children) || node.props.children; +function getAttributes(vNode, domNode) { + // Get the block attributes. + const attributes = JSON.parse( + vNode.props['data-wp-block-attributes'] + ); + + // Add the sourced attributes to the attributes object. + const sourcedAttributes = JSON.parse( + vNode.props['data-wp-block-sourced-attributes'] + ); + for (const attr in sourcedAttributes) { + attributes[attr] = matcherFromSource(sourcedAttributes[attr])( + domNode + ); + } + + return attributes; +} + +function getChildren(vNode) { + return getChildrenFromWrapper(vNode.props.children) || vNode.props.children; } function getChildrenFromWrapper(children) { @@ -38,8 +59,8 @@ function getChildrenFromWrapper(children) { ); } -function isChildrenWrapper(node) { - return node.type === 'wp-inner-blocks'; +function isChildrenWrapper(vNode) { + return vNode.type === 'wp-inner-blocks'; } function toCamelCase(name) { diff --git a/src/gutenberg-packages/markup/to-vdom.js b/src/gutenberg-packages/markup/to-vdom.js new file mode 100644 index 00000000..59b9dd3e --- /dev/null +++ b/src/gutenberg-packages/markup/to-vdom.js @@ -0,0 +1,71 @@ +const EMPTY_OBJ = {}; + +// deeply convert an XML DOM to VDOM +export default function toVdom(node, visitor, h, options) { + walk.visitor = visitor; + walk.h = h; + walk.options = options || EMPTY_OBJ; + return walk(node); +} + +function walk(n, index, arr) { + if (n.nodeType === 3) { + let text = 'textContent' in n ? n.textContent : n.nodeValue || ''; + + if (walk.options.trim !== false) { + let isFirstOrLast = index === 0 || index === arr.length - 1; + + // trim strings but don't entirely collapse whitespace + if (text.match(/^[\s\n]+$/g) && walk.options.trim !== 'all') { + text = ' '; + } else { + text = text.replace( + /(^[\s\n]+|[\s\n]+$)/g, + walk.options.trim === 'all' || isFirstOrLast ? '' : ' ' + ); + } + // skip leading/trailing whitespace + // if (!text || text===' ' && arr.length>1 && (index===0 || index===arr.length-1)) return null; + if ((!text || text === ' ') && arr.length > 1 && isFirstOrLast) + return null; + // if (!text && arr.length>1 && (index===0 || index===arr.length-1)) return null; + // if (!text || text===' ') return null; + } + return text; + } + if (n.nodeType !== 1) return null; + let nodeName = String(n.nodeName).toLowerCase(); + + // Do not allow script tags unless explicitly specified + if (nodeName === 'script' && !walk.options.allowScripts) return null; + + let out = walk.h( + nodeName, + getProps(n.attributes), + walkChildren(n.childNodes) + ); + if (walk.visitor) walk.visitor(out, n); + + return out; +} + +function getProps(attrs) { + let len = attrs && attrs.length; + if (!len) return null; + let props = {}; + for (let i = 0; i < len; i++) { + let { name, value } = attrs[i]; + if (name.substring(0, 2) === 'on' && walk.options.allowEvents) { + value = new Function(value); // eslint-disable-line no-new-func + } + props[name] = value; + } + return props; +} + +function walkChildren(children) { + let c = children && Array.prototype.map.call(children, walk).filter(exists); + return c && c.length ? c : null; +} + +let exists = (x) => x; From dae5bd49c99252acd0c02c9b5d1066c95d44f7ed Mon Sep 17 00:00:00 2001 From: David Arenas Date: Mon, 12 Sep 2022 12:21:45 +0200 Subject: [PATCH 09/20] Remove not needed code from preact-markup --- src/gutenberg-packages/hydration.js | 21 ++--- src/gutenberg-packages/markup/index.js | 89 ------------------- .../markup/markup-to-vdom.js | 50 ----------- .../{markup => }/to-vdom.js | 3 - .../process-wp-block.js => visitor.js} | 25 ++++-- 5 files changed, 28 insertions(+), 160 deletions(-) delete mode 100644 src/gutenberg-packages/markup/index.js delete mode 100644 src/gutenberg-packages/markup/markup-to-vdom.js rename src/gutenberg-packages/{markup => }/to-vdom.js (88%) rename src/gutenberg-packages/{markup/process-wp-block.js => visitor.js} (79%) diff --git a/src/gutenberg-packages/hydration.js b/src/gutenberg-packages/hydration.js index a2221b4c..b69d1a14 100644 --- a/src/gutenberg-packages/hydration.js +++ b/src/gutenberg-packages/hydration.js @@ -1,21 +1,16 @@ -import { hydrate } from 'preact/compat'; +import { hydrate, createElement } from 'preact/compat'; import { createGlobal } from './utils'; -import Markup from './markup'; +import toVdom from './to-vdom'; +import visitor from './visitor'; const blockViews = createGlobal('blockViews', new Map()); -const container = document.querySelector('.wp-site-blocks'); const components = Object.fromEntries( [...blockViews.entries()].map(([k, v]) => [k, v.Component]) ); -hydrate( - , - container -); +visitor.map = components; + +const dom = document.querySelector('.wp-site-blocks'); +const vdom = toVdom(dom, visitor, createElement, {}).props.children; +hydrate(vdom, dom); diff --git a/src/gutenberg-packages/markup/index.js b/src/gutenberg-packages/markup/index.js deleted file mode 100644 index 57628629..00000000 --- a/src/gutenberg-packages/markup/index.js +++ /dev/null @@ -1,89 +0,0 @@ -import { h as defaultReviver, Component } from "preact"; -import markupToVdom from "./markup-to-vdom"; - -let customReviver; - -export default class Markup extends Component { - static setReviver(h) { - customReviver = h; - } - - shouldComponentUpdate({ wrap, type, markup }) { - let p = this.props; - return wrap !== p.wrap || type !== p.type || markup !== p.markup; - } - - setComponents(components) { - this.map = {}; - if (components) { - for (let i in components) { - // eslint-disable-next-line no-prototype-builtins - if (components.hasOwnProperty(i)) { - let name = i - .replace(/([A-Z]+)([A-Z][a-z0-9])|([a-z0-9]+)([A-Z])/g, "$1$3-$2$4") - .toLowerCase(); - this.map[name] = components[i]; - } - } - } - } - - render({ - wrap = true, - type, - markup, - components, - reviver, - onError, - "allow-scripts": allowScripts, - "allow-events": allowEvents, - trim, - ...props - }) { - let h = - reviver || - this.reviver || - this.constructor.prototype.reviver || - customReviver || - defaultReviver, - vdom; - - this.setComponents(components); - - let options = { - allowScripts, - allowEvents, - trim - }; - - try { - vdom = markupToVdom(markup, type, h, this.map, options); - } catch (error) { - if (onError) { - onError({ error }); - } else if (typeof console !== "undefined" && console.error) { - console.error(`preact-markup: ${error}`); - } - } - - if (wrap === false) { - const result = vdom || null; - console.log(vdom); - return result; - } - - // eslint-disable-next-line no-prototype-builtins - let c = props.hasOwnProperty("className") ? "className" : "class", - cl = props[c]; - if (!cl) props[c] = "markup"; - else if (cl.splice) cl.splice(0, 0, "markup"); - else if (typeof cl === "string") props[c] += " markup"; - else if (typeof cl === "object") cl.markup = true; - - const result = h("div", props, vdom || null); - - console.log(result); - - return result; - } -} diff --git a/src/gutenberg-packages/markup/markup-to-vdom.js b/src/gutenberg-packages/markup/markup-to-vdom.js deleted file mode 100644 index 4d407c2a..00000000 --- a/src/gutenberg-packages/markup/markup-to-vdom.js +++ /dev/null @@ -1,50 +0,0 @@ -import parseMarkup from 'preact-markup/src/parse-markup'; -import toVdom from './to-vdom'; -import processWpBlock from './process-wp-block'; - -const EMPTY_OBJ = {}; - -/** Convert markup into a virtual DOM. - * @param {String} markup HTML or XML markup (indicate via `type`) - * @param {String} [type=xml] A type to use when parsing `markup`. Either `xml` or `html`. - * @param {Function} reviver The JSX/hyperscript reviver (`h` function) to use. For example, Preact's `h` or `ReactDOM.createElement`. - * @param {Object} [map] Optional map of custom element names to Components or variant element names. - */ -export default function markupToVdom(markup, type, reviver, map, options) { - let dom = type === 'dom' ? markup : parseMarkup(markup, type); - - if (dom && dom.error) { - throw new Error(dom.error); - } - - let body = (dom && dom.body) || dom; - visitor.map = map || EMPTY_OBJ; - let vdom = body && toVdom(body, visitor, reviver, options); - visitor.map = null; - - return (vdom && vdom.props && vdom.props.children) || null; -} - -function toCamelCase(name) { - return name.replace(/-(.)/g, (match, letter) => letter.toUpperCase()); -} - -function visitor(vNode, domNode) { - let name = (vNode.type || '').toLowerCase(), - map = visitor.map; - - // eslint-disable-next-line no-prototype-builtins - if (name === 'wp-block' && map) { - processWpBlock({ vNode, domNode, map }); - // vNode.type = WpBlock; - vNode.props = Object.keys(vNode.props || {}).reduce((attrs, attrName) => { - const key = !attrName.startsWith('data-') - ? toCamelCase(attrName) - : attrName; - attrs[key] = vNode.props[attrName]; - return attrs; - }, {}); - } else { - vNode.type = name.replace(/[^a-z0-9-]/i, ''); - } -} diff --git a/src/gutenberg-packages/markup/to-vdom.js b/src/gutenberg-packages/to-vdom.js similarity index 88% rename from src/gutenberg-packages/markup/to-vdom.js rename to src/gutenberg-packages/to-vdom.js index 59b9dd3e..5d187b91 100644 --- a/src/gutenberg-packages/markup/to-vdom.js +++ b/src/gutenberg-packages/to-vdom.js @@ -25,11 +25,8 @@ function walk(n, index, arr) { ); } // skip leading/trailing whitespace - // if (!text || text===' ' && arr.length>1 && (index===0 || index===arr.length-1)) return null; if ((!text || text === ' ') && arr.length > 1 && isFirstOrLast) return null; - // if (!text && arr.length>1 && (index===0 || index===arr.length-1)) return null; - // if (!text || text===' ') return null; } return text; } diff --git a/src/gutenberg-packages/markup/process-wp-block.js b/src/gutenberg-packages/visitor.js similarity index 79% rename from src/gutenberg-packages/markup/process-wp-block.js rename to src/gutenberg-packages/visitor.js index e68b2abc..c61fc57d 100644 --- a/src/gutenberg-packages/markup/process-wp-block.js +++ b/src/gutenberg-packages/visitor.js @@ -1,18 +1,33 @@ -import { matcherFromSource } from '../utils'; +import { createElement as h } from "preact/compat"; +import { matcherFromSource } from './utils'; -export default function processWpBlock({ vNode, domNode, map }) { +export default function visitor(vNode, domNode) { + const name = (vNode.type || '').toLowerCase(); + const map = visitor.map; + + if (name === 'wp-block' && map) { + processWpBlock({ vNode, domNode, map }); + } else { + vNode.type = name.replace(/[^a-z0-9-]/i, ''); + } +} + +function processWpBlock({ vNode, domNode, map }) { const blockType = vNode.props['data-wp-block-type']; const Component = map[blockType]; if (!Component) return vNode; - vNode.type = Component; - vNode.props = { - // ...vNode.props, + const block = h(Component, { attributes: getAttributes(vNode, domNode), context: {}, blockProps: getBlockProps(vNode), children: getChildren(vNode), + }); + + vNode.props = { + ...vNode.props, + children: [block] }; } From c0fe8c4d3839deb5c8fb7126e120fc36c9744ff5 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Mon, 12 Sep 2022 12:28:30 +0200 Subject: [PATCH 10/20] Remove button shared component --- src/blocks/interactive-parent/edit.js | 3 +-- src/blocks/interactive-parent/shared/button.js | 5 ----- src/blocks/interactive-parent/view.js | 5 ++--- 3 files changed, 3 insertions(+), 10 deletions(-) delete mode 100644 src/blocks/interactive-parent/shared/button.js diff --git a/src/blocks/interactive-parent/edit.js b/src/blocks/interactive-parent/edit.js index 421c2000..3962513d 100644 --- a/src/blocks/interactive-parent/edit.js +++ b/src/blocks/interactive-parent/edit.js @@ -5,7 +5,6 @@ import '@wordpress/block-editor'; import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; -import Button from './shared/button'; import Title from './shared/title'; const Edit = ({ attributes: { counter = 0, title, secret }, setAttributes }) => ( @@ -17,7 +16,7 @@ const Edit = ({ attributes: { counter = 0, title, secret }, setAttributes }) => > {title} - + diff --git a/src/blocks/interactive-parent/shared/button.js b/src/blocks/interactive-parent/shared/button.js deleted file mode 100644 index 2384a54a..00000000 --- a/src/blocks/interactive-parent/shared/button.js +++ /dev/null @@ -1,5 +0,0 @@ -const Button = ({ handler, children }) => { - return ; -}; - -export default Button; diff --git a/src/blocks/interactive-parent/view.js b/src/blocks/interactive-parent/view.js index 1c82e798..274341c9 100644 --- a/src/blocks/interactive-parent/view.js +++ b/src/blocks/interactive-parent/view.js @@ -1,5 +1,4 @@ import { useState } from '../../gutenberg-packages/wordpress-element'; -import Button from './shared/button'; import Title from './shared/title'; const View = ({ @@ -23,8 +22,8 @@ const View = ({ }} > {title} - - + + {show && children}
From abe0605441483388b5b9df752872d968fc013d76 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Mon, 12 Sep 2022 12:38:23 +0200 Subject: [PATCH 11/20] Remove title shared component --- src/blocks/interactive-parent/edit.js | 17 ++++++++++------- src/blocks/interactive-parent/shared/title.js | 7 ------- src/blocks/non-interactive-parent/edit.js | 7 ++----- 3 files changed, 12 insertions(+), 19 deletions(-) delete mode 100644 src/blocks/interactive-parent/shared/title.js diff --git a/src/blocks/interactive-parent/edit.js b/src/blocks/interactive-parent/edit.js index 3962513d..9460b7d4 100644 --- a/src/blocks/interactive-parent/edit.js +++ b/src/blocks/interactive-parent/edit.js @@ -4,18 +4,21 @@ // the site. import '@wordpress/block-editor'; -import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; -import Title from './shared/title'; +import { InnerBlocks, useBlockProps, RichText } from '@wordpress/block-editor'; -const Edit = ({ attributes: { counter = 0, title, secret }, setAttributes }) => ( +const Edit = ({ + attributes: { counter = 0, title, secret }, + setAttributes, +}) => ( <>
- setAttributes({ title: val })} placeholder="This will be passed through context to child blocks" - > - {title} - + /> diff --git a/src/blocks/non-interactive-parent/view.js b/src/blocks/non-interactive-parent/view.js index c381f3cb..2df201b5 100644 --- a/src/blocks/non-interactive-parent/view.js +++ b/src/blocks/non-interactive-parent/view.js @@ -1,6 +1,6 @@ const View = ({ attributes, blockProps, children }) => (
-

{attributes.title}

+

{attributes.title}

{children}
); From 10fb30a205186233863e8bade8f2c211189e16fc Mon Sep 17 00:00:00 2001 From: David Arenas Date: Tue, 13 Sep 2022 09:54:39 +0200 Subject: [PATCH 13/20] Add preact context --- src/blocks/interactive-child/view.js | 8 +++++-- src/blocks/interactive-parent/view.js | 34 +++++++++++++++++---------- src/context/counter.js | 14 +++++++---- src/context/theme.js | 14 +++++++---- 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/blocks/interactive-child/view.js b/src/blocks/interactive-child/view.js index 06ff5bcb..cedc867b 100644 --- a/src/blocks/interactive-child/view.js +++ b/src/blocks/interactive-child/view.js @@ -1,6 +1,10 @@ +import CounterContext from '../../context/counter'; +import ThemeContext from '../../context/theme'; +import { useContext } from '../../gutenberg-packages/wordpress-element'; + const View = ({ blockProps, context }) => { - const theme = 'cool theme'; - const counter = 0; + const theme = useContext(ThemeContext); + const counter = useContext(CounterContext); return (
diff --git a/src/blocks/interactive-parent/view.js b/src/blocks/interactive-parent/view.js index 110c7eb0..68b4d7f9 100644 --- a/src/blocks/interactive-parent/view.js +++ b/src/blocks/interactive-parent/view.js @@ -1,3 +1,5 @@ +import Counter from '../../context/counter'; +import Theme from '../../context/theme'; import { useState } from '../../gutenberg-packages/wordpress-element'; const View = ({ @@ -13,19 +15,25 @@ const View = ({ const [counter, setCounter] = useState(initialCounter); return ( -
-

{title}

- - - - {show && children} -
+ + +
+

{title}

+ + + + {show && children} +
+
+
); }; diff --git a/src/context/counter.js b/src/context/counter.js index 6061f470..388f8aca 100644 --- a/src/context/counter.js +++ b/src/context/counter.js @@ -1,7 +1,11 @@ -import { createContext } from '@wordpress/element'; +import { createContext } from 'preact/compat'; -if (typeof window.reactContext === 'undefined') { - window.reactContext = createContext(null); +if (typeof window.counterContext === 'undefined') { + window.counterContext = window.wp.element + ? window.wp.element.createContext(null) + : createContext(null); + + window.counterContext.displayName = 'CounterContext'; } -window.reactContext.displayName = 'CounterContext'; -export default window.reactContext; + +export default window.counterContext; diff --git a/src/context/theme.js b/src/context/theme.js index 8aefc8ec..331c1942 100644 --- a/src/context/theme.js +++ b/src/context/theme.js @@ -1,7 +1,11 @@ -import { createContext } from '@wordpress/element'; +import { createContext } from 'preact/compat'; -if (typeof window.themeReactContext === 'undefined') { - window.themeReactContext = createContext(null); +if (typeof window.themeContext === 'undefined') { + window.themeContext = window.wp.element + ? window.wp.element.createContext('initial') + : createContext('initial'); + + window.themeContext.displayName = 'ThemeContext'; } -window.themeReactContext.displayName = 'ThemeContext'; -export default window.themeReactContext; + +export default window.themeContext; From 2a8737ab56701551ddb10617bd88b33eba1b97bd Mon Sep 17 00:00:00 2001 From: David Arenas Date: Tue, 13 Sep 2022 15:58:53 +0200 Subject: [PATCH 14/20] Fix hydration --- src/gutenberg-packages/hydration.js | 3 ++- src/gutenberg-packages/to-vdom.js | 24 ++---------------------- src/gutenberg-packages/visitor.js | 8 ++++---- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/src/gutenberg-packages/hydration.js b/src/gutenberg-packages/hydration.js index b69d1a14..acb5f7f0 100644 --- a/src/gutenberg-packages/hydration.js +++ b/src/gutenberg-packages/hydration.js @@ -13,4 +13,5 @@ visitor.map = components; const dom = document.querySelector('.wp-site-blocks'); const vdom = toVdom(dom, visitor, createElement, {}).props.children; -hydrate(vdom, dom); + +setTimeout(() => console.log('hydrated', hydrate(vdom, dom)), 3000); diff --git a/src/gutenberg-packages/to-vdom.js b/src/gutenberg-packages/to-vdom.js index 5d187b91..c95e6584 100644 --- a/src/gutenberg-packages/to-vdom.js +++ b/src/gutenberg-packages/to-vdom.js @@ -8,28 +8,8 @@ export default function toVdom(node, visitor, h, options) { return walk(node); } -function walk(n, index, arr) { - if (n.nodeType === 3) { - let text = 'textContent' in n ? n.textContent : n.nodeValue || ''; - - if (walk.options.trim !== false) { - let isFirstOrLast = index === 0 || index === arr.length - 1; - - // trim strings but don't entirely collapse whitespace - if (text.match(/^[\s\n]+$/g) && walk.options.trim !== 'all') { - text = ' '; - } else { - text = text.replace( - /(^[\s\n]+|[\s\n]+$)/g, - walk.options.trim === 'all' || isFirstOrLast ? '' : ' ' - ); - } - // skip leading/trailing whitespace - if ((!text || text === ' ') && arr.length > 1 && isFirstOrLast) - return null; - } - return text; - } +function walk(n) { + if (n.nodeType === 3) return n.data; if (n.nodeType !== 1) return null; let nodeName = String(n.nodeName).toLowerCase(); diff --git a/src/gutenberg-packages/visitor.js b/src/gutenberg-packages/visitor.js index c61fc57d..cb818a16 100644 --- a/src/gutenberg-packages/visitor.js +++ b/src/gutenberg-packages/visitor.js @@ -1,4 +1,4 @@ -import { createElement as h } from "preact/compat"; +import { h } from "preact"; import { matcherFromSource } from './utils'; export default function visitor(vNode, domNode) { @@ -23,11 +23,11 @@ function processWpBlock({ vNode, domNode, map }) { context: {}, blockProps: getBlockProps(vNode), children: getChildren(vNode), - }); + }); vNode.props = { ...vNode.props, - children: [block] + children: ['\n', block, '\n'] }; } @@ -65,7 +65,7 @@ function getChildrenFromWrapper(children) { if (!children?.length) return null; for (const child of children) { - if (isChildrenWrapper(child)) return child.props?.children || []; + if (isChildrenWrapper(child)) return [child] || []; } // Try with the next nesting level. From 6b2e9f230820252349b1e1566bfcbbec1a57f2fb Mon Sep 17 00:00:00 2001 From: David Arenas Date: Tue, 13 Sep 2022 16:22:25 +0200 Subject: [PATCH 15/20] Remove new lines from PHP instead --- block-hydration-experiments.php | 2 +- src/gutenberg-packages/visitor.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/block-hydration-experiments.php b/block-hydration-experiments.php index 46c53de3..47a581ff 100644 --- a/block-hydration-experiments.php +++ b/block-hydration-experiments.php @@ -118,7 +118,7 @@ function bhe_block_wrapper($block_content, $block, $instance) $template_wrapper, sprintf($block_wrapper, $block_content . $empty_template) ); - return sprintf($block_wrapper, $block_content); + return sprintf($block_wrapper, substr($block_content, 1, -1)); } add_filter('render_block', 'bhe_block_wrapper', 10, 3); diff --git a/src/gutenberg-packages/visitor.js b/src/gutenberg-packages/visitor.js index cb818a16..ebe737d9 100644 --- a/src/gutenberg-packages/visitor.js +++ b/src/gutenberg-packages/visitor.js @@ -27,7 +27,7 @@ function processWpBlock({ vNode, domNode, map }) { vNode.props = { ...vNode.props, - children: ['\n', block, '\n'] + children: [block] }; } From 21a8f3faf0bb8f5eb05527e5588fb1d791adea41 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Tue, 13 Sep 2022 17:11:59 +0200 Subject: [PATCH 16/20] Add a note for the block content break lines --- block-hydration-experiments.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/block-hydration-experiments.php b/block-hydration-experiments.php index 47a581ff..58401408 100644 --- a/block-hydration-experiments.php +++ b/block-hydration-experiments.php @@ -118,6 +118,11 @@ function bhe_block_wrapper($block_content, $block, $instance) $template_wrapper, sprintf($block_wrapper, $block_content . $empty_template) ); + + // The block content comes between two line breaks that seem to be included during block + // serialization, corresponding to those between the block markup and the block content. + // + // They need to be removed here; otherwise, the preact hydration fails. return sprintf($block_wrapper, substr($block_content, 1, -1)); } From adfb7ae975a6f592a7f3872c43835bea6d18ec73 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Tue, 13 Sep 2022 17:14:42 +0200 Subject: [PATCH 17/20] Remove preact/debug imports --- src/blocks/interactive-child/register-view.js | 2 -- src/blocks/interactive-parent/register-view.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/blocks/interactive-child/register-view.js b/src/blocks/interactive-child/register-view.js index 8679c0f3..199d7e0e 100644 --- a/src/blocks/interactive-child/register-view.js +++ b/src/blocks/interactive-child/register-view.js @@ -1,5 +1,3 @@ -import 'preact/debug'; - import registerBlockView from '../../gutenberg-packages/register-block-view'; import View from './view'; diff --git a/src/blocks/interactive-parent/register-view.js b/src/blocks/interactive-parent/register-view.js index 2c89b518..342215c7 100644 --- a/src/blocks/interactive-parent/register-view.js +++ b/src/blocks/interactive-parent/register-view.js @@ -1,5 +1,3 @@ -import 'preact/debug'; - import registerBlockView from '../../gutenberg-packages/register-block-view'; import View from './view'; From ec9574a99b2b2197771fdf24ca8ab19f3e045339 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Tue, 13 Sep 2022 17:16:33 +0200 Subject: [PATCH 18/20] Remove preact-markup dependency --- package-lock.json | 5 ----- package.json | 3 +-- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7a19a5a3..940f8aa3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13166,11 +13166,6 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.10.6.tgz", "integrity": "sha512-w0mCL5vICUAZrh1DuHEdOWBjxdO62lvcO++jbzr8UhhYcTbFkpegLH9XX+7MadjTl/y0feoqwQ/zAnzkc/EGog==" }, - "preact-markup": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/preact-markup/-/preact-markup-2.1.1.tgz", - "integrity": "sha512-8JL2p36mzK8XkspOyhBxUSPjYwMxDM0L5BWBZWxsZMVW8WsGQrYQDgVuDKkRspt2hwrle+Cxr/053hpc9BJwfw==" - }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/package.json b/package.json index 690fdc9e..b68183ef 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ }, "dependencies": { "hpq": "^1.3.0", - "preact": "^10.10.6", - "preact-markup": "^2.1.1" + "preact": "^10.10.6" } } From 261b6de2a3323551b6c29db074c25cee5b6a83db Mon Sep 17 00:00:00 2001 From: David Arenas Date: Tue, 13 Sep 2022 17:20:03 +0200 Subject: [PATCH 19/20] Do not delete minimizer in webpack config --- webpack.config.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index d6a0a9a4..2674dcc3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,7 +1,5 @@ const defaultConfig = require('@wordpress/scripts/config/webpack.config'); -delete defaultConfig.optimization.minimizer; - module.exports = [ defaultConfig, { From b0c6ed768ec0070f8bd30eba7aa2e19dc4fe0218 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Tue, 13 Sep 2022 17:31:18 +0200 Subject: [PATCH 20/20] Remove toVdom options --- src/gutenberg-packages/hydration.js | 2 +- src/gutenberg-packages/to-vdom.js | 13 +++---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/gutenberg-packages/hydration.js b/src/gutenberg-packages/hydration.js index acb5f7f0..b0f26a1d 100644 --- a/src/gutenberg-packages/hydration.js +++ b/src/gutenberg-packages/hydration.js @@ -12,6 +12,6 @@ const components = Object.fromEntries( visitor.map = components; const dom = document.querySelector('.wp-site-blocks'); -const vdom = toVdom(dom, visitor, createElement, {}).props.children; +const vdom = toVdom(dom, visitor, createElement).props.children; setTimeout(() => console.log('hydrated', hydrate(vdom, dom)), 3000); diff --git a/src/gutenberg-packages/to-vdom.js b/src/gutenberg-packages/to-vdom.js index c95e6584..abe86eb6 100644 --- a/src/gutenberg-packages/to-vdom.js +++ b/src/gutenberg-packages/to-vdom.js @@ -1,10 +1,6 @@ -const EMPTY_OBJ = {}; - -// deeply convert an XML DOM to VDOM -export default function toVdom(node, visitor, h, options) { +export default function toVdom(node, visitor, h) { walk.visitor = visitor; walk.h = h; - walk.options = options || EMPTY_OBJ; return walk(node); } @@ -13,8 +9,8 @@ function walk(n) { if (n.nodeType !== 1) return null; let nodeName = String(n.nodeName).toLowerCase(); - // Do not allow script tags unless explicitly specified - if (nodeName === 'script' && !walk.options.allowScripts) return null; + // Do not allow script tags (for now). + if (nodeName === 'script') return null; let out = walk.h( nodeName, @@ -32,9 +28,6 @@ function getProps(attrs) { let props = {}; for (let i = 0; i < len; i++) { let { name, value } = attrs[i]; - if (name.substring(0, 2) === 'on' && walk.options.allowEvents) { - value = new Function(value); // eslint-disable-line no-new-func - } props[name] = value; } return props;