From 5751344608ce23ace7b3c37a3ab7ffb2ba5a6cc0 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Fri, 21 Apr 2023 15:57:35 +0200 Subject: [PATCH 01/17] Add interactivity runtime Co-authored-by: David Arenas --- package-lock.json | 64 +++++-- packages/block-library/package.json | 3 + .../src/utils/interactivity/constants.js | 1 + .../src/utils/interactivity/directives.js | 178 ++++++++++++++++++ .../src/utils/interactivity/hooks.js | 76 ++++++++ .../src/utils/interactivity/hydration.js | 22 +++ .../src/utils/interactivity/index.js | 11 ++ .../src/utils/interactivity/store.js | 45 +++++ .../src/utils/interactivity/utils.js | 20 ++ .../src/utils/interactivity/vdom.js | 94 +++++++++ 10 files changed, 502 insertions(+), 12 deletions(-) create mode 100644 packages/block-library/src/utils/interactivity/constants.js create mode 100644 packages/block-library/src/utils/interactivity/directives.js create mode 100644 packages/block-library/src/utils/interactivity/hooks.js create mode 100644 packages/block-library/src/utils/interactivity/hydration.js create mode 100644 packages/block-library/src/utils/interactivity/index.js create mode 100644 packages/block-library/src/utils/interactivity/store.js create mode 100644 packages/block-library/src/utils/interactivity/utils.js create mode 100644 packages/block-library/src/utils/interactivity/vdom.js diff --git a/package-lock.json b/package-lock.json index 0b2f6bca0197a..1553d82a365d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7095,6 +7095,28 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==" }, + "@preact/signals": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@preact/signals/-/signals-1.1.3.tgz", + "integrity": "sha512-N09DuAVvc90bBZVRwD+aFhtGyHAmJLhS3IFoawO/bYJRcil4k83nBOchpCEoS0s5+BXBpahgp0Mjf+IOqP57Og==", + "requires": { + "@preact/signals-core": "^1.2.3" + } + }, + "@preact/signals-core": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.3.0.tgz", + "integrity": "sha512-M+M3ZOtd1dtV/uasyk4SZu1vbfEJ4NeENv0F7F12nijZYedB5wSgbtZcuACyssnTznhF4ctUyrR0dZHuHfyWKA==" + }, + "@preact/signals-react": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@preact/signals-react/-/signals-react-1.3.1.tgz", + "integrity": "sha512-YHWoGAT2Mmv2OGlGx7CCCbaLjAH/InV9ytGAR+esX8Y0HJmMAw51QlqGYOD5GPA5LwimV7Ht1x7KEIegDZIoxg==", + "requires": { + "@preact/signals-core": "^1.3.0", + "use-sync-external-store": "^1.2.0" + } + }, "@radix-ui/primitive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz", @@ -16862,6 +16884,7 @@ "version": "file:packages/block-library", "requires": { "@babel/runtime": "^7.16.0", + "@preact/signals": "^1.1.3", "@wordpress/a11y": "file:packages/a11y", "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/autop": "file:packages/autop", @@ -16894,12 +16917,14 @@ "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", + "deepsignal": "^1.3.0", "escape-html": "^1.0.3", "fast-average-color": "^9.1.1", "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21", "memize": "^1.1.0", "micromodal": "^0.4.10", + "preact": "^10.13.2", "remove-accents": "^0.4.2" } }, @@ -25510,7 +25535,7 @@ "array-ify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", "dev": true }, "array-includes": { @@ -30679,7 +30704,7 @@ "debuglog": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", + "integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==", "dev": true }, "decache": { @@ -30794,6 +30819,16 @@ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true }, + "deepsignal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/deepsignal/-/deepsignal-1.3.0.tgz", + "integrity": "sha512-tMh3g3F7Ka6gKALqu7uOzi/3Xm00mGWgWR3hp1GUzGGnTz2J926ime5aOe1haz233v4encyjTkZESr5R6hr8oQ==", + "requires": { + "@preact/signals": "^1.0.0", + "@preact/signals-core": "^1.0.0", + "@preact/signals-react": "^1.0.0" + } + }, "default-browser-id": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-1.0.4.tgz", @@ -35511,7 +35546,7 @@ "git-remote-origin-url": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", - "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", + "integrity": "sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==", "dev": true, "requires": { "gitconfiglocal": "^1.0.0", @@ -35558,7 +35593,7 @@ "gitconfiglocal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", - "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", + "integrity": "sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==", "dev": true, "requires": { "ini": "^1.3.2" @@ -36832,7 +36867,7 @@ "humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "dev": true, "requires": { "ms": "^2.0.0" @@ -37848,7 +37883,7 @@ "is-text-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", "dev": true, "requires": { "text-extensions": "^1.0.0" @@ -39621,7 +39656,7 @@ "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", "dev": true }, "jsprim": { @@ -40721,7 +40756,7 @@ "lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", - "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", + "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", "dev": true }, "lodash.isplainobject": { @@ -47601,6 +47636,11 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" }, + "preact": { + "version": "10.13.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.2.tgz", + "integrity": "sha512-q44QFLhOhty2Bd0Y46fnYW0gD/cbVM9dUVtNTDKPcdXSMA7jfY+Jpd6rk3GB0lcQss0z5s/6CmVP0Z/hV+g6pw==" + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -48219,7 +48259,7 @@ "promzard": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz", - "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", + "integrity": "sha512-JZeYqd7UAcHCwI+sTOeUDYkvEU+1bQ7iE0UT1MgB/tERkAPkesW46MrpIySzODi+owTjZtiF8Ay5j9m60KmMBw==", "dev": true, "requires": { "read": "1" @@ -48253,7 +48293,7 @@ "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true }, "protocols": { @@ -49810,7 +49850,7 @@ "read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", "dev": true, "requires": { "mute-stream": "~0.0.4" @@ -55167,7 +55207,7 @@ "temp-dir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", + "integrity": "sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==", "dev": true }, "terminal-link": { diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 9a34e8ae084fa..79552d1becb66 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -31,6 +31,7 @@ ], "dependencies": { "@babel/runtime": "^7.16.0", + "@preact/signals": "^1.1.3", "@wordpress/a11y": "file:../a11y", "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/autop": "file:../autop", @@ -63,12 +64,14 @@ "change-case": "^4.1.2", "classnames": "^2.3.1", "colord": "^2.7.0", + "deepsignal": "^1.3.0", "escape-html": "^1.0.3", "fast-average-color": "^9.1.1", "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21", "memize": "^1.1.0", "micromodal": "^0.4.10", + "preact": "^10.13.2", "remove-accents": "^0.4.2" }, "peerDependencies": { diff --git a/packages/block-library/src/utils/interactivity/constants.js b/packages/block-library/src/utils/interactivity/constants.js new file mode 100644 index 0000000000000..f462753c9f817 --- /dev/null +++ b/packages/block-library/src/utils/interactivity/constants.js @@ -0,0 +1 @@ +export const directivePrefix = 'data-wp-'; diff --git a/packages/block-library/src/utils/interactivity/directives.js b/packages/block-library/src/utils/interactivity/directives.js new file mode 100644 index 0000000000000..07874d4f91d07 --- /dev/null +++ b/packages/block-library/src/utils/interactivity/directives.js @@ -0,0 +1,178 @@ +/** + * External dependencies + */ +import { useContext, useMemo, useEffect } from 'preact/hooks'; +import { useSignalEffect } from '@preact/signals'; +import { deepSignal, peek } from 'deepsignal'; +/** + * Internal dependencies + */ +import { directive } from './hooks'; + +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 () => { + // data-wp-context + directive( + 'context', + ( { + directives: { + context: { default: context }, + }, + props: { children }, + context: inherited, + } ) => { + const { Provider } = inherited; + const inheritedValue = useContext( inherited ); + const value = useMemo( () => { + const localValue = deepSignal( context ); + mergeDeepSignals( localValue, inheritedValue ); + return localValue; + }, [ context, inheritedValue ] ); + + return { children }; + } + ); + + // data-wp-effect.[name] + directive( 'effect', ( { directives: { effect }, context, evaluate } ) => { + const contextValue = useContext( context ); + Object.values( effect ).forEach( ( path ) => { + useSignalEffect( () => { + return evaluate( path, { context: contextValue } ); + } ); + } ); + } ); + + // data-wp-init.[name] + directive( 'init', ( { directives: { init }, context, evaluate } ) => { + const contextValue = useContext( context ); + Object.values( init ).forEach( ( path ) => { + useEffect( () => { + return evaluate( path, { context: contextValue } ); + }, [] ); + } ); + } ); + + // data-wp-on.[event] + directive( 'on', ( { directives: { on }, element, evaluate, context } ) => { + const contextValue = useContext( context ); + Object.entries( on ).forEach( ( [ name, path ] ) => { + element.props[ `on${ name }` ] = ( event ) => { + return evaluate( path, { event, context: contextValue } ); + }; + } ); + } ); + + // data-wp-class.[classname] + directive( + 'class', + ( { + directives: { class: className }, + element, + evaluate, + context, + } ) => { + const contextValue = useContext( context ); + Object.keys( className ) + .filter( ( n ) => n !== 'default' ) + .forEach( ( name ) => { + const result = evaluate( className[ name ], { + className: name, + context: contextValue, + } ); + const currentClass = element.props.class || ''; + const classFinder = new RegExp( + `(^|\\s)${ name }(\\s|$)`, + 'g' + ); + if ( ! result ) + element.props.class = currentClass + .replace( classFinder, ' ' ) + .trim(); + else if ( ! classFinder.test( currentClass ) ) + element.props.class = currentClass + ? `${ currentClass } ${ name }` + : name; + + useEffect( () => { + // This seems necessary because Preact doesn't change the class + // names on the hydration, so we have to do it manually. It doesn't + // need deps because it only needs to do it the first time. + if ( ! result ) { + element.ref.current.classList.remove( name ); + } else { + element.ref.current.classList.add( name ); + } + }, [] ); + } ); + } + ); + + // data-wp-bind.[attribute] + directive( + 'bind', + ( { directives: { bind }, element, context, evaluate } ) => { + const contextValue = useContext( context ); + Object.entries( bind ) + .filter( ( n ) => n !== 'default' ) + .forEach( ( [ attribute, path ] ) => { + const result = evaluate( path, { + context: contextValue, + } ); + element.props[ attribute ] = result; + + useEffect( () => { + // This seems necessary because Preact doesn't change the attributes + // on the hydration, so we have to do it manually. It doesn't need + // deps because it only needs to do it the first time. + if ( result === false ) { + element.ref.current.removeAttribute( attribute ); + } else { + element.ref.current.setAttribute( + attribute, + result === true ? '' : result + ); + } + }, [] ); + } ); + } + ); + + // data-wp-ignore + directive( + 'ignore', + ( { + element: { + type: Type, + props: { innerHTML, ...rest }, + }, + } ) => { + // Preserve the initial inner HTML. + const cached = useMemo( () => innerHTML, [] ); + return ( + + ); + } + ); +}; diff --git a/packages/block-library/src/utils/interactivity/hooks.js b/packages/block-library/src/utils/interactivity/hooks.js new file mode 100644 index 0000000000000..ca3bd20964d51 --- /dev/null +++ b/packages/block-library/src/utils/interactivity/hooks.js @@ -0,0 +1,76 @@ +/** + * External dependencies + */ +import { h, options, createContext } from 'preact'; +import { useRef, useMemo } from 'preact/hooks'; +/** + * Internal dependencies + */ +import { rawStore as store } from './store'; + +// Main context. +const context = createContext( {} ); + +// WordPress Directives. +const directiveMap = {}; +export const directive = ( name, cb ) => { + directiveMap[ name ] = cb; +}; + +// Resolve the path to some property of the store object. +const resolve = ( path, ctx ) => { + // If path starts with !, remove it and save a flag. + const hasNegationOperator = + path[ 0 ] === '!' && !! ( path = path.slice( 1 ) ); + let current = { ...store, context: ctx }; + path.split( '.' ).forEach( ( p ) => ( current = current[ p ] ) ); + return hasNegationOperator ? ! current : current; +}; + +// Generate the evaluate function. +const getEvaluate = + ( { ref } = {} ) => + ( path, extraArgs = {} ) => { + const value = resolve( path, extraArgs.context ); + return typeof value === 'function' + ? value( { + ref: ref.current, + ...store, + ...extraArgs, + } ) + : value; + }; + +// Directive wrapper. +const Directive = ( { type, directives, props: originalProps } ) => { + const ref = useRef( null ); + const element = h( type, { ...originalProps, ref } ); + const props = { ...originalProps, children: element }; + const evaluate = useMemo( () => getEvaluate( { ref } ), [] ); + const directiveArgs = { directives, props, element, context, evaluate }; + + for ( const d in directives ) { + const wrapper = directiveMap[ 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 ) => { + if ( vnode.props.__directives ) { + const props = vnode.props; + const directives = props.__directives; + delete props.__directives; + vnode.props = { + type: vnode.type, + directives, + props, + }; + vnode.type = Directive; + } + + if ( old ) old( vnode ); +}; diff --git a/packages/block-library/src/utils/interactivity/hydration.js b/packages/block-library/src/utils/interactivity/hydration.js new file mode 100644 index 0000000000000..2fc34eeb64b9b --- /dev/null +++ b/packages/block-library/src/utils/interactivity/hydration.js @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import { hydrate } from 'preact'; +/** + * Internal dependencies + */ +import { toVdom, hydratedIslands } from './vdom'; +import { createRootFragment } from './utils'; +import { directivePrefix } from './constants'; + +export const init = async () => { + document + .querySelectorAll( `[${ directivePrefix }island]` ) + .forEach( ( node ) => { + if ( ! hydratedIslands.has( node ) ) { + const fragment = createRootFragment( node.parentNode, node ); + const vdom = toVdom( node ); + hydrate( vdom, fragment ); + } + } ); +}; diff --git a/packages/block-library/src/utils/interactivity/index.js b/packages/block-library/src/utils/interactivity/index.js new file mode 100644 index 0000000000000..683be59c3fca8 --- /dev/null +++ b/packages/block-library/src/utils/interactivity/index.js @@ -0,0 +1,11 @@ +/** + * Internal dependencies + */ +import registerDirectives from './directives'; +import { init } from './hydration'; + +/** + * Initialize the Interactivity API. + */ +registerDirectives(); +init(); diff --git a/packages/block-library/src/utils/interactivity/store.js b/packages/block-library/src/utils/interactivity/store.js new file mode 100644 index 0000000000000..d11af90135201 --- /dev/null +++ b/packages/block-library/src/utils/interactivity/store.js @@ -0,0 +1,45 @@ +/** + * External dependencies + */ +import { deepSignal } from 'deepsignal'; + +const isObject = ( item ) => + item && typeof item === 'object' && ! Array.isArray( item ); + +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 ] } ); + } + } + } +}; + +const getSerializedState = () => { + // TODO: change the store tag ID for a better one. + const storeTag = document.querySelector( + `script[type="application/json"]#store` + ); + if ( ! storeTag ) return {}; + try { + const { state } = JSON.parse( storeTag.textContent ); + if ( isObject( state ) ) return state; + throw Error( 'Parsed state is not an object' ); + } catch ( e ) { + // eslint-disable-next-line no-console + console.log( e ); + } + return {}; +}; + +const rawState = getSerializedState(); +export const rawStore = { state: deepSignal( rawState ) }; + +export const store = ( { state, ...block } ) => { + deepMerge( rawStore, block ); + deepMerge( rawState, state ); +}; diff --git a/packages/block-library/src/utils/interactivity/utils.js b/packages/block-library/src/utils/interactivity/utils.js new file mode 100644 index 0000000000000..48c50fc537c05 --- /dev/null +++ b/packages/block-library/src/utils/interactivity/utils.js @@ -0,0 +1,20 @@ +// For wrapperless hydration. +// 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 ); + }, + } ); +}; diff --git a/packages/block-library/src/utils/interactivity/vdom.js b/packages/block-library/src/utils/interactivity/vdom.js new file mode 100644 index 0000000000000..07640319b88a8 --- /dev/null +++ b/packages/block-library/src/utils/interactivity/vdom.js @@ -0,0 +1,94 @@ +/** + * External dependencies + */ +import { h } from 'preact'; +/** + * Internal dependencies + */ +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 transforms a DOM tree into vDOM. +export function toVdom( root ) { + const treeWalker = document.createTreeWalker( + root, + 205 // ELEMENT + TEXT + COMMENT + CDATA_SECTION + PROCESSING_INSTRUCTION + ); + + function walk( node ) { + const { attributes, nodeType } = node; + + if ( nodeType === 3 ) return [ node.data ]; + if ( nodeType === 4 ) { + const next = treeWalker.nextSibling(); + node.replaceWith( new window.Text( node.nodeValue ) ); + return [ node.nodeValue, next ]; + } + if ( nodeType === 8 || nodeType === 7 ) { + const next = treeWalker.nextSibling(); + node.remove(); + return [ null, next ]; + } + + const props = {}; + const children = []; + const directives = {}; + let hasDirectives = false; + let ignore = false; + let island = false; + + for ( let i = 0; i < attributes.length; i++ ) { + const n = attributes[ i ].name; + if ( n[ p.length ] && n.slice( 0, p.length ) === p ) { + if ( n === ignoreAttr ) { + ignore = true; + } else if ( n === islandAttr ) { + island = true; + } else { + hasDirectives = true; + let val = attributes[ i ].value; + try { + val = JSON.parse( val ); + } catch ( e ) {} + const [ , prefix, suffix ] = directiveParser.exec( n ); + directives[ prefix ] = directives[ prefix ] || {}; + directives[ prefix ][ suffix || 'default' ] = val; + } + } else if ( n === 'ref' ) { + continue; + } + props[ n ] = attributes[ i ].value; + } + + if ( ignore && ! island ) + return [ + h( node.localName, { + ...props, + innerHTML: node.innerHTML, + __directives: { ignore: true }, + } ), + ]; + if ( island ) hydratedIslands.add( node ); + + if ( hasDirectives ) props.__directives = directives; + + let child = treeWalker.firstChild(); + if ( child ) { + while ( child ) { + const [ vnode, nextChild ] = walk( child ); + if ( vnode ) children.push( vnode ); + child = nextChild || treeWalker.nextSibling(); + } + treeWalker.parentNode(); + } + + return [ h( node.localName, props, children ) ]; + } + + return walk( treeWalker.currentNode ); +} From e8201c258fc8fc32c0fc3a3a14519b76aec1e2a2 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Fri, 21 Apr 2023 16:11:32 +0200 Subject: [PATCH 02/17] Add it to the image block This is still pretty basic, just to check that it works. Co-authored-by: David Arenas --- packages/block-library/src/image/block.json | 3 ++- packages/block-library/src/image/index.php | 7 ++++++- packages/block-library/src/image/view.js | 16 ++++++++++++++++ .../src/utils/interactivity/index.js | 10 ++++++++-- 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 packages/block-library/src/image/view.js diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index 92931455c1144..3a5c8a18c9af7 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -118,5 +118,6 @@ { "name": "rounded", "label": "Rounded" } ], "editorStyle": "wp-block-image-editor", - "style": "wp-block-image" + "style": "wp-block-image", + "viewScript": [ "file:./view.min.js" ] } diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index e05939a4d0fea..b775a6d6e085c 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -15,7 +15,10 @@ */ function render_block_core_image( $attributes, $content ) { $processor = new WP_HTML_Tag_Processor( $content ); + $processor->next_tag( 'figure' ); + $processor->set_attribute( 'data-wp-island', '' ); $processor->next_tag( 'img' ); + $processor->set_attribute( 'data-wp-effect', 'effects.alert' ); if ( $processor->get_attribute( 'src' ) === null ) { return ''; @@ -27,8 +30,10 @@ function render_block_core_image( $attributes, $content ) { // which now wraps Image Blocks within innerBlocks. // The data-id attribute is added in a core/gallery `render_block_data` hook. $processor->set_attribute( 'data-id', $attributes['data-id'] ); - $content = $processor->get_updated_html(); } + + $content = $processor->get_updated_html(); + return $content; } diff --git a/packages/block-library/src/image/view.js b/packages/block-library/src/image/view.js new file mode 100644 index 0000000000000..0b4b69df466e5 --- /dev/null +++ b/packages/block-library/src/image/view.js @@ -0,0 +1,16 @@ +/** + * Internal dependencies + */ +import { store } from '../utils/interactivity/store'; +import init from '../utils/interactivity'; + +store( { + effects: { + alert: () => { + // eslint-disable-next-line no-console + console.log( 'image hydrated!' ); + }, + }, +} ); + +init(); diff --git a/packages/block-library/src/utils/interactivity/index.js b/packages/block-library/src/utils/interactivity/index.js index 683be59c3fca8..351c0bc5424f3 100644 --- a/packages/block-library/src/utils/interactivity/index.js +++ b/packages/block-library/src/utils/interactivity/index.js @@ -7,5 +7,11 @@ import { init } from './hydration'; /** * Initialize the Interactivity API. */ -registerDirectives(); -init(); +export default () => { + window.addEventListener( 'DOMContentLoaded', () => { + registerDirectives(); + init(); + // eslint-disable-next-line no-console + console.log( 'hydrated!' ); + } ); +}; From df844f080486d00f4867aeacc5193735521f7859 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Fri, 21 Apr 2023 16:53:24 +0200 Subject: [PATCH 03/17] Add a separate webpack config Co-authored-by: David Arenas --- packages/block-library/src/image/block.json | 3 +- packages/block-library/src/image/index.php | 5 + .../block-library/src/image/interactivity.js | 13 + packages/block-library/src/image/view.js | 16 - .../src/utils/interactivity/index.js | 15 +- tools/webpack/blocks.js | 300 +++++++++++------- webpack.config.js | 2 +- 7 files changed, 212 insertions(+), 142 deletions(-) create mode 100644 packages/block-library/src/image/interactivity.js delete mode 100644 packages/block-library/src/image/view.js diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index 3a5c8a18c9af7..92931455c1144 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -118,6 +118,5 @@ { "name": "rounded", "label": "Rounded" } ], "editorStyle": "wp-block-image-editor", - "style": "wp-block-image", - "viewScript": [ "file:./view.min.js" ] + "style": "wp-block-image" } diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index b775a6d6e085c..db69a61dc1026 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -14,9 +14,14 @@ * @return string Returns the block content with the data-id attribute added. */ function render_block_core_image( $attributes, $content ) { + wp_enqueue_script( 'interactivity', plugins_url( '../interactive-blocks/interactivity.min.js', __FILE__ ) ); + wp_enqueue_script( 'interactivity-vendors', plugins_url( '../interactive-blocks/vendors.min.js', __FILE__ ) ); + wp_enqueue_script( 'interactivity-image', plugins_url( '../interactive-blocks/image.min.js', __FILE__ ) ); + $processor = new WP_HTML_Tag_Processor( $content ); $processor->next_tag( 'figure' ); $processor->set_attribute( 'data-wp-island', '' ); + $processor->set_attribute( 'data-wp-context', '{ "text": "image hydrated" }' ); $processor->next_tag( 'img' ); $processor->set_attribute( 'data-wp-effect', 'effects.alert' ); diff --git a/packages/block-library/src/image/interactivity.js b/packages/block-library/src/image/interactivity.js new file mode 100644 index 0000000000000..dd29ee4a7d368 --- /dev/null +++ b/packages/block-library/src/image/interactivity.js @@ -0,0 +1,13 @@ +/** + * Internal dependencies + */ +import { store } from '../utils/interactivity'; + +store( { + effects: { + alert: ( { context } ) => { + // eslint-disable-next-line no-console + console.log( context.text ); + }, + }, +} ); diff --git a/packages/block-library/src/image/view.js b/packages/block-library/src/image/view.js deleted file mode 100644 index 0b4b69df466e5..0000000000000 --- a/packages/block-library/src/image/view.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Internal dependencies - */ -import { store } from '../utils/interactivity/store'; -import init from '../utils/interactivity'; - -store( { - effects: { - alert: () => { - // eslint-disable-next-line no-console - console.log( 'image hydrated!' ); - }, - }, -} ); - -init(); diff --git a/packages/block-library/src/utils/interactivity/index.js b/packages/block-library/src/utils/interactivity/index.js index 351c0bc5424f3..b09a9669b6c07 100644 --- a/packages/block-library/src/utils/interactivity/index.js +++ b/packages/block-library/src/utils/interactivity/index.js @@ -3,15 +3,14 @@ */ import registerDirectives from './directives'; import { init } from './hydration'; +export { store } from './store'; /** * Initialize the Interactivity API. */ -export default () => { - window.addEventListener( 'DOMContentLoaded', () => { - registerDirectives(); - init(); - // eslint-disable-next-line no-console - console.log( 'hydrated!' ); - } ); -}; +window.addEventListener( 'DOMContentLoaded', () => { + registerDirectives(); + init(); + // eslint-disable-next-line no-console + console.log( 'Interactivity API started' ); +} ); diff --git a/tools/webpack/blocks.js b/tools/webpack/blocks.js index f2c30984141fb..96d78776655ac 100644 --- a/tools/webpack/blocks.js +++ b/tools/webpack/blocks.js @@ -74,134 +74,204 @@ const createEntrypoints = () => { }, {} ); }; -module.exports = { - ...baseConfig, - name: 'blocks', - entry: createEntrypoints(), - output: { - devtoolNamespace: 'wp', - filename: './build/block-library/blocks/[name].min.js', - path: join( __dirname, '..', '..' ), - }, - plugins: [ - ...plugins, - new DependencyExtractionWebpackPlugin( { injectPolyfill: false } ), - new CopyWebpackPlugin( { - patterns: [].concat( - [ - 'style', - 'style-rtl', - 'editor', - 'editor-rtl', - 'theme', - 'theme-rtl', - ].map( ( filename ) => ( { - from: `./packages/block-library/build-style/*/${ filename }.css`, - to( { absoluteFilename } ) { - const [ , dirname ] = absoluteFilename.match( - new RegExp( - `([\\w-]+)${ escapeRegExp( - sep - ) }${ filename }\\.css$` - ) - ); - - return join( - 'build/block-library/blocks', - dirname, - filename + '.css' - ); - }, - transform: stylesTransform, - } ) ), - Object.entries( { - './packages/block-library/src/': - 'build/block-library/blocks/', - './packages/edit-widgets/src/blocks/': - 'build/edit-widgets/blocks/', - './packages/widgets/src/blocks/': 'build/widgets/blocks/', - } ).flatMap( ( [ from, to ] ) => [ - { - from: `${ from }/**/index.php`, +module.exports = [ + { + ...baseConfig, + name: 'blocks', + entry: createEntrypoints(), + output: { + devtoolNamespace: 'wp', + filename: './build/block-library/blocks/[name].min.js', + path: join( __dirname, '..', '..' ), + }, + plugins: [ + ...plugins, + new DependencyExtractionWebpackPlugin( { injectPolyfill: false } ), + new CopyWebpackPlugin( { + patterns: [].concat( + [ + 'style', + 'style-rtl', + 'editor', + 'editor-rtl', + 'theme', + 'theme-rtl', + ].map( ( filename ) => ( { + from: `./packages/block-library/build-style/*/${ filename }.css`, to( { absoluteFilename } ) { const [ , dirname ] = absoluteFilename.match( new RegExp( `([\\w-]+)${ escapeRegExp( sep - ) }index\\.php$` + ) }${ filename }\\.css$` ) ); - return join( to, `${ dirname }.php` ); + return join( + 'build/block-library/blocks', + dirname, + filename + '.css' + ); }, - transform: ( content ) => { - const prefix = 'gutenberg_'; - content = content.toString(); + transform: stylesTransform, + } ) ), + Object.entries( { + './packages/block-library/src/': + 'build/block-library/blocks/', + './packages/edit-widgets/src/blocks/': + 'build/edit-widgets/blocks/', + './packages/widgets/src/blocks/': + 'build/widgets/blocks/', + } ).flatMap( ( [ from, to ] ) => [ + { + from: `${ from }/**/index.php`, + to( { absoluteFilename } ) { + const [ , dirname ] = absoluteFilename.match( + new RegExp( + `([\\w-]+)${ escapeRegExp( + sep + ) }index\\.php$` + ) + ); - // Within content, search and prefix any function calls from - // `prefixFunctions` list. This is needed because some functions - // are called inside block files, but have been declared elsewhere. - // So with the rename we can call Gutenberg override functions, but the - // block will still call the core function when updates are back ported. - content = content.replace( - new RegExp( prefixFunctions.join( '|' ), 'g' ), - ( match ) => - `${ prefix }${ match.replace( - /^wp_/, - '' - ) }` - ); + return join( to, `${ dirname }.php` ); + }, + transform: ( content ) => { + const prefix = 'gutenberg_'; + content = content.toString(); - // Within content, search for any function definitions. For - // each, replace every other reference to it in the file. - return ( - Array.from( - content.matchAll( - /^\s*function ([^\(]+)/gm - ) - ) - .reduce( ( result, [ , functionName ] ) => { - // Prepend the Gutenberg prefix, substituting any - // other core prefix (e.g. "wp_"). - return result.replace( - new RegExp( functionName, 'g' ), - ( match ) => - prefix + - match.replace( /^wp_/, '' ) - ); - }, content ) - // The core blocks override procedure takes place in - // the init action default priority to ensure that core - // blocks would have been registered already. Since the - // blocks implementations occur at the default priority - // and due to WordPress hooks behavior not considering - // mutations to the same priority during another's - // callback, the Gutenberg build blocks are modified - // to occur at a later priority. - .replace( - /(add_action\(\s*'init',\s*'gutenberg_register_block_[^']+'(?!,))/, - '$1, 20' + // Within content, search and prefix any function calls from + // `prefixFunctions` list. This is needed because some functions + // are called inside block files, but have been declared elsewhere. + // So with the rename we can call Gutenberg override functions, but the + // block will still call the core function when updates are back ported. + content = content.replace( + new RegExp( + prefixFunctions.join( '|' ), + 'g' + ), + ( match ) => + `${ prefix }${ match.replace( + /^wp_/, + '' + ) }` + ); + + // Within content, search for any function definitions. For + // each, replace every other reference to it in the file. + return ( + Array.from( + content.matchAll( + /^\s*function ([^\(]+)/gm + ) ) - ); + .reduce( + ( result, [ , functionName ] ) => { + // Prepend the Gutenberg prefix, substituting any + // other core prefix (e.g. "wp_"). + return result.replace( + new RegExp( + functionName, + 'g' + ), + ( match ) => + prefix + + match.replace( + /^wp_/, + '' + ) + ); + }, + content + ) + // The core blocks override procedure takes place in + // the init action default priority to ensure that core + // blocks would have been registered already. Since the + // blocks implementations occur at the default priority + // and due to WordPress hooks behavior not considering + // mutations to the same priority during another's + // callback, the Gutenberg build blocks are modified + // to occur at a later priority. + .replace( + /(add_action\(\s*'init',\s*'gutenberg_register_block_[^']+'(?!,))/, + '$1, 20' + ) + ); + }, + noErrorOnMissing: true, }, - noErrorOnMissing: true, - }, - { - from: `${ from }/*/block.json`, - to( { absoluteFilename } ) { - const [ , dirname ] = absoluteFilename.match( - new RegExp( - `([\\w-]+)${ escapeRegExp( - sep - ) }block\\.json$` - ) - ); + { + from: `${ from }/*/block.json`, + to( { absoluteFilename } ) { + const [ , dirname ] = absoluteFilename.match( + new RegExp( + `([\\w-]+)${ escapeRegExp( + sep + ) }block\\.json$` + ) + ); - return join( to, dirname, 'block.json' ); + return join( to, dirname, 'block.json' ); + }, }, + ] ) + ), + } ), + ].filter( Boolean ), + }, + { + entry: { + interactivity: + './packages/block-library/src/utils/interactivity/index.js', + image: './packages/block-library/src/image/interactivity.js', + }, + output: { + devtoolNamespace: 'wp', + filename: './build/block-library/interactive-blocks/[name].min.js', + path: join( __dirname, '..', '..' ), + }, + optimization: { + runtimeChunk: { + name: 'vendors', + }, + splitChunks: { + cacheGroups: { + vendors: { + test: /[\\/]node_modules[\\/]/, + name: 'vendors', + minSize: 0, + chunks: 'all', }, - ] ) - ), - } ), - ].filter( Boolean ), -}; + }, + }, + }, + module: { + rules: [ + { + test: /\.(j|t)sx?$/, + exclude: /node_modules/, + use: [ + { + loader: require.resolve( 'babel-loader' ), + options: { + cacheDirectory: + process.env.BABEL_CACHE_DIRECTORY || true, + babelrc: false, + configFile: false, + presets: [ + [ + '@babel/preset-react', + { + runtime: 'automatic', + importSource: 'preact', + }, + ], + ], + }, + }, + ], + }, + ], + }, + }, +]; diff --git a/webpack.config.js b/webpack.config.js index 9a29ed7782268..f1c5ce803adc1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,4 +5,4 @@ const blocksConfig = require( './tools/webpack/blocks' ); const developmentConfigs = require( './tools/webpack/development' ); const packagesConfig = require( './tools/webpack/packages' ); -module.exports = [ blocksConfig, packagesConfig, ...developmentConfigs ]; +module.exports = [ ...blocksConfig, packagesConfig, ...developmentConfigs ]; From d883391f72390bba7f415585b1d9c806745aaa5f Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Fri, 21 Apr 2023 17:20:41 +0200 Subject: [PATCH 04/17] Make sure the runtime is imported only once Co-authored-by: David Arenas --- packages/block-library/src/image/index.php | 1 + .../block-library/src/image/interactivity.js | 4 +++- .../block-library/src/image/interactivity2.js | 15 +++++++++++++++ .../src/utils/interactivity/index.js | 19 ++++++++++++------- tools/webpack/blocks.js | 12 +++++++++--- 5 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 packages/block-library/src/image/interactivity2.js diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index db69a61dc1026..b97dff5f4f6ea 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -17,6 +17,7 @@ function render_block_core_image( $attributes, $content ) { wp_enqueue_script( 'interactivity', plugins_url( '../interactive-blocks/interactivity.min.js', __FILE__ ) ); wp_enqueue_script( 'interactivity-vendors', plugins_url( '../interactive-blocks/vendors.min.js', __FILE__ ) ); wp_enqueue_script( 'interactivity-image', plugins_url( '../interactive-blocks/image.min.js', __FILE__ ) ); + wp_enqueue_script( 'interactivity-image-2', plugins_url( '../interactive-blocks/image2.min.js', __FILE__ ) ); $processor = new WP_HTML_Tag_Processor( $content ); $processor->next_tag( 'figure' ); diff --git a/packages/block-library/src/image/interactivity.js b/packages/block-library/src/image/interactivity.js index dd29ee4a7d368..fc56b5069d300 100644 --- a/packages/block-library/src/image/interactivity.js +++ b/packages/block-library/src/image/interactivity.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { store } from '../utils/interactivity'; +import { store, init } from '../utils/interactivity'; store( { effects: { @@ -11,3 +11,5 @@ store( { }, }, } ); + +init(); diff --git a/packages/block-library/src/image/interactivity2.js b/packages/block-library/src/image/interactivity2.js new file mode 100644 index 0000000000000..fc56b5069d300 --- /dev/null +++ b/packages/block-library/src/image/interactivity2.js @@ -0,0 +1,15 @@ +/** + * Internal dependencies + */ +import { store, init } from '../utils/interactivity'; + +store( { + effects: { + alert: ( { context } ) => { + // eslint-disable-next-line no-console + console.log( context.text ); + }, + }, +} ); + +init(); diff --git a/packages/block-library/src/utils/interactivity/index.js b/packages/block-library/src/utils/interactivity/index.js index b09a9669b6c07..6ff7a86127313 100644 --- a/packages/block-library/src/utils/interactivity/index.js +++ b/packages/block-library/src/utils/interactivity/index.js @@ -2,15 +2,20 @@ * Internal dependencies */ import registerDirectives from './directives'; -import { init } from './hydration'; +import { init as hydrate } from './hydration'; export { store } from './store'; /** * Initialize the Interactivity API. */ -window.addEventListener( 'DOMContentLoaded', () => { - registerDirectives(); - init(); - // eslint-disable-next-line no-console - console.log( 'Interactivity API started' ); -} ); +let started = false; +export const init = () => { + if ( ! started ) + window.addEventListener( 'DOMContentLoaded', () => { + registerDirectives(); + hydrate(); + // eslint-disable-next-line no-console + console.log( 'Interactivity API started' ); + } ); + started = true; +}; diff --git a/tools/webpack/blocks.js b/tools/webpack/blocks.js index 96d78776655ac..5bc27223b7600 100644 --- a/tools/webpack/blocks.js +++ b/tools/webpack/blocks.js @@ -221,9 +221,8 @@ module.exports = [ }, { entry: { - interactivity: - './packages/block-library/src/utils/interactivity/index.js', image: './packages/block-library/src/image/interactivity.js', + image2: './packages/block-library/src/image/interactivity2.js', }, output: { devtoolNamespace: 'wp', @@ -237,11 +236,18 @@ module.exports = [ splitChunks: { cacheGroups: { vendors: { - test: /[\\/]node_modules[\\/]/, name: 'vendors', + test: /[\\/]node_modules[\\/]/, minSize: 0, chunks: 'all', }, + interactivity: { + name: 'interactivity', + test: /[\\/]utils\/interactivity[\\/]/, + chunks: 'all', + minSize: 0, + priority: -10, + }, }, }, }, From b1ea5e3a76908f343f4f8aa292b7a6732e3d9bf1 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Fri, 21 Apr 2023 17:30:24 +0200 Subject: [PATCH 05/17] Use sideEffects instead of init Co-authored-by: David Arenas --- packages/block-library/package.json | 3 ++- .../block-library/src/image/interactivity.js | 4 +--- .../block-library/src/image/interactivity2.js | 4 +--- .../src/utils/interactivity/index.js | 17 ++++++----------- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 79552d1becb66..000f904a4f7d4 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -27,7 +27,8 @@ "sideEffects": [ "build-style/**", "src/**/*.scss", - "{src,build,build-module}/*/init.js" + "{src,build,build-module}/*/init.js", + "src/utils/interactivity/index.js" ], "dependencies": { "@babel/runtime": "^7.16.0", diff --git a/packages/block-library/src/image/interactivity.js b/packages/block-library/src/image/interactivity.js index fc56b5069d300..dd29ee4a7d368 100644 --- a/packages/block-library/src/image/interactivity.js +++ b/packages/block-library/src/image/interactivity.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { store, init } from '../utils/interactivity'; +import { store } from '../utils/interactivity'; store( { effects: { @@ -11,5 +11,3 @@ store( { }, }, } ); - -init(); diff --git a/packages/block-library/src/image/interactivity2.js b/packages/block-library/src/image/interactivity2.js index fc56b5069d300..dd29ee4a7d368 100644 --- a/packages/block-library/src/image/interactivity2.js +++ b/packages/block-library/src/image/interactivity2.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { store, init } from '../utils/interactivity'; +import { store } from '../utils/interactivity'; store( { effects: { @@ -11,5 +11,3 @@ store( { }, }, } ); - -init(); diff --git a/packages/block-library/src/utils/interactivity/index.js b/packages/block-library/src/utils/interactivity/index.js index 6ff7a86127313..9acf78ac1037b 100644 --- a/packages/block-library/src/utils/interactivity/index.js +++ b/packages/block-library/src/utils/interactivity/index.js @@ -8,14 +8,9 @@ export { store } from './store'; /** * Initialize the Interactivity API. */ -let started = false; -export const init = () => { - if ( ! started ) - window.addEventListener( 'DOMContentLoaded', () => { - registerDirectives(); - hydrate(); - // eslint-disable-next-line no-console - console.log( 'Interactivity API started' ); - } ); - started = true; -}; +window.addEventListener( 'DOMContentLoaded', () => { + registerDirectives(); + hydrate(); + // eslint-disable-next-line no-console + console.log( 'Interactivity API started' ); +} ); From 20d6213e0de345c57b496d0fe6ccfc146d185dfe Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Fri, 21 Apr 2023 17:47:50 +0200 Subject: [PATCH 06/17] Move script registration to a general file Co-authored-by: David Arenas --- lib/client-assets.php | 29 ++++++++++++++++++++++ packages/block-library/src/image/index.php | 9 ++++--- tools/webpack/blocks.js | 1 - 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 9c0483ea539b9..82139786b612d 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -577,3 +577,32 @@ function gutenberg_register_vendor_scripts( $scripts ) { // Enqueue stored styles. add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_stored_styles' ); add_action( 'wp_footer', 'gutenberg_enqueue_stored_styles', 1 ); + +/** + * Registers interactivity scripts for Gutenberg. + * + * This function registers interactivity scripts for Gutenberg when not in the + * admin panel. + */ +function gutenberg_register_interactivity_scripts() { + if ( ! is_admin() ) { + wp_register_script( + 'interactivity-runtime', + plugins_url( + '../build/block-library/interactive-blocks/interactivity.min.js', + __FILE__ + ), + array( 'interactivity-vendors') + ); + + wp_register_script( + 'interactivity-vendors', + plugins_url( + '../build/block-library/interactive-blocks/vendors.min.js', + __FILE__ + ) + ); + } +} +// Register interactivity scripts +add_action( 'wp_enqueue_scripts', 'gutenberg_register_interactivity_scripts' ); diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index b97dff5f4f6ea..331ae3982ff98 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -14,10 +14,11 @@ * @return string Returns the block content with the data-id attribute added. */ function render_block_core_image( $attributes, $content ) { - wp_enqueue_script( 'interactivity', plugins_url( '../interactive-blocks/interactivity.min.js', __FILE__ ) ); - wp_enqueue_script( 'interactivity-vendors', plugins_url( '../interactive-blocks/vendors.min.js', __FILE__ ) ); - wp_enqueue_script( 'interactivity-image', plugins_url( '../interactive-blocks/image.min.js', __FILE__ ) ); - wp_enqueue_script( 'interactivity-image-2', plugins_url( '../interactive-blocks/image2.min.js', __FILE__ ) ); + wp_enqueue_script( + 'interactivity-image', + plugins_url('../interactive-blocks/image.min.js', __FILE__ ), + array( 'interactivity-runtime' ) + ); $processor = new WP_HTML_Tag_Processor( $content ); $processor->next_tag( 'figure' ); diff --git a/tools/webpack/blocks.js b/tools/webpack/blocks.js index 5bc27223b7600..928f5049031e9 100644 --- a/tools/webpack/blocks.js +++ b/tools/webpack/blocks.js @@ -222,7 +222,6 @@ module.exports = [ { entry: { image: './packages/block-library/src/image/interactivity.js', - image2: './packages/block-library/src/image/interactivity2.js', }, output: { devtoolNamespace: 'wp', From 6fb8aa7ae96025d00eaa8cfaaf9937d389183310 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Fri, 21 Apr 2023 17:57:30 +0200 Subject: [PATCH 07/17] Add `defer` to the interactivity scripts Co-authored-by: David Arenas --- lib/client-assets.php | 20 ++++++++++++++++++- .../src/utils/interactivity/index.js | 10 ++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 82139786b612d..a0dc00cbcd676 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -604,5 +604,23 @@ function gutenberg_register_interactivity_scripts() { ); } } -// Register interactivity scripts add_action( 'wp_enqueue_scripts', 'gutenberg_register_interactivity_scripts' ); + +/** + * Adds the "defer" attribute to all the interactivity script tags. + * + * @param string $tag The generated script tag. + * @param string $handle The script's registered handle. + * + * @return string The modified script tag. + */ +function add_defer_attribute( $tag, $handle ) { + if ( 0 === strpos( $handle, 'interactivity-' ) ) { + $p = new WP_HTML_Tag_Processor( $tag ); + $p->next_tag( array( 'tag' => 'script' ) ); + $p->set_attribute( 'defer', true ); + return $p->get_updated_html(); + } + return $tag; +} +add_filter( 'script_loader_tag', 'add_defer_attribute', 10, 2 ); diff --git a/packages/block-library/src/utils/interactivity/index.js b/packages/block-library/src/utils/interactivity/index.js index 9acf78ac1037b..63e9c0aca0597 100644 --- a/packages/block-library/src/utils/interactivity/index.js +++ b/packages/block-library/src/utils/interactivity/index.js @@ -8,9 +8,7 @@ export { store } from './store'; /** * Initialize the Interactivity API. */ -window.addEventListener( 'DOMContentLoaded', () => { - registerDirectives(); - hydrate(); - // eslint-disable-next-line no-console - console.log( 'Interactivity API started' ); -} ); +registerDirectives(); +hydrate(); +// eslint-disable-next-line no-console +console.log( 'Interactivity API started' ); From 6c9650936b210582a7da66f5f125072e557bfd85 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Fri, 21 Apr 2023 18:01:26 +0200 Subject: [PATCH 08/17] Revert changes of the image block Co-authored-by: David Arenas --- packages/block-library/src/image/index.php | 14 +------------- packages/block-library/src/image/interactivity.js | 13 ------------- packages/block-library/src/image/interactivity2.js | 13 ------------- tools/webpack/blocks.js | 2 +- 4 files changed, 2 insertions(+), 40 deletions(-) delete mode 100644 packages/block-library/src/image/interactivity.js delete mode 100644 packages/block-library/src/image/interactivity2.js diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index 331ae3982ff98..e05939a4d0fea 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -14,18 +14,8 @@ * @return string Returns the block content with the data-id attribute added. */ function render_block_core_image( $attributes, $content ) { - wp_enqueue_script( - 'interactivity-image', - plugins_url('../interactive-blocks/image.min.js', __FILE__ ), - array( 'interactivity-runtime' ) - ); - $processor = new WP_HTML_Tag_Processor( $content ); - $processor->next_tag( 'figure' ); - $processor->set_attribute( 'data-wp-island', '' ); - $processor->set_attribute( 'data-wp-context', '{ "text": "image hydrated" }' ); $processor->next_tag( 'img' ); - $processor->set_attribute( 'data-wp-effect', 'effects.alert' ); if ( $processor->get_attribute( 'src' ) === null ) { return ''; @@ -37,10 +27,8 @@ function render_block_core_image( $attributes, $content ) { // which now wraps Image Blocks within innerBlocks. // The data-id attribute is added in a core/gallery `render_block_data` hook. $processor->set_attribute( 'data-id', $attributes['data-id'] ); + $content = $processor->get_updated_html(); } - - $content = $processor->get_updated_html(); - return $content; } diff --git a/packages/block-library/src/image/interactivity.js b/packages/block-library/src/image/interactivity.js deleted file mode 100644 index dd29ee4a7d368..0000000000000 --- a/packages/block-library/src/image/interactivity.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { store } from '../utils/interactivity'; - -store( { - effects: { - alert: ( { context } ) => { - // eslint-disable-next-line no-console - console.log( context.text ); - }, - }, -} ); diff --git a/packages/block-library/src/image/interactivity2.js b/packages/block-library/src/image/interactivity2.js deleted file mode 100644 index dd29ee4a7d368..0000000000000 --- a/packages/block-library/src/image/interactivity2.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Internal dependencies - */ -import { store } from '../utils/interactivity'; - -store( { - effects: { - alert: ( { context } ) => { - // eslint-disable-next-line no-console - console.log( context.text ); - }, - }, -} ); diff --git a/tools/webpack/blocks.js b/tools/webpack/blocks.js index 928f5049031e9..35081eb3b12dd 100644 --- a/tools/webpack/blocks.js +++ b/tools/webpack/blocks.js @@ -221,7 +221,7 @@ module.exports = [ }, { entry: { - image: './packages/block-library/src/image/interactivity.js', + // blockname: './packages/block-library/src/blockname/interactivity.js', }, output: { devtoolNamespace: 'wp', From e843b800b3704c7104ee2e9ef704eec06935ffa1 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Fri, 21 Apr 2023 18:13:15 +0200 Subject: [PATCH 09/17] Fix init import name Co-authored-by: David Arenas --- packages/block-library/src/utils/interactivity/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/utils/interactivity/index.js b/packages/block-library/src/utils/interactivity/index.js index 63e9c0aca0597..55cf73342013b 100644 --- a/packages/block-library/src/utils/interactivity/index.js +++ b/packages/block-library/src/utils/interactivity/index.js @@ -2,13 +2,13 @@ * Internal dependencies */ import registerDirectives from './directives'; -import { init as hydrate } from './hydration'; +import { init } from './hydration'; export { store } from './store'; /** * Initialize the Interactivity API. */ registerDirectives(); -hydrate(); +init(); // eslint-disable-next-line no-console console.log( 'Interactivity API started' ); From cdf74b9c3a563e869306a023e9ba661d041ea281 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Fri, 28 Apr 2023 14:39:48 +0200 Subject: [PATCH 10/17] Move and refactor the interactive scritps registration --- lib/client-assets.php | 47 ----------------- .../interactivity-api/script-loader.php | 50 +++++++++++++++++++ lib/load.php | 1 + 3 files changed, 51 insertions(+), 47 deletions(-) create mode 100644 lib/experimental/interactivity-api/script-loader.php diff --git a/lib/client-assets.php b/lib/client-assets.php index a0dc00cbcd676..9c0483ea539b9 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -577,50 +577,3 @@ function gutenberg_register_vendor_scripts( $scripts ) { // Enqueue stored styles. add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_stored_styles' ); add_action( 'wp_footer', 'gutenberg_enqueue_stored_styles', 1 ); - -/** - * Registers interactivity scripts for Gutenberg. - * - * This function registers interactivity scripts for Gutenberg when not in the - * admin panel. - */ -function gutenberg_register_interactivity_scripts() { - if ( ! is_admin() ) { - wp_register_script( - 'interactivity-runtime', - plugins_url( - '../build/block-library/interactive-blocks/interactivity.min.js', - __FILE__ - ), - array( 'interactivity-vendors') - ); - - wp_register_script( - 'interactivity-vendors', - plugins_url( - '../build/block-library/interactive-blocks/vendors.min.js', - __FILE__ - ) - ); - } -} -add_action( 'wp_enqueue_scripts', 'gutenberg_register_interactivity_scripts' ); - -/** - * Adds the "defer" attribute to all the interactivity script tags. - * - * @param string $tag The generated script tag. - * @param string $handle The script's registered handle. - * - * @return string The modified script tag. - */ -function add_defer_attribute( $tag, $handle ) { - if ( 0 === strpos( $handle, 'interactivity-' ) ) { - $p = new WP_HTML_Tag_Processor( $tag ); - $p->next_tag( array( 'tag' => 'script' ) ); - $p->set_attribute( 'defer', true ); - return $p->get_updated_html(); - } - return $tag; -} -add_filter( 'script_loader_tag', 'add_defer_attribute', 10, 2 ); diff --git a/lib/experimental/interactivity-api/script-loader.php b/lib/experimental/interactivity-api/script-loader.php new file mode 100644 index 0000000000000..206a1d8e5e0b7 --- /dev/null +++ b/lib/experimental/interactivity-api/script-loader.php @@ -0,0 +1,50 @@ +next_tag( array( 'tag' => 'script' ) ); + $p->set_attribute( 'defer', true ); + return $p->get_updated_html(); + } + return $tag; +} +add_filter( 'script_loader_tag', 'gutenberg_interactivity_scripts_add_defer_attribute', 10, 2 ); diff --git a/lib/load.php b/lib/load.php index 65b731de4b9ac..ac4941cb0e96a 100644 --- a/lib/load.php +++ b/lib/load.php @@ -98,6 +98,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/experimental/block-editor-settings-mobile.php'; require __DIR__ . '/experimental/block-editor-settings.php'; require __DIR__ . '/experimental/blocks.php'; +require __DIR__ . '/experimental/interactivity-api/script-loader.php'; require __DIR__ . '/experimental/navigation-theme-opt-in.php'; require __DIR__ . '/experimental/kses.php'; require __DIR__ . '/experimental/l10n.php'; From de0e874edecd075a4943adb2e90a3f8107c20d9e Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Fri, 28 Apr 2023 14:48:32 +0200 Subject: [PATCH 11/17] Fix code style violations --- .../interactivity-api/script-loader.php | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/experimental/interactivity-api/script-loader.php b/lib/experimental/interactivity-api/script-loader.php index 206a1d8e5e0b7..74ea388316f11 100644 --- a/lib/experimental/interactivity-api/script-loader.php +++ b/lib/experimental/interactivity-api/script-loader.php @@ -17,7 +17,7 @@ function gutenberg_register_interactivity_scripts( $scripts ) { gutenberg_url( 'build/block-library/interactive-blocks/interactivity.min.js' ), - array( 'interactivity-vendors') + array( 'interactivity-vendors' ) ); gutenberg_override_script( @@ -31,20 +31,20 @@ function gutenberg_register_interactivity_scripts( $scripts ) { add_action( 'wp_default_scripts', 'gutenberg_register_interactivity_scripts', 10, 1 ); /** - * Adds the "defer" attribute to all the interactivity script tags. - * - * @param string $tag The generated script tag. - * @param string $handle The script's registered handle. - * - * @return string The modified script tag. - */ + * Adds the "defer" attribute to all the interactivity script tags. + * + * @param string $tag The generated script tag. + * @param string $handle The script's registered handle. + * + * @return string The modified script tag. + */ function gutenberg_interactivity_scripts_add_defer_attribute( $tag, $handle ) { - if ( 0 === strpos( $handle, 'interactivity-' ) ) { - $p = new WP_HTML_Tag_Processor( $tag ); - $p->next_tag( array( 'tag' => 'script' ) ); - $p->set_attribute( 'defer', true ); - return $p->get_updated_html(); - } - return $tag; + if ( 0 === strpos( $handle, 'interactivity-' ) ) { + $p = new WP_HTML_Tag_Processor( $tag ); + $p->next_tag( array( 'tag' => 'script' ) ); + $p->set_attribute( 'defer', true ); + return $p->get_updated_html(); + } + return $tag; } add_filter( 'script_loader_tag', 'gutenberg_interactivity_scripts_add_defer_attribute', 10, 2 ); From fa7c00d39e4e37f97b0ea77c3feda3d5c849c883 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Fri, 28 Apr 2023 14:59:57 +0200 Subject: [PATCH 12/17] Use `wp-interactivity-` prefix for script handles --- lib/experimental/interactivity-api/script-loader.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/experimental/interactivity-api/script-loader.php b/lib/experimental/interactivity-api/script-loader.php index 74ea388316f11..f03debc4c7fa1 100644 --- a/lib/experimental/interactivity-api/script-loader.php +++ b/lib/experimental/interactivity-api/script-loader.php @@ -13,16 +13,16 @@ function gutenberg_register_interactivity_scripts( $scripts ) { gutenberg_override_script( $scripts, - 'interactivity-runtime', + 'wp-interactivity-runtime', gutenberg_url( 'build/block-library/interactive-blocks/interactivity.min.js' ), - array( 'interactivity-vendors' ) + array( 'wp-interactivity-vendors' ) ); gutenberg_override_script( $scripts, - 'interactivity-vendors', + 'wp-interactivity-vendors', gutenberg_url( 'build/block-library/interactive-blocks/vendors.min.js' ) @@ -39,7 +39,7 @@ function gutenberg_register_interactivity_scripts( $scripts ) { * @return string The modified script tag. */ function gutenberg_interactivity_scripts_add_defer_attribute( $tag, $handle ) { - if ( 0 === strpos( $handle, 'interactivity-' ) ) { + if ( 0 === strpos( $handle, 'wp-interactivity-' ) ) { $p = new WP_HTML_Tag_Processor( $tag ); $p->next_tag( array( 'tag' => 'script' ) ); $p->set_attribute( 'defer', true ); From 0d88c77023bb79dde5fd568964c83bafad95e8a9 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Fri, 28 Apr 2023 15:00:46 +0200 Subject: [PATCH 13/17] Improve the matcher for side effects in `package.json` --- packages/block-library/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 000f904a4f7d4..3b6ab1f014080 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -28,7 +28,7 @@ "build-style/**", "src/**/*.scss", "{src,build,build-module}/*/init.js", - "src/utils/interactivity/index.js" + "{src,build,build-module}/utils/interactivity/index.js" ], "dependencies": { "@babel/runtime": "^7.16.0", From 142050c82745dc582b3084636787bece16fa47b8 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Thu, 4 May 2023 11:36:27 +0200 Subject: [PATCH 14/17] Add custom useSignalEffect --- .../src/utils/interactivity/directives.js | 5 +- .../src/utils/interactivity/utils.js | 46 +++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/utils/interactivity/directives.js b/packages/block-library/src/utils/interactivity/directives.js index 07874d4f91d07..d7583fa8eaa33 100644 --- a/packages/block-library/src/utils/interactivity/directives.js +++ b/packages/block-library/src/utils/interactivity/directives.js @@ -2,11 +2,12 @@ * External dependencies */ import { useContext, useMemo, useEffect } from 'preact/hooks'; -import { useSignalEffect } from '@preact/signals'; import { deepSignal, peek } from 'deepsignal'; + /** * Internal dependencies */ +import { useSignalEffect } from './utils'; import { directive } from './hooks'; const isObject = ( item ) => @@ -76,7 +77,7 @@ export default () => { const contextValue = useContext( context ); Object.entries( on ).forEach( ( [ name, path ] ) => { element.props[ `on${ name }` ] = ( event ) => { - return evaluate( path, { event, context: contextValue } ); + evaluate( path, { event, context: contextValue } ); }; } ); } ); diff --git a/packages/block-library/src/utils/interactivity/utils.js b/packages/block-library/src/utils/interactivity/utils.js index 48c50fc537c05..21d15da2f94ff 100644 --- a/packages/block-library/src/utils/interactivity/utils.js +++ b/packages/block-library/src/utils/interactivity/utils.js @@ -1,3 +1,49 @@ +/** + * External dependencies + */ +import { useRef, useEffect } from 'preact/hooks'; +import { effect } from '@preact/signals'; + +function afterNextFrame( callback ) { + const done = () => { + window.cancelAnimationFrame( raf ); + setTimeout( callback ); + }; + const raf = window.requestAnimationFrame( done ); +} + +// Using the mangled properties: +// this.c: this._callback +// this.x: this._compute +// https://github.com/preactjs/signals/blob/main/mangle.json +function createFlusher( compute, notify ) { + let flush; + const dispose = effect( function () { + flush = this.c.bind( this ); + this.x = compute; + this.c = notify; + return compute(); + } ); + return { flush, dispose }; +} + +// Version of `useSignalEffect` with a `useEffect`-like execution. This hook +// implementation comes from this PR: +// https://github.com/preactjs/signals/pull/290. +// +// We need to include it here in this repo until the mentioned PR is merged. +export function useSignalEffect( cb ) { + const callback = useRef( cb ); + callback.current = cb; + + useEffect( () => { + const execute = () => callback.current(); + const notify = () => afterNextFrame( eff.flush ); + const eff = createFlusher( execute, notify ); + return eff.dispose; + }, [] ); +} + // For wrapperless hydration. // See https://gist.github.com/developit/f4c67a2ede71dc2fab7f357f39cff28c export const createRootFragment = ( parent, replaceNode ) => { From e65b91ec31908a1e453537f9f83bb8f1523b4a80 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Thu, 4 May 2023 19:11:26 +0200 Subject: [PATCH 15/17] Call `init` after `store` has been initialized --- packages/block-library/src/utils/interactivity/index.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/utils/interactivity/index.js b/packages/block-library/src/utils/interactivity/index.js index 55cf73342013b..6dbac1a45e88c 100644 --- a/packages/block-library/src/utils/interactivity/index.js +++ b/packages/block-library/src/utils/interactivity/index.js @@ -9,6 +9,9 @@ export { store } from './store'; * Initialize the Interactivity API. */ registerDirectives(); -init(); -// eslint-disable-next-line no-console -console.log( 'Interactivity API started' ); + +document.addEventListener( 'DOMContentLoaded', async () => { + await init(); + // eslint-disable-next-line no-console + console.log( 'Interactivity API started' ); +} ); From 9a31bb42b3883ec63413979917054403ba8f3371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Fri, 5 May 2023 10:28:05 +0200 Subject: [PATCH 16/17] Update lib/experimental/interactivity-api/script-loader.php Co-authored-by: Weston Ruter --- lib/experimental/interactivity-api/script-loader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/experimental/interactivity-api/script-loader.php b/lib/experimental/interactivity-api/script-loader.php index f03debc4c7fa1..924dc196f10d2 100644 --- a/lib/experimental/interactivity-api/script-loader.php +++ b/lib/experimental/interactivity-api/script-loader.php @@ -39,7 +39,7 @@ function gutenberg_register_interactivity_scripts( $scripts ) { * @return string The modified script tag. */ function gutenberg_interactivity_scripts_add_defer_attribute( $tag, $handle ) { - if ( 0 === strpos( $handle, 'wp-interactivity-' ) ) { + if ( str_starts_with( $handle, 'wp-interactivity-' ) ) { $p = new WP_HTML_Tag_Processor( $tag ); $p->next_tag( array( 'tag' => 'script' ) ); $p->set_attribute( 'defer', true ); From 0cdab0869726d5ebde7f33357456c92fac1b1e2e Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Fri, 5 May 2023 10:40:12 +0200 Subject: [PATCH 17/17] Plugin: Ensure that translations are set correctly when overriding scripts This replicates the same logic in WordPress core: https://github.com/WordPress/wordpress-develop/blob/a5f3087f6b5d9c52dbe67ed247165dc32427c57d/src/wp-includes/script-loader.php#L306-L308 --- lib/client-assets.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 9c0483ea539b9..9757e4b7ff24a 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -77,16 +77,8 @@ function gutenberg_override_script( $scripts, $handle, $src, $deps = array(), $v $scripts->add( $handle, $src, $deps, $ver, ( $in_footer ? 1 : null ) ); } - /* - * `WP_Dependencies::set_translations` will fall over on itself if setting - * translations on the `wp-i18n` handle, since it internally adds `wp-i18n` - * as a dependency of itself, exhausting memory. The same applies for the - * polyfill and hooks scripts, which are dependencies _of_ `wp-i18n`. - * - * See: https://core.trac.wordpress.org/ticket/46089 - */ - if ( ! in_array( $handle, array( 'wp-i18n', 'wp-polyfill', 'wp-hooks' ), true ) ) { - $scripts->set_translations( $handle, 'default' ); + if ( in_array( 'wp-i18n', $deps, true ) ) { + $scripts->set_translations( $handle ); } /*