fix: prevent asset conflicts between React and Grid.js versions

Add coexistence checks to all enqueue methods to prevent loading
both React and Grid.js assets simultaneously.

Changes:
- ReactAdmin.php: Only enqueue React assets when ?react=1
- Init.php: Skip Grid.js when React active on admin pages
- Form.php, Coupon.php, Access.php: Restore classic assets when ?react=0
- Customer.php, Product.php, License.php: Add coexistence checks

Now the toggle between Classic and React versions works correctly.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
dwindown
2026-04-18 17:02:14 +07:00
parent bd9cdac02e
commit e8fbfb14c1
74973 changed files with 6658406 additions and 71 deletions

View File

@@ -0,0 +1,234 @@
/**
* wordpress/private-apis the utilities to enable private cross-package
* exports of private APIs.
*
* This "implementation.js" file is needed for the sake of the unit tests. It
* exports more than the public API of the package to aid in testing.
*/
/**
* The list of core modules allowed to opt-in to the private APIs.
*/
const CORE_MODULES_USING_PRIVATE_APIS = [
'@wordpress/block-directory',
'@wordpress/block-editor',
'@wordpress/block-library',
'@wordpress/blocks',
'@wordpress/commands',
'@wordpress/components',
'@wordpress/core-commands',
'@wordpress/core-data',
'@wordpress/customize-widgets',
'@wordpress/data',
'@wordpress/edit-post',
'@wordpress/edit-site',
'@wordpress/edit-widgets',
'@wordpress/editor',
'@wordpress/format-library',
'@wordpress/interface',
'@wordpress/patterns',
'@wordpress/preferences',
'@wordpress/reusable-blocks',
'@wordpress/router',
'@wordpress/dataviews',
];
/**
* A list of core modules that already opted-in to
* the privateApis package.
*
* @type {string[]}
*/
const registeredPrivateApis = [];
/*
* Warning for theme and plugin developers.
*
* The use of private developer APIs is intended for use by WordPress Core
* and the Gutenberg plugin exclusively.
*
* Dangerously opting in to using these APIs is NOT RECOMMENDED. Furthermore,
* the WordPress Core philosophy to strive to maintain backward compatibility
* for third-party developers DOES NOT APPLY to private APIs.
*
* THE CONSENT STRING FOR OPTING IN TO THESE APIS MAY CHANGE AT ANY TIME AND
* WITHOUT NOTICE. THIS CHANGE WILL BREAK EXISTING THIRD-PARTY CODE. SUCH A
* CHANGE MAY OCCUR IN EITHER A MAJOR OR MINOR RELEASE.
*/
const requiredConsent =
'I know using unstable features means my theme or plugin will inevitably break in the next version of WordPress.';
/** @type {boolean} */
let allowReRegistration;
// The safety measure is meant for WordPress core where IS_WORDPRESS_CORE
// is set to true.
// For the general use-case, the re-registration should be allowed by default
// Let's default to true, then. Try/catch will fall back to "true" even if the
// environment variable is not explicitly defined.
try {
allowReRegistration = process.env.IS_WORDPRESS_CORE ? false : true;
} catch ( error ) {
allowReRegistration = true;
}
/**
* Called by a @wordpress package wishing to opt-in to accessing or exposing
* private private APIs.
*
* @param {string} consent The consent string.
* @param {string} moduleName The name of the module that is opting in.
* @return {{lock: typeof lock, unlock: typeof unlock}} An object containing the lock and unlock functions.
*/
export const __dangerousOptInToUnstableAPIsOnlyForCoreModules = (
consent,
moduleName
) => {
if ( ! CORE_MODULES_USING_PRIVATE_APIS.includes( moduleName ) ) {
throw new Error(
`You tried to opt-in to unstable APIs as module "${ moduleName }". ` +
'This feature is only for JavaScript modules shipped with WordPress core. ' +
'Please do not use it in plugins and themes as the unstable APIs will be removed ' +
'without a warning. If you ignore this error and depend on unstable features, ' +
'your product will inevitably break on one of the next WordPress releases.'
);
}
if (
! allowReRegistration &&
registeredPrivateApis.includes( moduleName )
) {
// This check doesn't play well with Story Books / Hot Module Reloading
// and isn't included in the Gutenberg plugin. It only matters in the
// WordPress core release.
throw new Error(
`You tried to opt-in to unstable APIs as module "${ moduleName }" which is already registered. ` +
'This feature is only for JavaScript modules shipped with WordPress core. ' +
'Please do not use it in plugins and themes as the unstable APIs will be removed ' +
'without a warning. If you ignore this error and depend on unstable features, ' +
'your product will inevitably break on one of the next WordPress releases.'
);
}
if ( consent !== requiredConsent ) {
throw new Error(
`You tried to opt-in to unstable APIs without confirming you know the consequences. ` +
'This feature is only for JavaScript modules shipped with WordPress core. ' +
'Please do not use it in plugins and themes as the unstable APIs will removed ' +
'without a warning. If you ignore this error and depend on unstable features, ' +
'your product will inevitably break on the next WordPress release.'
);
}
registeredPrivateApis.push( moduleName );
return {
lock,
unlock,
};
};
/**
* Binds private data to an object.
* It does not alter the passed object in any way, only
* registers it in an internal map of private data.
*
* The private data can't be accessed by any other means
* than the `unlock` function.
*
* @example
* ```js
* const object = {};
* const privateData = { a: 1 };
* lock( object, privateData );
*
* object
* // {}
*
* unlock( object );
* // { a: 1 }
* ```
*
* @param {any} object The object to bind the private data to.
* @param {any} privateData The private data to bind to the object.
*/
function lock( object, privateData ) {
if ( ! object ) {
throw new Error( 'Cannot lock an undefined object.' );
}
if ( ! ( __private in object ) ) {
object[ __private ] = {};
}
lockedData.set( object[ __private ], privateData );
}
/**
* Unlocks the private data bound to an object.
*
* It does not alter the passed object in any way, only
* returns the private data paired with it using the `lock()`
* function.
*
* @example
* ```js
* const object = {};
* const privateData = { a: 1 };
* lock( object, privateData );
*
* object
* // {}
*
* unlock( object );
* // { a: 1 }
* ```
*
* @param {any} object The object to unlock the private data from.
* @return {any} The private data bound to the object.
*/
function unlock( object ) {
if ( ! object ) {
throw new Error( 'Cannot unlock an undefined object.' );
}
if ( ! ( __private in object ) ) {
throw new Error(
'Cannot unlock an object that was not locked before. '
);
}
return lockedData.get( object[ __private ] );
}
const lockedData = new WeakMap();
/**
* Used by lock() and unlock() to uniquely identify the private data
* related to a containing object.
*/
const __private = Symbol( 'Private API ID' );
// Unit tests utilities:
/**
* Private function to allow the unit tests to allow
* a mock module to access the private APIs.
*
* @param {string} name The name of the module.
*/
export function allowCoreModule( name ) {
CORE_MODULES_USING_PRIVATE_APIS.push( name );
}
/**
* Private function to allow the unit tests to set
* a custom list of allowed modules.
*/
export function resetAllowedCoreModules() {
while ( CORE_MODULES_USING_PRIVATE_APIS.length ) {
CORE_MODULES_USING_PRIVATE_APIS.pop();
}
}
/**
* Private function to allow the unit tests to reset
* the list of registered private apis.
*/
export function resetRegisteredPrivateApis() {
while ( registeredPrivateApis.length ) {
registeredPrivateApis.pop();
}
}

