Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Update directives prefix to data-woo- #8316

Merged
merged 6 commits into from
Feb 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 8 additions & 12 deletions assets/js/interactivity/components.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
/**
* External dependencies
*/
import { useMemo, useContext } from 'preact/hooks';
import { deepSignal } from 'deepsignal';

/**
* Internal dependencies
*/
import { component } from './hooks';

export default () => {
const WpContext = ( { children, data, context: { Provider } } ) => {
// <wp-context>
const Context = ( { children, data, context: { Provider } } ) => {
const signals = useMemo(
() => deepSignal( JSON.parse( data ) ),
[ data ]
);
return <Provider value={ signals }>{ children }</Provider>;
};
component( 'wp-context', WpContext );
component( 'context', Context );

const WpShow = ( { children, when, evaluate, context } ) => {
// <wp-show>
const Show = ( { children, when, evaluate, context } ) => {
const contextValue = useContext( context );
if ( evaluate( when, { context: contextValue } ) ) {
return children;
} else {
return <template>{ children }</template>;
}
return <template>{ children }</template>;
};
component( 'wp-show', WpShow );
component( 'show', Show );
};
3 changes: 3 additions & 0 deletions assets/js/interactivity/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const cstMetaTagItemprop = 'woo-client-side-transitions';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we will remove it later once we switch to Product block-only navigation, but to match the upstream this should now be called woo-client-side-navigation.

The export was also renamed to csnMetaTagItemprop.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I think I've already included those changes in #8447, so I'm going to merge this PR and continue the development there.

export const componentPrefix = 'woo-';
export const directivePrefix = 'data-woo-';
42 changes: 30 additions & 12 deletions assets/js/interactivity/directives.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
/**
* External dependencies
*/

import { useContext, useMemo, useEffect } from 'preact/hooks';
import { useSignalEffect } from '@preact/signals';
import { deepSignal } from 'deepsignal';

/**
* Internal dependencies
*/
import { deepSignal, peek } from 'deepsignal';
import { directive } from './hooks';
import { prefetch, navigate, hasClientSideTransitions } from './router';

Expand All @@ -20,6 +12,25 @@ const tick = () => new Promise( ( r ) => raf( () => raf( r ) ) );
// Check if current page has client-side transitions enabled.
const clientSideTransitions = hasClientSideTransitions( document.head );

const isObject = ( item ) =>
item && typeof item === 'object' && ! Array.isArray( item );

const mergeDeepSignals = ( target, source ) => {
for ( const k in source ) {
if ( typeof peek( target, k ) === 'undefined' ) {
target[ `$${ k }` ] = source[ `$${ k }` ];
} else if (
isObject( peek( target, k ) ) &&
isObject( peek( source, k ) )
) {
mergeDeepSignals(
target[ `$${ k }` ].peek(),
source[ `$${ k }` ].peek()
);
}
}
};

export default () => {
// wp-context
directive(
Expand All @@ -29,10 +40,17 @@ export default () => {
context: { default: context },
},
props: { children },
context: { Provider },
context: inherited,
} ) => {
const signals = useMemo( () => deepSignal( context ), [ context ] );
return <Provider value={ signals }>{ children }</Provider>;
const { Provider } = inherited;
const inheritedValue = useContext( inherited );
const value = useMemo( () => {
const localValue = deepSignal( context );
mergeDeepSignals( localValue, inheritedValue );
return localValue;
}, [ context, inheritedValue ] );

return <Provider value={ value }>{ children }</Provider>;
}
);

Expand Down
41 changes: 19 additions & 22 deletions assets/js/interactivity/hooks.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
/**
* External dependencies
*/
import { h, options, createContext } from 'preact';
import { useRef } from 'preact/hooks';

/**
* Internal dependencies
*/
import { store } from './wpx';
import { componentPrefix } from './constants';

// Main context.
const context = createContext( {} );

// WordPress Directives.
const directives = {};
const directiveMap = {};
export const directive = ( name, cb ) => {
directives[ name ] = cb;
directiveMap[ name ] = cb;
};

// WordPress Components.
const components = {};
const componentMap = {};
export const component = ( name, Comp ) => {
components[ name ] = Comp;
componentMap[ name ] = Comp;
};

// Resolve the path to some property of the wpx object.
Expand All @@ -46,15 +40,15 @@ const getEvaluate =
};

