diff --git a/packages/interactivity/src/constants.js b/packages/interactivity/src/constants.js index aecf919067bdb6..669e94263fb9ca 100644 --- a/packages/interactivity/src/constants.js +++ b/packages/interactivity/src/constants.js @@ -1,4 +1 @@ export const directivePrefix = 'wp'; - -export const isDebug = - typeof SCRIPT_DEBUG !== 'undefined' && SCRIPT_DEBUG === true; diff --git a/packages/interactivity/src/directives.js b/packages/interactivity/src/directives.js index b3ffd8d404e128..8100ac7a52eee5 100644 --- a/packages/interactivity/src/directives.js +++ b/packages/interactivity/src/directives.js @@ -13,7 +13,7 @@ import { deepSignal, peek } from 'deepsignal'; import { useWatch, useInit } from './utils'; import { directive, getScope, getEvaluate } from './hooks'; import { kebabToCamelCase } from './utils/kebab-to-camelcase'; -import { isDebug } from './constants'; +import { warn } from './utils/warn'; // Assigned objects should be ignore during proxification. const contextAssignedObjects = new WeakMap(); @@ -243,9 +243,8 @@ export default () => { if ( defaultEntry ) { const { namespace, value } = defaultEntry; // Check that the value is a JSON object. Send a console warning if not. - if ( isDebug && ! isPlainObject( value ) ) { - // eslint-disable-next-line no-console - console.warn( + if ( ! isPlainObject( value ) ) { + warn( `The value of data-wp-context in "${ namespace }" store must be a valid stringified JSON object.` ); } diff --git a/packages/interactivity/src/hooks.tsx b/packages/interactivity/src/hooks.tsx index 4463a455ac81f3..7100fab615c149 100644 --- a/packages/interactivity/src/hooks.tsx +++ b/packages/interactivity/src/hooks.tsx @@ -16,7 +16,7 @@ import type { VNode, Context, RefObject } from 'preact'; * Internal dependencies */ import { store, stores, universalUnlock } from './store'; -import { isDebug } from './constants'; +import { warn } from './utils/warn'; interface DirectiveEntry { value: string | Object; namespace: string; @@ -261,15 +261,6 @@ export const directive = ( // Resolve the path to some property of the store object. const resolve = ( path, namespace ) => { - if ( namespace === '' ) { - if ( isDebug ) { - // eslint-disable-next-line no-console - console.warn( - `Namespace cannot be an empty string. Error found when trying to use "${ path }"` - ); - } - return; - } let resolvedStore = stores.get( namespace ); if ( typeof resolvedStore === 'undefined' ) { resolvedStore = store( namespace, undefined, { @@ -282,14 +273,7 @@ const resolve = ( path, namespace ) => { }; try { return path.split( '.' ).reduce( ( acc, key ) => acc[ key ], current ); - } catch ( e ) { - if ( isDebug ) { - // eslint-disable-next-line no-console - console.warn( - `There was an error when trying to resolve the path "${ path }" in the namespace "${ namespace }".` - ); - } - } + } catch ( e ) {} }; // Generate the evaluate function. @@ -300,6 +284,12 @@ export const getEvaluate: GetEvaluate = if ( typeof path !== 'string' ) { throw new Error( 'The `value` prop should be a string path' ); } + if ( ! namespace || namespace === '' ) { + // TODO: Support lazy/dynamically initialized stores + warn( + `The "namespace" cannot be "{}", "null" or an emtpy string. Path: ${ path }` + ); + } // If path starts with !, remove it and save a flag. const hasNegationOperator = path[ 0 ] === '!' && !! ( path = path.slice( 1 ) ); diff --git a/packages/interactivity/src/utils/warn.ts b/packages/interactivity/src/utils/warn.ts new file mode 100644 index 00000000000000..98bf88c157acaf --- /dev/null +++ b/packages/interactivity/src/utils/warn.ts @@ -0,0 +1,21 @@ +const logged = new Set(); + +export const warn = ( message ) => { + // @ts-expect-error + if ( typeof SCRIPT_DEBUG !== 'undefined' && SCRIPT_DEBUG === true ) { + if ( logged.has( message ) ) { + return; + } + + // eslint-disable-next-line no-console + console.warn( message ); + + // Adding a stack trace to the warning message to help with debugging. + try { + throw Error( message ); + } catch ( e ) { + // Do nothing. + } + logged.add( message ); + } +}; diff --git a/packages/interactivity/src/vdom.ts b/packages/interactivity/src/vdom.ts index dcd4bde60fb414..78f6d7032613a5 100644 --- a/packages/interactivity/src/vdom.ts +++ b/packages/interactivity/src/vdom.ts @@ -5,7 +5,8 @@ import { h } from 'preact'; /** * Internal dependencies */ -import { isDebug, directivePrefix as p } from './constants'; +import { directivePrefix as p } from './constants'; +import { warn } from './utils/warn'; const ignoreAttr = `data-${ p }-ignore`; const islandAttr = `data-${ p }-interactive`; @@ -120,10 +121,7 @@ export function toVdom( root ) { ( obj, [ name, ns, value ] ) => { const directiveMatch = directiveParser.exec( name ); if ( directiveMatch === null ) { - if ( isDebug ) { - // eslint-disable-next-line no-console - console.warn( `Invalid directive: ${ name }.` ); - } + warn( `Invalid directive: ${ name }.` ); return obj; } const prefix = directiveMatch[ 1 ] || '';