This repository has been archived by the owner on Jul 28, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
⚛️ Hydrate the blocks with Directives Hydration (using wp-block
and wp-inner-blocks
)
#66
Merged
DAreRodz
merged 20 commits into
main-full-vdom-hydration
from
full-vdom/hydrate-vdom-from-dom
Sep 13, 2022
Merged
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
aa164e1
Add modified Markup component
DAreRodz 88a73ab
Remove unsupported blockProps & attributes
DAreRodz 64c8877
Try to fix preact erros with hooks
DAreRodz 99635c4
Use a single runtime
luisherranz 087815b
Use compiled components
DAreRodz 81c6c56
Modify wordpress-element to work with preact hooks
DAreRodz 4dc3cd2
Support block props
DAreRodz e82e445
Support attributes and sourced attributes
DAreRodz dae5bd4
Remove not needed code from preact-markup
DAreRodz c0fe8c4
Remove button shared component
DAreRodz abe0605
Remove title shared component
DAreRodz 333368f
Fix title tags in parent views
DAreRodz 10fb30a
Add preact context
DAreRodz 2a8737a
Fix hydration
DAreRodz 6b2e9f2
Remove new lines from PHP instead
DAreRodz 21a8f3f
Add a note for the block content break lines
DAreRodz adfb7ae
Remove preact/debug imports
DAreRodz ec9574a
Remove preact-markup dependency
DAreRodz 261b6de
Do not delete minimizer in webpack config
DAreRodz b0c6ed7
Remove toVdom options
DAreRodz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,17 @@ | ||
import { hydrate, createElement } from 'preact/compat'; | ||
import { createGlobal } from './utils'; | ||
import toVdom from './to-vdom'; | ||
import visitor from './visitor'; | ||
|
||
const blockViews = createGlobal('blockViews', new Map()); | ||
|
||
const components = Object.fromEntries( | ||
[...blockViews.entries()].map(([k, v]) => [k, v.Component]) | ||
); | ||
|
||
visitor.map = components; | ||
|
||
const dom = document.querySelector('.wp-site-blocks'); | ||
const vdom = toVdom(dom, visitor, createElement).props.children; | ||
|
||
setTimeout(() => console.log('hydrated', hydrate(vdom, dom)), 3000); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
export default function toVdom(node, visitor, h) { | ||
walk.visitor = visitor; | ||
walk.h = h; | ||
return walk(node); | ||
} | ||
|
||
function walk(n) { | ||
if (n.nodeType === 3) return n.data; | ||
if (n.nodeType !== 1) return null; | ||
let nodeName = String(n.nodeName).toLowerCase(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is there a possibility that a node name is not in lowercase? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the node is an Element, it always return the uppercase name (e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. I was using |
||
|
||
// Do not allow script tags (for now). | ||
if (nodeName === 'script') 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]; | ||
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import { h } from "preact"; | ||
import { matcherFromSource } from './utils'; | ||
|
||
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; | ||
|
||
const block = h(Component, { | ||
attributes: getAttributes(vNode, domNode), | ||
context: {}, | ||
blockProps: getBlockProps(vNode), | ||
children: getChildren(vNode), | ||
}); | ||
|
||
vNode.props = { | ||
...vNode.props, | ||
children: [block] | ||
}; | ||
} | ||
|
||
function getBlockProps(vNode) { | ||
const { class: className, style } = JSON.parse( | ||
vNode.props['data-wp-block-props'] | ||
); | ||
return { className, style: getStyleProp(style) }; | ||
} | ||
|
||
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) { | ||
if (!children?.length) return null; | ||
|
||
for (const child of children) { | ||
if (isChildrenWrapper(child)) return [child] || []; | ||
} | ||
|
||
// Try with the next nesting level. | ||
return getChildrenFromWrapper( | ||
[].concat(...children.map((child) => child?.props?.children || [])) | ||
); | ||
} | ||
|
||
function isChildrenWrapper(vNode) { | ||
return vNode.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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,42 +1,40 @@ | ||
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'; | ||
} | ||
}; | ||
|
||
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; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've left a note here explaining the fix for the Preact hydration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be fixed in Gutenberg itself.