// Directive wrapper.
const WpDirective = ( { type, wp, props: originalProps } ) => {
const Directive = ( { type, directives, props: originalProps } ) => {
const ref = useRef( null );
const element = h( type, { ...originalProps, ref, _wrapped: true } );
const props = { ...originalProps, children: element };
const evaluate = getEvaluate( { ref: ref.current } );
const directiveArgs = { directives: wp, props, element, context, evaluate };
const directiveArgs = { directives, props, element, context, evaluate };

for ( const d in wp ) {
const wrapper = directives[ d ]?.( directiveArgs );
for ( const d in directives ) {
const wrapper = directiveMap[ d ]?.( directiveArgs );
if ( wrapper !== undefined ) props.children = wrapper;
}

Expand All @@ -65,20 +59,23 @@ const WpDirective = ( { type, wp, props: originalProps } ) => {
const old = options.vnode;
options.vnode = ( vnode ) => {
const type = vnode.type;
const wp = vnode.props.wp;
const { directives } = vnode.props;

if ( typeof type === 'string' && type.startsWith( 'wp-' ) ) {
if (
typeof type === 'string' &&
type.slice( 0, componentPrefix.length ) === componentPrefix
) {
vnode.props.children = h(
components[ type ],
componentMap[ type.slice( componentPrefix.length ) ],
{ ...vnode.props, context, evaluate: getEvaluate() },
vnode.props.children
);
} else if ( wp ) {
} else if ( directives ) {
const props = vnode.props;
delete props.wp;
delete props.directives;
if ( ! props._wrapped ) {
vnode.props = { type: vnode.type, wp, props };
vnode.type = WpDirective;
vnode.props = { type: vnode.type, directives, props };
vnode.type = Directive;
} else {
delete props._wrapped;
}
Expand Down
3 changes: 0 additions & 3 deletions assets/js/interactivity/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
/**
* Internal dependencies
*/
import registerDirectives from './directives';
import registerComponents from './components';
import { init } from './router';
Expand Down
29 changes: 14 additions & 15 deletions assets/js/interactivity/router.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
/**
* External dependencies
*/
import { hydrate, render } from 'preact';

/**
* Internal dependencies
*/
import { toVdom, hydratedIslands } from './vdom';
import { createRootFragment } from './utils';
import { cstMetaTagItemprop, directivePrefix } from './constants';

// The root to render the vdom (document.body).
let rootFragment;
Expand All @@ -26,7 +20,7 @@ const cleanUrl = ( url ) => {
// Helper to check if a page has client-side transitions activated.
export const hasClientSideTransitions = ( dom ) =>
dom
.querySelector( "meta[itemprop='wp-client-side-transitions']" )
.querySelector( `meta[itemprop='${ cstMetaTagItemprop }']` )
?.getAttribute( 'content' ) === 'active';

// Fetch styles of a new page.
Expand Down Expand Up @@ -120,12 +114,17 @@ export const init = async () => {
Promise.resolve( { body, head } )
);
} else {
document.querySelectorAll( '[wp-island]' ).forEach( ( node ) => {
if ( ! hydratedIslands.has( node ) ) {
const fragment = createRootFragment( node.parentNode, node );
const vdom = toVdom( node );
hydrate( vdom, fragment );
}
} );
document
.querySelectorAll( `[${ directivePrefix }island]` )
.forEach( ( node ) => {
if ( ! hydratedIslands.has( node ) ) {
const fragment = createRootFragment(
node.parentNode,
node
);
const vdom = toVdom( node );
hydrate( vdom, fragment );
}
} );
}
};
28 changes: 15 additions & 13 deletions assets/js/interactivity/vdom.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
/**
* External dependencies
*/
import { h } from 'preact';
import { directivePrefix as p } from './constants';

const ignoreAttr = `${ p }ignore`;
const islandAttr = `${ p }island`;
const directiveParser = new RegExp( `${ p }([^:]+):?(.*)$` );

export const hydratedIslands = new WeakSet();

// Recursive function that transfoms a DOM tree into vDOM.
export function toVdom( node ) {
const props = {};
const { attributes, childNodes } = node;
const wpDirectives = {};
let hasWpDirectives = false;
const directives = {};
let hasDirectives = false;
let ignore = false;
let island = false;

Expand All @@ -22,20 +24,20 @@ export function toVdom( node ) {

for ( let i = 0; i < attributes.length; i++ ) {
const n = attributes[ i ].name;
if ( n[ 0 ] === 'w' && n[ 1 ] === 'p' && n[ 2 ] === '-' && n[ 3 ] ) {
if ( n === 'wp-ignore' ) {
if ( n[ p.length ] && n.slice( 0, p.length ) === p ) {
if ( n === ignoreAttr ) {
ignore = true;
} else if ( n === 'wp-island' ) {
} else if ( n === islandAttr ) {
island = true;
} else {
hasWpDirectives = true;
hasDirectives = true;
let val = attributes[ i ].value;
try {
val = JSON.parse( val );
} catch ( e ) {}
const [ , prefix, suffix ] = /wp-([^:]+):?(.*)$/.exec( n );
wpDirectives[ prefix ] = wpDirectives[ prefix ] || {};
wpDirectives[ prefix ][ suffix || 'default' ] = val;
const [ , prefix, suffix ] = directiveParser.exec( n );
directives[ prefix ] = directives[ prefix ] || {};
directives[ prefix ][ suffix || 'default' ] = val;
}
} else if ( n === 'ref' ) {
continue;
Expand All @@ -50,7 +52,7 @@ export function toVdom( node ) {
} );
if ( island ) hydratedIslands.add( node );

if ( hasWpDirectives ) props.wp = wpDirectives;
if ( hasDirectives ) props.directives = directives;

const children = [];
for ( let i = 0; i < childNodes.length; i++ ) {
Expand Down
3 changes: 0 additions & 3 deletions assets/js/interactivity/wpx.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
/**
* External dependencies
*/
import { deepSignal } from 'deepsignal';

const isObject = ( item ) =>
Expand Down
2 changes: 1 addition & 1 deletion bin/webpack-configs.js
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,7 @@ const getInteractivityAPIConfig = ( options = {} ) => {
runtime: './assets/js/interactivity',
},
output: {
filename: 'wp-directives-[name].js',
filename: 'woo-directives-[name].js',
path: path.resolve( __dirname, '../build/' ),
},
resolve: {
Expand Down
16 changes: 8 additions & 8 deletions woocommerce-gutenberg-products-block.php
Original file line number Diff line number Diff line change
Expand Up @@ -289,22 +289,22 @@ function woocommerce_blocks_plugin_outdated_notice() {

/**
* Register the Interactivity API scripts. These files are enqueued when a block
* defines `wp-directives-runtime` as a dependency.
* defines `woo-directives-runtime` as a dependency.
*/
function wp_directives_register_scripts() {
function woo_directives_register_scripts() {
wp_register_script(
'wp-directives-vendors',
plugins_url( 'build/wp-directives-vendors.js', __FILE__ ),
'woo-directives-vendors',
plugins_url( 'build/woo-directives-vendors.js', __FILE__ ),
array(),
'1.0.0',
true
);
wp_register_script(
'wp-directives-runtime',
plugins_url( 'build/wp-directives-runtime.js', __FILE__ ),
array( 'wp-directives-vendors' ),
'woo-directives-runtime',
plugins_url( 'build/woo-directives-runtime.js', __FILE__ ),
array( 'woo-directives-vendors' ),
'1.0.0',
true
);
}
add_action( 'init', 'wp_directives_register_scripts' );
add_action( 'init', 'woo_directives_register_scripts' );