1
node_modules/@wordpress/private-apis/src/index.js generated vendored Normal file
View File

@@ -0,0 +1 @@
export { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from './implementation';

308
node_modules/@wordpress/private-apis/src/test/index.js generated vendored Normal file
View File

@@ -0,0 +1,308 @@
/**
* Internal dependencies
*/
import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '../';
import {
resetRegisteredPrivateApis,
resetAllowedCoreModules,
allowCoreModule,
} from '../implementation';
beforeEach( () => {
resetRegisteredPrivateApis();
resetAllowedCoreModules();
allowCoreModule( '@privateApis/test' );
allowCoreModule( '@privateApis/test-consumer' );
} );
const requiredConsent =
'I know using unstable features means my theme or plugin will inevitably break in the next version of WordPress.';
describe( '__dangerousOptInToUnstableAPIsOnlyForCoreModules', () => {
it( 'Should require a consent string', () => {
expect( () => {
__dangerousOptInToUnstableAPIsOnlyForCoreModules(
'',
'@privateApis/test'
);
} ).toThrow( /without confirming you know the consequences/ );
} );
it( 'Should require a valid @wordpress package name', () => {
expect( () => {
__dangerousOptInToUnstableAPIsOnlyForCoreModules(
requiredConsent,
'custom_package'
);
} ).toThrow(
/This feature is only for JavaScript modules shipped with WordPress core/
);
} );
it( 'Should not register the same module twice', () => {
expect( () => {
__dangerousOptInToUnstableAPIsOnlyForCoreModules(
requiredConsent,
'@privateApis/test'
);
__dangerousOptInToUnstableAPIsOnlyForCoreModules(
requiredConsent,
'@privateApis/test'
);
} ).toThrow( /is already registered/ );
} );
it( 'Should grant access to unstable APIs when passed both a consent string and a previously unregistered package name', () => {
const unstableAPIs = __dangerousOptInToUnstableAPIsOnlyForCoreModules(
requiredConsent,
'@privateApis/test'
);
expect( unstableAPIs.lock ).toEqual( expect.any( Function ) );
expect( unstableAPIs.unlock ).toEqual( expect.any( Function ) );
} );
} );
describe( 'lock(), unlock()', () => {
let lock, unlock;
beforeEach( () => {
// This would live in @privateApis/test:
// Opt-in to private APIs
const privateApisAPI = __dangerousOptInToUnstableAPIsOnlyForCoreModules(
requiredConsent,
'@privateApis/test'
);
lock = privateApisAPI.lock;
unlock = privateApisAPI.unlock;
} );
it( 'Should lock and unlock objects "inside" other objects', () => {
const object = {};
const privateData = { secret: 'sh' };
lock( object, privateData );
expect( unlock( object ).secret ).toBe( 'sh' );
} );
it( 'Should lock and unlock functions "inside" objects', () => {
const object = {};
const privateData = () => 'sh';
lock( object, privateData );
expect( unlock( object )() ).toBe( 'sh' );
} );
it( 'Should lock and unlock strings "inside" objects', () => {
const object = {};
const privateData = 'sh';
lock( object, privateData );
expect( unlock( object ) ).toBe( 'sh' );
} );
it( 'Should lock and unlock objects "inside" functions', () => {
const fn = function () {};
const privateData = { secret: 'sh' };
lock( fn, privateData );
expect( unlock( fn ).secret ).toBe( 'sh' );
} );
it( 'Should lock and unlock functions "inside" other functions', () => {
const fn = function () {};
const privateData = () => 'sh';
lock( fn, privateData );
expect( unlock( fn )() ).toBe( 'sh' );
} );
it( 'Should lock and unlock strings "inside" functions', () => {
const fn = function () {};
const privateData = 'sh';
lock( fn, privateData );
expect( unlock( fn ) ).toBe( 'sh' );
} );
it( 'Should grant other opt-int modules access to locked objects', () => {
const object = {};
const privateData = { secret: 'sh' };
lock( object, privateData );
// This would live in @wordpress/core-data:
// Register the private APIs
const coreDataPrivateApis =
__dangerousOptInToUnstableAPIsOnlyForCoreModules(
requiredConsent,
'@privateApis/test-consumer'
);
// Get the private APIs registered by @privateApis/test
expect( coreDataPrivateApis.unlock( object ).secret ).toBe( 'sh' );
} );
} );
describe( 'Specific use-cases of sharing private APIs', () => {
let lock, unlock;
beforeEach( () => {
// This would live in @privateApis/test:
// Opt-in to private APIs
const privateApisAPI = __dangerousOptInToUnstableAPIsOnlyForCoreModules(
requiredConsent,
'@privateApis/test'
);
lock = privateApisAPI.lock;
unlock = privateApisAPI.unlock;
} );
it( 'Should enable privately exporting private functions', () => {
/**
* Problem: The private __privateFunction should not be publicly
* exposed to the consumers of package1.
*/
function __privateFunction() {}
/**
* Solution: Privately lock it inside a publicly exported object.
*
* In `package1/index.js` we'd say:
*
* ```js
* export const privateApis = {};
* lock(privateApis, {
* __privateFunction
* });
* ```
*
* Let's simulate in the test code:
*/
const privateApis = {};
const package1Exports = {
privateApis,
};
lock( privateApis, { __privateFunction } );
/**
* Then, in the consumer package we'd say:
*
* ```js
* import { privateApis } from 'package1';
* const { __privateFunction } = unlock( privateApis );
* ```
*
* Let's simulate that, too:
*/
const unlockedFunction = unlock(
package1Exports.privateApis
).__privateFunction;
expect( unlockedFunction ).toBe( __privateFunction );
} );
it( 'Should enable exporting functions with private private arguments', () => {
/**
* The publicly exported function does not have any private
* arguments.
*
* @param {any} data The data to log
*/
function logData( data ) {
// Internally, it calls the private version of the function
// with fixed default values for the private arguments.
__privateLogData( data, 'plain' );
}
/**
* The private private function is not publicly exported. Instead, it's
* "locked" inside of the public logData function. It can be unlocked by any
* participant of the private importing system.
*
* @param {any} data The data to log
* @param {string} __privateFormat The logging format to use.
*/
function __privateLogData( data, __privateFormat ) {
if ( __privateFormat === 'table' ) {
// eslint-disable-next-line no-console
console.table( data );
} else {
// eslint-disable-next-line no-console
console.log( data );
}
}
lock( logData, __privateLogData );
/**
* In package/log-data.js:
*
* ```js
* lock( logData, __privateLogData );
* export logData;
* ```
*
* Then, in package/index.js:
*
* ```js
* export { logData } from './log-data';
* ```
*
* And that's it! The public function is publicly exported, and the
* private function is available via unlock( logData ):
*
* ```js
* import { logData } from 'package1';
* const experimenalLogData = unlock( logData );
* ```
*/
expect( unlock( logData ) ).toBe( __privateLogData );
} );
it( 'Should enable exporting React Components with private private properties', () => {
// eslint-disable-next-line jsdoc/require-param
/**
* The publicly exported component does not have any private
* properties.
*/
function DataTable( { data } ) {
// Internally, it calls the private version of the function
// with fixed default values for the private arguments.
return (
<PrivateDataTable
data={ data }
__privateFancyFormatting={ false }
/>
);
}
// eslint-disable-next-line jsdoc/require-param
/**
* The private private component is not publicly exported. Instead, it's
* "locked" inside of the public logData function. It can be unlocked by any
* participant of the private importing system.
*/
function PrivateDataTable( { data, __privateFancyFormatting } ) {
const className = __privateFancyFormatting
? 'savage-css'
: 'regular-css';
return (
<table className={ className }>
{ data.map( ( row, i ) => (
<tr key={ i }>
{ row.map( ( col, j ) => (
<td key={ j }>{ col }</td>
) ) }
</tr>
) ) }
</table>
);
}
lock( DataTable, PrivateDataTable );
/**
* In package/data-table.js:
*
* ```js
* lock( DataTable, PrivateDataTable );
* export DataTable;
* ```
*
* Then, in package/index.js:
*
* ```js
* export { DataTable } from './data-table';
* ```
*
* And that's it! The public function is publicly exported, and the
* private function is available via unlock( logData ):
*
* ```js
* import { DataTable } from 'package1';
* const PrivateDataTable = unlock( DataTable );
* ```
*/
expect( unlock( DataTable ) ).toBe( PrivateDataTable );
} );
} );