-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Limit access to experimental APIs to WordPress codebase (#43386)
Make the __experimental APIs private by introducing a dealer mechanism that only grants access to core WordPress packages. It solves the problem of leaking private experimental APIs to extenders in public stable releases. See #40316 for more details. Usage example: ```js // in @wordpress/data import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/experiments'; const experiments = __dangerousOptInToUnstableAPIsOnlyForCoreModules( 'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.', '@wordpress/data' ); export const __experiments = experiments.register({ __experimentalFunction: () => { /* ... */ }, }); ``` ```js // In @wordpress/core-data: import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/experiments'; import { __experiments as __dataExperiments } from '@wordpress/data'; const experiments = __dangerousOptInToUnstableAPIsOnlyForCoreModules( 'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.', '@wordpress/core-data' ); // Get the experimental APIs registered by @wordpress/data const { __experimentalFunction } = experiments.unlock( __dataExperiments ); __experimentalFunction(); ```
- Loading branch information
Showing
6 changed files
with
217 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
{ | ||
"name": "@wordpress/dependency-injection", | ||
"version": "0.0.1", | ||
"description": "Dependency Injection container for WordPress.", | ||
"author": "The WordPress Contributors", | ||
"license": "GPL-2.0-or-later", | ||
"keywords": [ | ||
"wordpress", | ||
"gutenberg", | ||
"dom", | ||
"utils" | ||
], | ||
"homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/packages/dependency-injection/README.md", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/WordPress/gutenberg.git", | ||
"directory": "packages/injection" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/WordPress/gutenberg/issues" | ||
}, | ||
"engines": { | ||
"node": ">=12" | ||
}, | ||
"main": "build/index.js", | ||
"module": "build-module/index.js", | ||
"react-native": "src/index", | ||
"sideEffects": false, | ||
"dependencies": { | ||
"@babel/runtime": "^7.16.0" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
const CORE_MODULES_USING_EXPERIMENTS = [ | ||
'@wordpress/data', | ||
'@wordpress/block-editor', | ||
'@wordpress/block-library', | ||
'@wordpress/blocks', | ||
'@wordpress/core-data', | ||
'@wordpress/date', | ||
'@wordpress/edit-site', | ||
'@wordpress/edit-widgets', | ||
]; | ||
|
||
const registeredExperiments = {}; | ||
/* | ||
* Warning for theme and plugin developers. | ||
* | ||
* The use of experimental 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 experimental 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 plugin or theme will inevitably break on the next WordPress release.'; | ||
|
||
export const __dangerousOptInToUnstableAPIsOnlyForCoreModules = ( | ||
consent, | ||
moduleName | ||
) => { | ||
if ( ! CORE_MODULES_USING_EXPERIMENTS.includes( moduleName ) ) { | ||
throw new Error( | ||
`You tried to opt-in to unstable APIs as a 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 ( moduleName in registeredExperiments ) { | ||
throw new Error( | ||
`You tried to opt-in to unstable APIs as a 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.' | ||
); | ||
} | ||
registeredExperiments[ moduleName ] = { | ||
accessKey: {}, | ||
apis: {}, | ||
}; | ||
return { | ||
register: ( experiments ) => { | ||
for ( const key in experiments ) { | ||
registeredExperiments[ moduleName ].apis[ key ] = | ||
experiments[ key ]; | ||
} | ||
return registeredExperiments[ moduleName ].accessKey; | ||
}, | ||
unlock: ( accessKey ) => { | ||
for ( const experiment of Object.values( registeredExperiments ) ) { | ||
if ( experiment.accessKey === accessKey ) { | ||
return experiment.apis; | ||
} | ||
} | ||
|
||
throw new Error( | ||
'There is no registered module matching the specified access key' | ||
); | ||
}, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '../'; | ||
|
||
const requiredConsent = | ||
'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.'; | ||
|
||
describe( '__dangerousOptInToUnstableAPIsOnlyForCoreModules', () => { | ||
it( 'Should require a consent string', () => { | ||
expect( () => { | ||
__dangerousOptInToUnstableAPIsOnlyForCoreModules( | ||
'', | ||
'@wordpress/data' | ||
); | ||
} ).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, | ||
'@wordpress/edit-widgets' | ||
); | ||
__dangerousOptInToUnstableAPIsOnlyForCoreModules( | ||
requiredConsent, | ||
'@wordpress/edit-widgets' | ||
); | ||
} ).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, | ||
'@wordpress/edit-site' | ||
); | ||
expect( unstableAPIs.unlock ).toEqual( expect.any( Function ) ); | ||
expect( unstableAPIs.register ).toEqual( expect.any( Function ) ); | ||
} ); | ||
it( 'Should register and unlock experimental APIs', () => { | ||
// This would live in @wordpress/data: | ||
// Opt-in to experimental APIs | ||
const dataExperiments = | ||
__dangerousOptInToUnstableAPIsOnlyForCoreModules( | ||
requiredConsent, | ||
'@wordpress/data' | ||
); | ||
|
||
// Register the experimental APIs | ||
const dataExperimentalFunctions = { | ||
__experimentalFunction: jest.fn(), | ||
}; | ||
const dataAccessKey = dataExperiments.register( | ||
dataExperimentalFunctions | ||
); | ||
|
||
// This would live in @wordpress/core-data: | ||
// Register the experimental APIs | ||
const coreDataExperiments = | ||
__dangerousOptInToUnstableAPIsOnlyForCoreModules( | ||
requiredConsent, | ||
'@wordpress/core-data' | ||
); | ||
|
||
// Get the experimental APIs registered by @wordpress/data | ||
const { __experimentalFunction } = | ||
coreDataExperiments.unlock( dataAccessKey ); | ||
|
||
// Call one! | ||
__experimentalFunction(); | ||
|
||
expect( | ||
dataExperimentalFunctions.__experimentalFunction | ||
).toHaveBeenCalled(); | ||
} ); | ||
} ); |