This repository was archived by the owner on Jul 28, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
🎨 Add basic functionality #82
Merged
luisherranz
merged 2 commits into
main-wp-directives-plugin
from
Add-basic-full-vdom-functionality
Oct 17, 2022
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or 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 hidden or 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,11 @@ | ||
| import { useMemo } from 'preact/hooks'; | ||
| import { deepSignal } from './deepsignal'; | ||
| import { component } from './hooks'; | ||
|
|
||
| export default () => { | ||
| const WpContext = ({ children, data, context: { Provider } }) => { | ||
| const signals = useMemo(() => deepSignal(JSON.parse(data)), []); | ||
| return <Provider value={signals}>{children}</Provider>; | ||
| }; | ||
| component('wp-context', WpContext); | ||
| }; |
This file contains hidden or 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,42 @@ | ||
| import { signal } from '@preact/signals'; | ||
| import { knownSymbols, shouldWrap } from './utils'; | ||
|
|
||
| const proxyToSignals = new WeakMap(); | ||
| const objToProxy = new WeakMap(); | ||
| const returnSignal = /^\$/; | ||
|
|
||
| export const deepSignal = (obj) => new Proxy(obj, handlers); | ||
|
|
||
| const handlers = { | ||
| get(target, prop, receiver) { | ||
| if (typeof prop === 'symbol' && knownSymbols.has(prop)) | ||
| return Reflect.get(target, prop, receiver); | ||
| const shouldReturnSignal = returnSignal.test(prop); | ||
| const key = shouldReturnSignal ? prop.replace(returnSignal, '') : prop; | ||
| if (!proxyToSignals.has(receiver)) | ||
| proxyToSignals.set(receiver, new Map()); | ||
| const signals = proxyToSignals.get(receiver); | ||
| if (!signals.has(key)) { | ||
| let val = Reflect.get(target, key, receiver); | ||
| if (typeof val === 'object' && val !== null && shouldWrap(val)) | ||
| val = new Proxy(val, handlers); | ||
| signals.set(key, signal(val)); | ||
| } | ||
| return returnSignal ? signals.get(key) : signals.get(key).value; | ||
| }, | ||
|
|
||
| set(target, prop, val, receiver) { | ||
| let internal = val; | ||
| if (typeof val === 'object' && val !== null && shouldWrap(val)) { | ||
| if (!objToProxy.has(val)) | ||
| objToProxy.set(val, new Proxy(val, handlers)); | ||
| internal = objToProxy.get(val); | ||
| } | ||
| if (!proxyToSignals.has(receiver)) | ||
| proxyToSignals.set(receiver, new Map()); | ||
| const signals = proxyToSignals.get(receiver); | ||
| if (!signals.has(prop)) signals.set(prop, signal(internal)); | ||
| else signals.get(prop).value = internal; | ||
| return Reflect.set(target, prop, val, receiver); | ||
| }, | ||
| }; |
This file contains hidden or 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,84 @@ | ||
| import { useContext, useMemo } from 'preact/hooks'; | ||
| import { useSignalEffect } from '@preact/signals'; | ||
| import { directive } from './hooks'; | ||
| import { deepSignal } from './deepsignal'; | ||
| import { getCallback } from './utils'; | ||
|
|
||
| const raf = window.requestAnimationFrame; | ||
| // Until useSignalEffects is fixed: https://github.com/preactjs/signals/issues/228 | ||
| const tick = () => new Promise((r) => raf(() => raf(r))); | ||
|
|
||
| export default () => { | ||
| // wp-context | ||
| directive( | ||
| 'context', | ||
| ({ | ||
| directives: { context }, | ||
| props: { children }, | ||
| context: { Provider }, | ||
| }) => { | ||
| const signals = useMemo(() => deepSignal(context.default), []); | ||
| return <Provider value={signals}>{children}</Provider>; | ||
| } | ||
| ); | ||
|
|
||
| // wp-effect | ||
| directive( | ||
| 'effect', | ||
| ({ directives: { effect }, element, context: mainContext }) => { | ||
| const context = useContext(mainContext); | ||
| Object.values(effect).forEach((callback) => { | ||
| useSignalEffect(() => { | ||
| const cb = getCallback(callback); | ||
| cb({ context, tick, ref: element.ref.current }); | ||
| }); | ||
| }); | ||
| } | ||
| ); | ||
|
|
||
| // wp-on:[event] | ||
| directive('on', ({ directives: { on }, element, context: mainContext }) => { | ||
| const context = useContext(mainContext); | ||
| Object.entries(on).forEach(([name, callback]) => { | ||
| element.props[`on${name}`] = (event) => { | ||
| const cb = getCallback(callback); | ||
| cb({ context, event }); | ||
| }; | ||
| }); | ||
| }); | ||
|
|
||
| // wp-class:[classname] | ||
| directive( | ||
| 'class', | ||
| ({ | ||
| directives: { class: className }, | ||
| element, | ||
| context: mainContext, | ||
| }) => { | ||
| const context = useContext(mainContext); | ||
| Object.keys(className) | ||
| .filter((n) => n !== 'default') | ||
| .forEach((name) => { | ||
| const cb = getCallback(className[name]); | ||
| const result = cb({ context }); | ||
| if (!result) element.props.class.replace(name, ''); | ||
| else if (!element.props.class.includes(name)) | ||
| element.props.class += ` ${name}`; | ||
| }); | ||
| } | ||
| ); | ||
|
|
||
| // wp-bind:[attribute] | ||
| directive( | ||
| 'bind', | ||
| ({ directives: { bind }, element, context: mainContext }) => { | ||
| const context = useContext(mainContext); | ||
| Object.entries(bind) | ||
| .filter((n) => n !== 'default') | ||
| .forEach(([attribute, callback]) => { | ||
| const cb = getCallback(callback); | ||
| element.props[attribute] = cb({ context }); | ||
| }); | ||
| } | ||
| ); | ||
| }; |
This file contains hidden or 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,57 @@ | ||
| import { h, options, createContext } from 'preact'; | ||
| import { useRef } from 'preact/hooks'; | ||
|
|
||
| // Main context | ||
| const context = createContext({}); | ||
|
|
||
| // WordPress Directives. | ||
| const directives = {}; | ||
| export const directive = (name, cb) => { | ||
| directives[name] = cb; | ||
| }; | ||
|
|
||
| // WordPress Components. | ||
| const components = {}; | ||
| export const component = (name, Comp) => { | ||
| components[name] = Comp; | ||
| }; | ||
|
|
||
| // Directive wrapper. | ||
| const WpDirective = ({ type, wp, props: originalProps }) => { | ||
| const ref = useRef(null); | ||
| const element = h(type, { ...originalProps, ref, _wrapped: true }); | ||
| const props = { ...originalProps, children: element }; | ||
| const directiveArgs = { directives: wp, props, element, context }; | ||
|
|
||
| for (const d in wp) { | ||
| const wrapper = directives[d]?.(directiveArgs); | ||
| if (wrapper !== undefined) props.children = wrapper; | ||
| } | ||
|
|
||
| return props.children; | ||
| }; | ||
|
|
||
| // Preact Options Hook called each time a vnode is created. | ||
| const old = options.vnode; | ||
| options.vnode = (vnode) => { | ||
| const type = vnode.type; | ||
| const wp = vnode.props.wp; | ||
|
|
||
| if (typeof type === 'string' && type.startsWith('wp-')) { | ||
| vnode.type = components[type]; | ||
| vnode.props.context = context; | ||
| } | ||
|
|
||
| if (wp) { | ||
| const props = vnode.props; | ||
| delete props.wp; | ||
| if (!props._wrapped) { | ||
| vnode.props = { type: vnode.type, wp, props }; | ||
| vnode.type = WpDirective; | ||
| } else { | ||
| delete props._wrapped; | ||
| } | ||
| } | ||
|
|
||
| if (old) old(vnode); | ||
| }; |
This file contains hidden or 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 +1,24 @@ | ||
| console.log('Runtime loaded correctly.'); | ||
| import { hydrate } from 'preact'; | ||
| import registerDirectives from './directives'; | ||
| import registerComponents from './components'; | ||
| import toVdom from './vdom'; | ||
| import { createRootFragment } from './utils'; | ||
|
|
||
| /** | ||
| * Initialize the initial vDOM. | ||
| */ | ||
| document.addEventListener('DOMContentLoaded', async () => { | ||
| registerDirectives(); | ||
| registerComponents(); | ||
|
|
||
| // Create the root fragment to hydrate everything. | ||
| const rootFragment = createRootFragment( | ||
| document.documentElement, | ||
| document.body | ||
| ); | ||
|
|
||
| const vdom = toVdom(document.body); | ||
| hydrate(vdom, rootFragment); | ||
|
|
||
| console.log('hydrated!'); | ||
| }); | ||
This file contains hidden or 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,74 @@ | ||
| // For wrapperless hydration of document.body. | ||
| // See https://gist.github.com/developit/f4c67a2ede71dc2fab7f357f39cff28c | ||
| export const createRootFragment = (parent, replaceNode) => { | ||
| replaceNode = [].concat(replaceNode); | ||
| const s = replaceNode[replaceNode.length - 1].nextSibling; | ||
| function insert(c, r) { | ||
| parent.insertBefore(c, r || s); | ||
| } | ||
| return (parent.__k = { | ||
| nodeType: 1, | ||
| parentNode: parent, | ||
| firstChild: replaceNode[0], | ||
| childNodes: replaceNode, | ||
| insertBefore: insert, | ||
| appendChild: insert, | ||
| removeChild(c) { | ||
| parent.removeChild(c); | ||
| }, | ||
| }); | ||
| }; | ||
|
|
||
| // Helper function to await until the CPU is idle. | ||
| export const idle = () => | ||
| new Promise((resolve) => window.requestIdleCallback(resolve)); | ||
|
|
||
| export const knownSymbols = new Set( | ||
| Object.getOwnPropertyNames(Symbol) | ||
| .map((key) => Symbol[key]) | ||
| .filter((value) => typeof value === 'symbol') | ||
| ); | ||
| const supported = new Set([ | ||
| Object, | ||
| Array, | ||
| Int8Array, | ||
| Uint8Array, | ||
| Uint8ClampedArray, | ||
| Int16Array, | ||
| Uint16Array, | ||
| Int32Array, | ||
| Uint32Array, | ||
| Float32Array, | ||
| Float64Array, | ||
| ]); | ||
| export const shouldWrap = ({ constructor }) => { | ||
| const isBuiltIn = | ||
| typeof constructor === 'function' && | ||
| constructor.name in globalThis && | ||
| globalThis[constructor.name] === constructor; | ||
| return !isBuiltIn || supported.has(constructor); | ||
| }; | ||
|
|
||
| // Deep Merge | ||
| const isObject = (item) => | ||
| item && typeof item === 'object' && !Array.isArray(item); | ||
|
|
||
| export const deepMerge = (target, source) => { | ||
| if (isObject(target) && isObject(source)) { | ||
| for (const key in source) { | ||
| if (isObject(source[key])) { | ||
| if (!target[key]) Object.assign(target, { [key]: {} }); | ||
| deepMerge(target[key], source[key]); | ||
| } else { | ||
| Object.assign(target, { [key]: source[key] }); | ||
| } | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| // Get callback. | ||
| export const getCallback = (path) => { | ||
| let current = window.wpx; | ||
| path.split('.').forEach((p) => (current = current[p])); | ||
| return current; | ||
| }; |
This file contains hidden or 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,42 @@ | ||
| /** | ||
| * External dependencies | ||
| */ | ||
| import { h } from 'preact'; | ||
|
|
||
| // Recursive function that transfoms a DOM tree into vDOM. | ||
| export default function toVdom(node) { | ||
| const props = {}; | ||
| const attributes = node.attributes; | ||
| const wpDirectives = {}; | ||
| let hasWpDirectives = false; | ||
|
|
||
| if (node.nodeType === 3) return node.data; | ||
| if (node.nodeType === 8) return null; | ||
| if (node.localName === 'script') return h('script'); | ||
|
|
||
| for (let i = 0; i < attributes.length; i++) { | ||
| const name = attributes[i].name; | ||
| if (name.startsWith('wp-')) { | ||
| hasWpDirectives = true; | ||
| let val = attributes[i].value; | ||
| try { | ||
| val = JSON.parse(val); | ||
| } catch (e) {} | ||
| const [, prefix, suffix] = /wp-([^:]+):?(.*)$/.exec(name); | ||
| wpDirectives[prefix] = wpDirectives[prefix] || {}; | ||
| wpDirectives[prefix][suffix || 'default'] = val; | ||
| } else { | ||
| props[name] = attributes[i].value; | ||
| } | ||
| } | ||
|
|
||
| if (hasWpDirectives) props.wp = wpDirectives; | ||
|
|
||
| // Walk child nodes and return vDOM children. | ||
| const children = [].map.call(node.childNodes, toVdom).filter(exists); | ||
|
|
||
| return h(node.localName, props, children); | ||
| } | ||
|
|
||
| // Filter existing items. | ||
| const exists = (x) => x; |
This file contains hidden or 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
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.
Uh oh!
There was an error while loading. Please reload this page.