diff --git a/.eslintrc.js b/.eslintrc.js index 0b0c71c39a2664..490c542f9d4565 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -92,6 +92,7 @@ module.exports = { ], globals: { wp: 'off', + globalThis: 'readonly', }, settings: { jsdoc: { @@ -104,7 +105,7 @@ module.exports = { 'jest/expect-expect': 'off', 'react/jsx-boolean-value': 'error', '@wordpress/dependency-group': 'error', - '@wordpress/is-gutenberg-plugin': 'error', + '@wordpress/wp-global-usage': 'error', '@wordpress/react-no-unsafe-timeout': 'error', '@wordpress/i18n-text-domain': [ 'error', diff --git a/docs/contributors/code/coding-guidelines.md b/docs/contributors/code/coding-guidelines.md index 06f86715a65a06..d89df5876e3804 100644 --- a/docs/contributors/code/coding-guidelines.md +++ b/docs/contributors/code/coding-guidelines.md @@ -141,9 +141,9 @@ An **plugin-only API** is one which is planned for eventual public availability, Plugin-only APIs are excluded from WordPress Core and only available in the Gutenberg Plugin: ```js -// Using process.env.IS_GUTENBERG_PLUGIN allows Webpack to exclude this +// Using globalThis.IS_GUTENBERG_PLUGIN allows Webpack to exclude this // export from WordPress core: -if ( process.env.IS_GUTENBERG_PLUGIN ) { +if ( globalThis.IS_GUTENBERG_PLUGIN ) { export { doSomethingExciting } from './api'; } ``` @@ -448,8 +448,8 @@ lock( privateApis, { privateEverywhere, privateInCorePublicInPlugin } ); // The privateInCorePublicInPlugin function is explicitly exported, // but this export will not be merged into WordPress core thanks to -// the process.env.IS_GUTENBERG_PLUGIN check. -if ( process.env.IS_GUTENBERG_PLUGIN ) { +// the globalThis.IS_GUTENBERG_PLUGIN check. +if ( globalThis.IS_GUTENBERG_PLUGIN ) { export const privateInCorePublicInPlugin = unlock( privateApis ).privateInCorePublicInPlugin; } diff --git a/docs/how-to-guides/feature-flags.md b/docs/how-to-guides/feature-flags.md index 5855f93f8ed9fc..11c5ae881337dd 100644 --- a/docs/how-to-guides/feature-flags.md +++ b/docs/how-to-guides/feature-flags.md @@ -2,9 +2,9 @@ 'Feature flags' are variables that allow you to prevent specific code in the Gutenberg project from being shipped to WordPress core, and to run certain experimental features only in the plugin. -## Introducing `process.env.IS_GUTENBERG_PLUGIN` +## Introducing `globalThis.IS_GUTENBERG_PLUGIN` -The `process.env.IS_GUTENBERG_PLUGIN` is an environment variable whose value 'flags' whether code is running within the Gutenberg plugin. +The `globalThis.IS_GUTENBERG_PLUGIN` is an environment variable whose value 'flags' whether code is running within the Gutenberg plugin. When the codebase is built for the plugin, this variable will be set to `true`. When building for WordPress core, it will be set to `false` or `undefined`. @@ -19,8 +19,9 @@ function myPluginOnlyFeature() { // implementation } -export const pluginOnlyFeature = - process.env.IS_GUTENBERG_PLUGIN ? myPluginOnlyFeature : undefined; +export const pluginOnlyFeature = globalThis.IS_GUTENBERG_PLUGIN + ? myPluginOnlyFeature + : undefined; ``` In the above example, the `pluginOnlyFeature` export will be `undefined` in non-plugin environments such as WordPress core. @@ -32,37 +33,39 @@ If you're attempting to import and call a plugin-only feature, be sure to wrap t ```js import { pluginOnlyFeature } from '@wordpress/foo'; -if ( process.env.IS_GUTENBERG_PLUGIN ) { +if ( globalThis.IS_GUTENBERG_PLUGIN ) { pluginOnlyFeature(); } ``` ## How it works -During the webpack build, instances of `process.env.IS_GUTENBERG_PLUGIN` will be replaced using webpack's [define plugin](https://webpack.js.org/plugins/define-plugin/). +During the webpack build, instances of `globalThis.IS_GUTENBERG_PLUGIN` will be replaced using webpack's [define plugin](https://webpack.js.org/plugins/define-plugin/). For example, in the following code – ```js -if ( process.env.IS_GUTENBERG_PLUGIN ) { +if ( globalThis.IS_GUTENBERG_PLUGIN ) { pluginOnlyFeature(); } ``` -– the variable `process.env.IS_GUTENBERG_PLUGIN` will be replaced with the boolean `true` during the plugin-only build: +– the variable `globalThis.IS_GUTENBERG_PLUGIN` will be replaced with the boolean `true` during the plugin-only build: ```js -if ( true ) { // Wepack has replaced `process.env.IS_GUTENBERG_PLUGIN` with `true` +if ( true ) { + // Wepack has replaced `globalThis.IS_GUTENBERG_PLUGIN` with `true` pluginOnlyFeature(); } ``` This ensures that code within the body of the `if` statement will always be executed. -In WordPress core, the `process.env.IS_GUTENBERG_PLUGIN` variable is replaced with `undefined`. The built code looks like this: +In WordPress core, the `globalThis.IS_GUTENBERG_PLUGIN` variable is replaced with `undefined`. The built code looks like this: ```js -if ( undefined ) { // Wepack has replaced `process.env.IS_GUTENBERG_PLUGIN` with `undefined` +if ( undefined ) { + // Webpack has replaced `globalThis.IS_GUTENBERG_PLUGIN` with `undefined` pluginOnlyFeature(); } ``` @@ -99,6 +102,6 @@ In this case, the minification process will remove the entire `if` statement inc ## Frequently asked questions -### Why shouldn't I assign the result of an expression involving `IS_GUTENBERG_PLUGIN` to a variable, e.g. `const isMyFeatureActive = process.env.IS_GUTENBERG_PLUGIN === 2`? +### Why shouldn't I assign the result of an expression involving `IS_GUTENBERG_PLUGIN` to a variable, e.g. `const isMyFeatureActive = ! Object.is( undefined, globalThis.IS_GUTENBERG_PLUGIN )`? Introducing complexity may prevent webpack's minifier from identifying and therefore eliminating dead code. Therefore it is recommended to use the examples in this document to ensure your feature flag functions as intended. For further details, see the [Dead Code Elimination](#dead-code-elimination) section. diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index c938638b9d06f1..ee42f9653ad8ad 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 8.35.0 (2024-05-16) ### Internal @@ -84,7 +88,7 @@ ### Breaking Changes -- Updated dependencies to require React 18 ([45235](https://github.com/WordPress/gutenberg/pull/45235)) +- Updated dependencies to require React 18 ([#45235](https://github.com/WordPress/gutenberg/pull/45235)) ## 7.19.0 (2022-11-16) diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 9cb2f44d05eb9b..56365c87a268fd 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -314,21 +314,21 @@ export const registerCoreBlocks = ( * __experimentalRegisterExperimentalCoreBlocks( settings ); * ``` */ -export const __experimentalRegisterExperimentalCoreBlocks = process.env - .IS_GUTENBERG_PLUGIN - ? ( { enableFSEBlocks } = {} ) => { - const enabledExperiments = [ enableFSEBlocks ? 'fse' : null ]; - getAllBlocks() - .filter( ( { metadata } ) => - isBlockMetadataExperimental( metadata ) - ) - .filter( - ( { metadata: { __experimental } } ) => - __experimental === true || - enabledExperiments.includes( __experimental ) - ) - .forEach( ( { init } ) => init() ); - } - : undefined; +export const __experimentalRegisterExperimentalCoreBlocks = + globalThis.IS_GUTENBERG_PLUGIN + ? ( { enableFSEBlocks } = {} ) => { + const enabledExperiments = [ enableFSEBlocks ? 'fse' : null ]; + getAllBlocks() + .filter( ( { metadata } ) => + isBlockMetadataExperimental( metadata ) + ) + .filter( + ( { metadata: { __experimental } } ) => + __experimental === true || + enabledExperiments.includes( __experimental ) + ) + .forEach( ( { init } ) => init() ); + } + : undefined; export { privateApis } from './private-apis'; diff --git a/packages/blocks/CHANGELOG.md b/packages/blocks/CHANGELOG.md index 85f09d262242c9..db9afca0d285a7 100644 --- a/packages/blocks/CHANGELOG.md +++ b/packages/blocks/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 12.35.0 (2024-05-16) ## 12.34.0 (2024-05-02) @@ -92,7 +96,8 @@ ## 11.17.0 (2022-09-21) -- The block attribute sources `children` and `node` have been deprecated. Please use the `html` source instead. See https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/ and the core blocks for examples. +- The block attribute sources `children` and `node` have been deprecated. Please use the `html` source instead. See https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/introducing-attributes-and-editable-fields/ and the core blocks for examples. + ## 11.16.0 (2022-09-13) ## 11.15.0 (2022-08-24) diff --git a/packages/blocks/src/api/parser/convert-legacy-block.js b/packages/blocks/src/api/parser/convert-legacy-block.js index c828a3f5db6c49..8396b98109792f 100644 --- a/packages/blocks/src/api/parser/convert-legacy-block.js +++ b/packages/blocks/src/api/parser/convert-legacy-block.js @@ -79,7 +79,7 @@ export function convertLegacyBlockNameAndAttributes( name, attributes ) { // The following code is only relevant for the Gutenberg plugin. // It's a stand-alone if statement for dead-code elimination. - if ( process.env.IS_GUTENBERG_PLUGIN ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { // Convert pattern overrides added during experimental phase. // Only four blocks were supported initially. // These checks can be removed in WordPress 6.6. diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 914e9e771ddff1..8f0bb757903979 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ### Enhancements - `ComboboxControl`: Introduce Combobox expandOnFocus prop ([#61705](https://github.com/WordPress/gutenberg/pull/61705)). diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index 81045d05c21fc5..2033a6f43fede6 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -9,10 +9,7 @@ "gutenberg-test-env", "jest", "@testing-library/jest-dom" - ], - // TODO: Remove `skipLibCheck` after resolving duplicate declaration of the `process` variable - // between `@types/webpack-env` (from @storybook packages) and `gutenberg-env`. - "skipLibCheck": true + ] }, "references": [ { "path": "../a11y" }, diff --git a/packages/core-data/CHANGELOG.md b/packages/core-data/CHANGELOG.md index 31df8b2074dbdc..348e1e7ea0e782 100644 --- a/packages/core-data/CHANGELOG.md +++ b/packages/core-data/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 6.35.0 (2024-05-16) ## 6.34.0 (2024-05-02) @@ -97,7 +101,7 @@ ### Breaking Changes -– Add TypeScript types to the built package (via "types": "build-types" in the package.json) +– Add TypeScript types to the built package (via "types": "build-types" in the package.json) ## 4.14.0 (2022-08-24) @@ -132,6 +136,7 @@ ## 4.3.0 (2022-03-23) ### New Features + - The saveEntityRecord, saveEditedEntityRecord, and deleteEntityRecord actions now accept an optional throwOnError option (defaults to false). When set to true, any exceptions occurring when the action was executing are re-thrown, causing dispatch().saveEntityRecord() to reject with an error. ([#39258](https://github.com/WordPress/gutenberg/pull/39258)) - Added support for fetching block patterns and their categories, with the `getBlockPatterns` and `getBlockPatternCategories` selectors. diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 454250e8ad13c9..be4d12f0cb9ef6 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -396,7 +396,7 @@ export const editEntityRecord = }, {} ), }; if ( window.__experimentalEnableSync && entityConfig.syncConfig ) { - if ( process.env.IS_GUTENBERG_PLUGIN ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { const objectId = entityConfig.getSyncObjectId( recordId ); getSyncProvider().update( entityConfig.syncObjectType + '--edit', diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index e91744110faf32..8d09402087cf90 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -485,7 +485,7 @@ export const getOrLoadEntitiesConfig = if ( configs?.length > 0 && hasConfig ) { if ( window.__experimentalEnableSync ) { - if ( process.env.IS_GUTENBERG_PLUGIN ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { registerSyncConfigs( configs ); } } @@ -506,7 +506,7 @@ export const getOrLoadEntitiesConfig = configs = await loader.loadEntities(); if ( window.__experimentalEnableSync ) { - if ( process.env.IS_GUTENBERG_PLUGIN ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { registerSyncConfigs( configs ); } } diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index f040e548e61605..3e5373eda6d6ab 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -81,7 +81,7 @@ export const getEntityRecord = entityConfig.syncConfig && ! query ) { - if ( process.env.IS_GUTENBERG_PLUGIN ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { const objectId = entityConfig.getSyncObjectId( key ); // Loads the persisted document. diff --git a/packages/customize-widgets/CHANGELOG.md b/packages/customize-widgets/CHANGELOG.md index 9c2687cf60b9c9..d6f184afe97366 100644 --- a/packages/customize-widgets/CHANGELOG.md +++ b/packages/customize-widgets/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 4.35.0 (2024-05-16) ## 4.34.0 (2024-05-02) diff --git a/packages/customize-widgets/src/index.js b/packages/customize-widgets/src/index.js index 5b438cac86f49b..9afda775a1701c 100644 --- a/packages/customize-widgets/src/index.js +++ b/packages/customize-widgets/src/index.js @@ -61,7 +61,7 @@ export function initialize( editorName, blockEditorSettings ) { } ); registerCoreBlocks( coreBlocks ); registerLegacyWidgetBlock(); - if ( process.env.IS_GUTENBERG_PLUGIN ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { __experimentalRegisterExperimentalCoreBlocks( { enableFSEBlocks: ENABLE_EXPERIMENTAL_FSE_BLOCKS, } ); diff --git a/packages/dataviews/CHANGELOG.md b/packages/dataviews/CHANGELOG.md index 711836f69eab23..ba4cd177561df3 100644 --- a/packages/dataviews/CHANGELOG.md +++ b/packages/dataviews/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 1.2.0 (2024-05-16) ### Internal diff --git a/packages/dataviews/tsconfig.json b/packages/dataviews/tsconfig.json index 60122ee152c80e..83c47d8320d838 100644 --- a/packages/dataviews/tsconfig.json +++ b/packages/dataviews/tsconfig.json @@ -4,7 +4,6 @@ "compilerOptions": { "rootDir": "src", "declarationDir": "build-types", - "skipLibCheck": true, "checkJs": false }, "references": [ diff --git a/packages/e2e-tests/CHANGELOG.md b/packages/e2e-tests/CHANGELOG.md index 1293ba8a6f2082..95cbb721932d5e 100644 --- a/packages/e2e-tests/CHANGELOG.md +++ b/packages/e2e-tests/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 7.29.0 (2024-05-16) ## 7.28.0 (2024-05-02) @@ -64,7 +68,7 @@ ### Breaking Changes -- Started requiring Jest v29 instead of v27 as a peer dependency. See [breaking changes in Jest 28](https://jestjs.io/blog/2022/04/25/jest-28) and [in jest 29](https://jestjs.io/blog/2022/08/25/jest-29) ([#47388](https://github.com/WordPress/gutenberg/pull/47388)) +- Started requiring Jest v29 instead of v27 as a peer dependency. See [breaking changes in Jest 28](https://jestjs.io/blog/2022/04/25/jest-28) and [in jest 29](https://jestjs.io/blog/2022/08/25/jest-29) ([#47388](https://github.com/WordPress/gutenberg/pull/47388)) ## 6.5.0 (2023-03-01) diff --git a/packages/e2e-tests/config/is-gutenberg-plugin.js b/packages/e2e-tests/config/is-gutenberg-plugin.js index d03b70b3ec11a3..5cfe3ada705df2 100644 --- a/packages/e2e-tests/config/is-gutenberg-plugin.js +++ b/packages/e2e-tests/config/is-gutenberg-plugin.js @@ -1,6 +1,3 @@ -global.process.env = { - ...global.process.env, - // Inject the `IS_GUTENBERG_PLUGIN` global, used for feature flagging. - // eslint-disable-next-line @wordpress/is-gutenberg-plugin - IS_GUTENBERG_PLUGIN: process.env.npm_package_config_IS_GUTENBERG_PLUGIN, -}; +// eslint-disable-next-line @wordpress/wp-global-usage +globalThis.IS_GUTENBERG_PLUGIN = + process.env.npm_package_config_IS_GUTENBERG_PLUGIN === 'true'; diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 03d6585fbfdf8f..763952b71ab604 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 7.35.0 (2024-05-16) ### Internal diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js index 3bb50999c2a929..c1e13d79f29411 100644 --- a/packages/edit-post/src/components/visual-editor/index.js +++ b/packages/edit-post/src/components/visual-editor/index.js @@ -24,7 +24,7 @@ import { usePaddingAppender } from './use-padding-appender'; const { EditorCanvas } = unlock( editorPrivateApis ); -const isGutenbergPlugin = process.env.IS_GUTENBERG_PLUGIN ? true : false; +const isGutenbergPlugin = globalThis.IS_GUTENBERG_PLUGIN ? true : false; export default function VisualEditor( { styles } ) { const { diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 0a2d17a344e474..1e0b3fe7d4d6ff 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -80,7 +80,7 @@ export function initializeEditor( registerCoreBlocks(); registerLegacyWidgetBlock( { inserter: false } ); registerWidgetGroupBlock( { inserter: false } ); - if ( process.env.IS_GUTENBERG_PLUGIN ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { __experimentalRegisterExperimentalCoreBlocks( { enableFSEBlocks: settings.__unstableEnableFullSiteEditingBlocks, } ); diff --git a/packages/edit-site/CHANGELOG.md b/packages/edit-site/CHANGELOG.md index d2eab07c746da1..3437c7c35fd9c4 100644 --- a/packages/edit-site/CHANGELOG.md +++ b/packages/edit-site/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 5.35.0 (2024-05-16) ### Internal diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index f42842f504e54d..41fc0a1985fded 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -42,7 +42,7 @@ export function initializeEditor( id, settings ) { dispatch( blocksStore ).setFreeformFallbackBlockName( 'core/html' ); registerLegacyWidgetBlock( { inserter: false } ); registerWidgetGroupBlock( { inserter: false } ); - if ( process.env.IS_GUTENBERG_PLUGIN ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { __experimentalRegisterExperimentalCoreBlocks( { enableFSEBlocks: true, } ); diff --git a/packages/edit-widgets/CHANGELOG.md b/packages/edit-widgets/CHANGELOG.md index 61bb76814f4db4..1c76fff573a054 100644 --- a/packages/edit-widgets/CHANGELOG.md +++ b/packages/edit-widgets/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 5.35.0 (2024-05-16) ### Internal diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index eb87d22fefef9e..2374ec19dabd7b 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -73,7 +73,7 @@ export function initializeEditor( id, settings ) { dispatch( blocksStore ).reapplyBlockTypeFilters(); registerCoreBlocks( coreBlocks ); registerLegacyWidgetBlock(); - if ( process.env.IS_GUTENBERG_PLUGIN ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { __experimentalRegisterExperimentalCoreBlocks( { enableFSEBlocks: ENABLE_EXPERIMENTAL_FSE_BLOCKS, } ); diff --git a/packages/editor/CHANGELOG.md b/packages/editor/CHANGELOG.md index 73b00e61139c8c..b33b049d245a4c 100644 --- a/packages/editor/CHANGELOG.md +++ b/packages/editor/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 13.35.0 (2024-05-16) ### Internal diff --git a/packages/editor/src/bindings/index.js b/packages/editor/src/bindings/index.js index ce77b87ebc7a5c..5824cdde022cc6 100644 --- a/packages/editor/src/bindings/index.js +++ b/packages/editor/src/bindings/index.js @@ -13,6 +13,6 @@ import postMeta from './post-meta'; const { registerBlockBindingsSource } = unlock( dispatch( blocksStore ) ); registerBlockBindingsSource( postMeta ); -if ( process.env.IS_GUTENBERG_PLUGIN ) { +if ( globalThis.IS_GUTENBERG_PLUGIN ) { registerBlockBindingsSource( patternOverrides ); } diff --git a/packages/editor/src/components/post-actions/actions.js b/packages/editor/src/components/post-actions/actions.js index 926d9b92fe5d13..f6a28c34213566 100644 --- a/packages/editor/src/components/post-actions/actions.js +++ b/packages/editor/src/components/post-actions/actions.js @@ -1116,7 +1116,7 @@ export function usePostActions( postType, onActionPerformed ) { const actions = [ postTypeObject?.viewable && viewPostAction, postRevisionsAction, - process.env.IS_GUTENBERG_PLUGIN + globalThis.IS_GUTENBERG_PLUGIN ? ! isTemplateOrTemplatePart && ! isPattern && duplicatePostAction diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 84251a677bd1ed..682fac4e8f1f03 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +### Breaking Changes + +- `@wordpress/is-gutenberg-plugin` rule has been replaced by `@wordpress/wp-global-usage` ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). +- `@wordpress/wp-process-env` rule has been added and included in the recommended configurations ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). +- `@wordpress/gutenberg-phase` rule has been removed (deprecated in v10.0.0) ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 18.1.0 (2024-05-16) ### Internal diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 1619778add661a..bd629a593f277d 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -69,25 +69,26 @@ The granular rulesets will not define any environment globals. As such, if they ### Rules -| Rule | Description | Recommended | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ----------- | -| [data-no-store-string-literals](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/data-no-store-string-literals.md) | Discourage passing string literals to reference data stores | | -| [dependency-group](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/dependency-group.md) | Enforce dependencies docblocks formatting | ✓ | -| [is-gutenberg-plugin](docs/rules/is-gutenberg-plugin.md) | Governs the use of the `process.env.IS_GUTENBERG_PLUGIN` constant | ✓ | -| [no-base-control-with-label-without-id](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-base-control-with-label-without-id.md) | Disallow the usage of BaseControl component with a label prop set but omitting the id property | ✓ | -| [no-unguarded-get-range-at](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md) | Disallow the usage of unguarded `getRangeAt` calls | ✓ | -| [no-unsafe-wp-apis](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unsafe-wp-apis.md) | Disallow the usage of unsafe APIs from `@wordpress/*` packages | ✓ | -| [no-unused-vars-before-return](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) | Disallow assigning variable values if unused before a return | ✓ | -| [react-no-unsafe-timeout](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/react-no-unsafe-timeout.md) | Disallow unsafe `setTimeout` in component | | -| [valid-sprintf](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/valid-sprintf.md) | Enforce valid sprintf usage | ✓ | -| [i18n-ellipsis](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-ellipsis.md) | Disallow using three dots in translatable strings | ✓ | -| [i18n-no-collapsible-whitespace](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-collapsible-whitespace.md) | Disallow collapsible whitespace in translatable strings | ✓ | -| [i18n-no-placeholders-only](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-placeholders-only.md) | Prevent using only placeholders in translatable strings | ✓ | -| [i18n-no-variables](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-variables.md) | Enforce string literals as translation function arguments | ✓ | -| [i18n-text-domain](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-text-domain.md) | Enforce passing valid text domains | ✓ | -| [i18n-translator-comments](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-translator-comments.md) | Enforce adding translator comments | ✓ | -| [i18n-no-flanking-whitespace](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-flanking-whitespace.md) | Disallow leading or trailing whitespace in translatable strings | | -| [i18n-hyphenated-range](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-hyphenated-range.md) | Disallow hyphenated numerical ranges in translatable strings | | +| Rule | Description | Recommended | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------- | +| [data-no-store-string-literals](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/data-no-store-string-literals.md) | Discourage passing string literals to reference data stores. | | +| [dependency-group](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/dependency-group.md) | Enforce dependencies docblocks formatting. | ✓ | +| [i18n-ellipsis](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-ellipsis.md) | Disallow using three dots in translatable strings. | ✓ | +| [i18n-hyphenated-range](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-hyphenated-range.md) | Disallow hyphenated numerical ranges in translatable strings. | | +| [i18n-no-collapsible-whitespace](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-collapsible-whitespace.md) | Disallow collapsible whitespace in translatable strings. | ✓ | +| [i18n-no-flanking-whitespace](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-flanking-whitespace.md) | Disallow leading or trailing whitespace in translatable strings. | | +| [i18n-no-placeholders-only](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-placeholders-only.md) | Prevent using only placeholders in translatable strings. | ✓ | +| [i18n-no-variables](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-no-variables.md) | Enforce string literals as translation function arguments. | ✓ | +| [i18n-text-domain](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-text-domain.md) | Enforce passing valid text domains. | ✓ | +| [i18n-translator-comments](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/i18n-translator-comments.md) | Enforce adding translator comments. | ✓ | +| [no-base-control-with-label-without-id](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-base-control-with-label-without-id.md) | Disallow the usage of BaseControl component with a label prop set but omitting the id property. | ✓ | +| [no-unguarded-get-range-at](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md) | Disallow the usage of unguarded `getRangeAt` calls. | ✓ | +| [no-unsafe-wp-apis](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unsafe-wp-apis.md) | Disallow the usage of unsafe APIs from `@wordpress/*` packagesl | ✓ | +| [no-unused-vars-before-return](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unused-vars-before-return.md) | Disallow assigning variable values if unused before a return. | ✓ | +| [no-wp-process-env](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-wp-process-env.md) | Disallow legacy usage of WordPress variables via `process.env` like `process.env.SCRIPT_DEBUG`. | ✓ | +| [react-no-unsafe-timeout](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/react-no-unsafe-timeout.md) | Disallow unsafe `setTimeout` in component. | | +| [valid-sprintf](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/valid-sprintf.md) | Enforce valid sprintf usage. | ✓ | +| [wp-global-usage](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/wp-global-usage.md) | Enforce correct usage of WordPress globals like `globalThis.SCRIPT_DEBUG`. | | ### Legacy diff --git a/packages/eslint-plugin/configs/custom.js b/packages/eslint-plugin/configs/custom.js index dcee5de349bad7..e6c67c0c6e4350 100644 --- a/packages/eslint-plugin/configs/custom.js +++ b/packages/eslint-plugin/configs/custom.js @@ -7,6 +7,7 @@ module.exports = { '@wordpress/no-global-active-element': 'error', '@wordpress/no-global-get-selection': 'error', '@wordpress/no-unsafe-wp-apis': 'error', + '@wordpress/no-wp-process-env': 'error', }, overrides: [ { diff --git a/packages/eslint-plugin/docs/rules/is-gutenberg-plugin.md b/packages/eslint-plugin/docs/rules/is-gutenberg-plugin.md deleted file mode 100644 index 99b51cf5cbb93b..00000000000000 --- a/packages/eslint-plugin/docs/rules/is-gutenberg-plugin.md +++ /dev/null @@ -1,61 +0,0 @@ -# The `IS_GUTENBERG_PLUGIN` global (is-gutenberg-plugin) - -To enable the use of feature flags in Gutenberg, the IS_GUTENBERG_PLUGIN global constant was introduced. This constant is replaced with a boolean value at build time using webpack's define plugin. - -There are a few rules around using this constant: - -- Only access `IS_GUTENBERG_PLUGIN` via `process.env`, e.g. `process.env.IS_GUTENBERG_PLUGIN`. This is required since webpack's define plugin only replaces exact matches of `process.env.IS_GUTENBERG_PLUGIN` in the codebase. -- The `IS_GUTENBERG_PLUGIN` variable should only be used as a simple boolean expression. -- `IS_GUTENBERG_PLUGIN` should only be used within the condition of an if statement, e.g. `if ( process.env.IS_GUTENBERG_PLUGIN ) { // implement feature here }` or ternary `process.env.IS_GUTENBERG_PLUGIN ? something : somethingElse`. This rule ensures code that is disabled through a feature flag is removed by dead code elimination. - -## Rule details - -Examples of **incorrect** code for this rule: - -```js -if ( IS_GUTENBERG_PLUGIN ) { - // implement feature here. -} -``` - -```js -if ( window[ 'IS_GUTENBERG_PLUGIN' ] ) { - // implement feature here. -} -``` - -```js -if ( process.env.IS_GUTENBERG_PLUGIN == 1 ) { - // implement feature here. -} -``` - -```js -if ( process.env.IS_GUTENBERG_PLUGIN === true ) { - // implement feature here. -} -``` - -```js -if ( true || process.env.IS_GUTENBERG_PLUGIN ) { - // implement feature here. -} -``` - -```js -const isMyFeatureActive = process.env.IS_GUTENBERG_PLUGIN; -``` - -Examples of **correct** code for this rule: - -```js -if ( process.env.IS_GUTENBERG_PLUGIN ) { - // implement feature here. -} -``` - -```js -if ( ! process.env.IS_GUTENBERG_PLUGIN ) { - return; -} -``` diff --git a/packages/eslint-plugin/docs/rules/no-wp-process-env.md b/packages/eslint-plugin/docs/rules/no-wp-process-env.md new file mode 100644 index 00000000000000..46fa6ed431c200 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-wp-process-env.md @@ -0,0 +1,23 @@ +# No WordPress process.env (no-wp-process-env) + +WordPress globals were accessed via `process.env` in the past. This practice created difficulty for +package consumers and was removed. + +The correct way to access these globals is now via `globalThis`, e.g. `globalThis.SCRIPT_DEBUG`. +This is safer for package consumers. + +This rule is fixable. + +## Rule details + +Examples of **incorrect** code for this rule: + +```js +process.env.SCRIPT_DEBUG; +``` + +Examples of **correct** code for this rule: + +```js +globalThis.SCRIPT_DEBUG; +``` diff --git a/packages/eslint-plugin/docs/rules/wp-global-usage.md b/packages/eslint-plugin/docs/rules/wp-global-usage.md new file mode 100644 index 00000000000000..d7b90b89258a90 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/wp-global-usage.md @@ -0,0 +1,60 @@ +# WordPress global usage (wp-global-usage) + +To enable the use of feature flags in Gutenberg some globals are used, such as `IS_GUTENBERG_PLUGIN` and `SCRIPT_DEBUG`. + +There are a few rules around using this constant: + +- Only access the globals via `globalThis`, e.g. `globalThis.IS_GUTENBERG_PLUGIN`. This allows the variables to be replaced compile time. +- The globals should only be used as a conditional test (negation is allowed). + +## Rule details + +Examples of **incorrect** code for this rule: + +```js +if ( IS_GUTENBERG_PLUGIN ) { + // implement feature here. +} +``` + +```js +if ( window[ 'IS_GUTENBERG_PLUGIN' ] ) { + // implement feature here. +} +``` + +```js +if ( globalThis.IS_GUTENBERG_PLUGIN == 1 ) { + // implement feature here. +} +``` + +```js +if ( globalThis.IS_GUTENBERG_PLUGIN === true ) { + // implement feature here. +} +``` + +```js +if ( true || globalThis.IS_GUTENBERG_PLUGIN ) { + // implement feature here. +} +``` + +```js +const isMyFeatureActive = globalThis.IS_GUTENBERG_PLUGIN; +``` + +Examples of **correct** code for this rule: + +```js +if ( globalThis.IS_GUTENBERG_PLUGIN ) { + // implement feature here. +} +``` + +```js +if ( ! globalThis.IS_GUTENBERG_PLUGIN ) { + return; +} +``` diff --git a/packages/eslint-plugin/rules/__tests__/is-gutenberg-plugin.js b/packages/eslint-plugin/rules/__tests__/is-gutenberg-plugin.js deleted file mode 100644 index c2ffa8415269dd..00000000000000 --- a/packages/eslint-plugin/rules/__tests__/is-gutenberg-plugin.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * External dependencies - */ -import { RuleTester } from 'eslint'; - -/** - * Internal dependencies - */ -import rule from '../is-gutenberg-plugin'; - -const ruleTester = new RuleTester( { - parserOptions: { - ecmaVersion: 6, - }, -} ); - -const ERROR_MESSAGE = - 'The `process.env.IS_GUTENBERG_PLUGIN` constant should only be used as the condition in an if statement or ternary expression.'; - -ruleTester.run( 'is-gutenberg-plugin', rule, { - valid: [ - { code: `if ( process.env.IS_GUTENBERG_PLUGIN ) {}` }, - { code: `if ( ! process.env.IS_GUTENBERG_PLUGIN ) {}` }, - { - // Ensure whitespace is ok. - code: `if ( - process.env. - IS_GUTENBERG_PLUGIN - ) {}`, - }, - { code: `const test = process.env.IS_GUTENBERG_PLUGIN ? foo : bar` }, - { code: `const test = ! process.env.IS_GUTENBERG_PLUGIN ? bar : foo` }, - { - // Ensure whitespace is ok. - code: `const test = ! process.env. - IS_GUTENBERG_PLUGIN ? bar : foo`, - }, - ], - invalid: [ - { - code: `if ( IS_GUTENBERG_PLUGIN ) {}`, - errors: [ { message: ERROR_MESSAGE } ], - }, - { - code: `if ( window[ 'IS_GUTENBERG_PLUGIN' ] ) {}`, - errors: [ { message: ERROR_MESSAGE } ], - }, - { - code: `if ( true ) { process.env.IS_GUTENBERG_PLUGIN === 2 }`, - errors: [ { message: ERROR_MESSAGE } ], - }, - { - code: `if ( process.env.IS_GUTENBERG_PLUGIN === 2 ) {}`, - errors: [ { message: ERROR_MESSAGE } ], - }, - { - code: `if ( true || process.env.IS_GUTENBERG_PLUGIN === 2 ) {}`, - errors: [ { message: ERROR_MESSAGE } ], - }, - { - code: `const isFeatureActive = process.env.IS_GUTENBERG_PLUGIN;`, - errors: [ { message: ERROR_MESSAGE } ], - }, - ], -} ); diff --git a/packages/eslint-plugin/rules/__tests__/no-wp-process-env.js b/packages/eslint-plugin/rules/__tests__/no-wp-process-env.js new file mode 100644 index 00000000000000..330ed560aea992 --- /dev/null +++ b/packages/eslint-plugin/rules/__tests__/no-wp-process-env.js @@ -0,0 +1,62 @@ +/** + * External dependencies + */ +import { RuleTester } from 'eslint'; + +/** + * Internal dependencies + */ +import rule from '../no-wp-process-env'; + +const ruleTester = new RuleTester( { + parserOptions: { + ecmaVersion: 6, + }, +} ); + +ruleTester.run( 'no-wp-process-env', rule, { + valid: [ + { code: 'process.env.NODE_ENV' }, + { code: 'process.env.WHATEVER' }, + { code: 'process.env[foo]' }, + { code: 'process.env["foo"]' }, + { code: `process['env']["foo"]` }, + { code: "process['env'][`foo`]" }, + { code: "process.env[`${ '' }IS_GUTENBERG_PLUGIN`]" }, + { code: `a.b.c` }, + { code: `x['y']['z']` }, + { code: `d[e][f]` }, + { code: `process[ (()=>'env')() ][ {_:'SCRIPT_DEBUG'}['_'] ]` }, + ], + invalid: [ + { + code: 'process.env.IS_GUTENBERG_PLUGIN', + errors: [ { messageId: 'useGlobalThis' } ], + output: 'globalThis.IS_GUTENBERG_PLUGIN', + }, + { + code: 'process.env.SCRIPT_DEBUG', + errors: [ { messageId: 'useGlobalThis' } ], + output: 'globalThis.SCRIPT_DEBUG', + }, + { + code: 'process.env.IS_WORDPRESS_CORE', + errors: [ { messageId: 'useGlobalThis' } ], + output: 'globalThis.IS_WORDPRESS_CORE', + }, + { + code: `process['env']["IS_GUTENBERG_PLUGIN"]`, + errors: [ { messageId: 'useGlobalThis' } ], + output: 'globalThis.IS_GUTENBERG_PLUGIN', + }, + { + code: 'process[`env`][`IS_GUTENBERG_PLUGIN`]', + errors: [ { messageId: 'useGlobalThis' } ], + output: 'globalThis.IS_GUTENBERG_PLUGIN', + }, + { + code: 'process.env.GUTENBERG_PHASE', + errors: [ { messageId: 'noGutenbergPhase' } ], + }, + ], +} ); diff --git a/packages/eslint-plugin/rules/__tests__/wp-global-usage.js b/packages/eslint-plugin/rules/__tests__/wp-global-usage.js new file mode 100644 index 00000000000000..9d3dc9b2a29d72 --- /dev/null +++ b/packages/eslint-plugin/rules/__tests__/wp-global-usage.js @@ -0,0 +1,129 @@ +/** + * External dependencies + */ +import { RuleTester } from 'eslint'; + +/** + * Internal dependencies + */ +import rule from '../wp-global-usage'; + +const ruleTester = new RuleTester( { + parserOptions: { + ecmaVersion: 6, + }, +} ); + +ruleTester.run( 'wp-global-usage', rule, { + valid: [ + { code: "const text = 'SCRIPT_DEBUG'" }, + { code: 'const config = { SCRIPT_DEBUG: true }' }, + { code: 'if ( globalThis.IS_GUTENBERG_PLUGIN ) {}' }, + { code: 'if ( globalThis.IS_WORDPRESS_CORE ) {}' }, + { code: 'if ( globalThis.SCRIPT_DEBUG ) {}' }, + { code: 'if ( process.env.SCRIPT_DEBUG ) {}' }, + { code: 'if ( ! globalThis.IS_GUTENBERG_PLUGIN ) {}' }, + { + // Ensure whitespace is ok. + code: `if ( + globalThis. + IS_GUTENBERG_PLUGIN + ) {}`, + }, + { code: 'const test = globalThis.IS_GUTENBERG_PLUGIN ? foo : bar' }, + { code: 'const test = ! globalThis.IS_GUTENBERG_PLUGIN ? bar : foo' }, + { + // Ensure whitespace is ok. + code: `const test = ! globalThis. + IS_GUTENBERG_PLUGIN ? bar : foo`, + }, + ], + invalid: [ + { + code: 'if ( IS_GUTENBERG_PLUGIN ) {}', + errors: [ + { + messageId: 'usedWithoutGlobalThis', + data: { name: 'IS_GUTENBERG_PLUGIN' }, + }, + ], + output: 'if ( globalThis.IS_GUTENBERG_PLUGIN ) {}', + }, + { + code: 'if ( window.IS_GUTENBERG_PLUGIN ) {}', + errors: [ + { + messageId: 'usedWithoutGlobalThis', + data: { name: 'IS_GUTENBERG_PLUGIN' }, + }, + ], + output: 'if ( globalThis.IS_GUTENBERG_PLUGIN ) {}', + }, + { + code: 'if ( SCRIPT_DEBUG ) {}', + errors: [ + { + messageId: 'usedWithoutGlobalThis', + data: { name: 'SCRIPT_DEBUG' }, + }, + ], + output: 'if ( globalThis.SCRIPT_DEBUG ) {}', + }, + { + code: 'if ( IS_WORDPRESS_CORE ) {}', + errors: [ + { + messageId: 'usedWithoutGlobalThis', + data: { name: 'IS_WORDPRESS_CORE' }, + }, + ], + output: 'if ( globalThis.IS_WORDPRESS_CORE ) {}', + }, + { + code: "if ( window[ 'IS_GUTENBERG_PLUGIN' ] ) {}", + errors: [ + { + messageId: 'usedWithoutGlobalThis', + data: { name: 'IS_GUTENBERG_PLUGIN' }, + }, + ], + output: 'if ( globalThis.IS_GUTENBERG_PLUGIN ) {}', + }, + { + code: 'if ( true ) { globalThis.IS_GUTENBERG_PLUGIN === 2 }', + errors: [ + { + messageId: 'usedOutsideConditional', + data: { name: 'IS_GUTENBERG_PLUGIN' }, + }, + ], + }, + { + code: 'if ( globalThis.IS_GUTENBERG_PLUGIN === 2 ) {}', + errors: [ + { + messageId: 'usedOutsideConditional', + data: { name: 'IS_GUTENBERG_PLUGIN' }, + }, + ], + }, + { + code: 'if ( true || globalThis.IS_GUTENBERG_PLUGIN === 2 ) {}', + errors: [ + { + messageId: 'usedOutsideConditional', + data: { name: 'IS_GUTENBERG_PLUGIN' }, + }, + ], + }, + { + code: 'const isFeatureActive = globalThis.IS_GUTENBERG_PLUGIN;', + errors: [ + { + messageId: 'usedOutsideConditional', + data: { name: 'IS_GUTENBERG_PLUGIN' }, + }, + ], + }, + ], +} ); diff --git a/packages/eslint-plugin/rules/gutenberg-phase.js b/packages/eslint-plugin/rules/gutenberg-phase.js deleted file mode 100644 index e3ec9ccf8180ef..00000000000000 --- a/packages/eslint-plugin/rules/gutenberg-phase.js +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Traverse up through the chain of parent AST nodes returning the first parent - * the predicate returns a truthy value for. - * - * @param {Object} sourceNode The AST node to search from. - * @param {Function} predicate A predicate invoked for each parent. - * - * @return {Object | undefined} The first encountered parent node where the predicate - * returns a truthy value. - */ -function findParent( sourceNode, predicate ) { - if ( ! sourceNode.parent ) { - return; - } - - if ( predicate( sourceNode.parent ) ) { - return sourceNode.parent; - } - - return findParent( sourceNode.parent, predicate ); -} - -/** - * Tests whether the GUTENBERG_PHASE variable is accessed via - * `process.env.GUTENBERG_PHASE`. - * - * @example - * ```js - * // good - * if ( process.env.GUTENBERG_PHASE === 2 ) { - * - * // bad - * if ( GUTENBERG_PHASE === 2 ) { - * ``` - * - * @param {Object} node The GUTENBERG_PHASE identifier node. - * @param {Object} context The eslint context object. - */ -function testIsAccessedViaProcessEnv( node, context ) { - const parent = node.parent; - - if ( - parent && - parent.type === 'MemberExpression' && - context.getSource( parent ) === 'process.env.GUTENBERG_PHASE' - ) { - return; - } - - context.report( - node, - 'The `GUTENBERG_PHASE` constant should be accessed using `process.env.GUTENBERG_PHASE`.' - ); -} - -/** - * Tests whether the GUTENBERG_PHASE variable is used in a strict binary - * equality expression in a comparison with a number, triggering a - * violation if not. - * - * @example - * ```js - * // good - * if ( process.env.GUTENBERG_PHASE === 2 ) { - * - * // bad - * if ( process.env.GUTENBERG_PHASE >= '2' ) { - * ``` - * - * @param {Object} node The GUTENBERG_PHASE identifier node. - * @param {Object} context The eslint context object. - */ -function testIsUsedInStrictBinaryExpression( node, context ) { - const parent = findParent( - node, - ( candidate ) => candidate.type === 'BinaryExpression' - ); - - if ( parent ) { - const comparisonNode = - node.parent.type === 'MemberExpression' ? node.parent : node; - - // Test for process.env.GUTENBERG_PHASE === or === process.env.GUTENBERG_PHASE. - const hasCorrectOperator = [ '===', '!==' ].includes( parent.operator ); - const hasCorrectOperands = - ( parent.left === comparisonNode && - typeof parent.right.value === 'number' ) || - ( parent.right === comparisonNode && - typeof parent.left.value === 'number' ); - - if ( hasCorrectOperator && hasCorrectOperands ) { - return; - } - } - - context.report( - node, - 'The `GUTENBERG_PHASE` constant should only be used in a strict equality comparison with a primitive number.' - ); -} - -/** - * Tests whether the GUTENBERG_PHASE variable is used as the condition for an - * if statement, triggering a violation if not. - * - * @example - * ```js - * // good - * if ( process.env.GUTENBERG_PHASE === 2 ) { - * - * // bad - * const isFeatureActive = process.env.GUTENBERG_PHASE === 2; - * ``` - * - * @param {Object} node The GUTENBERG_PHASE identifier node. - * @param {Object} context The eslint context object. - */ -function testIsUsedInIfOrTernary( node, context ) { - const conditionalParent = findParent( node, ( candidate ) => - [ 'IfStatement', 'ConditionalExpression' ].includes( candidate.type ) - ); - const binaryParent = findParent( - node, - ( candidate ) => candidate.type === 'BinaryExpression' - ); - - if ( - conditionalParent && - binaryParent && - conditionalParent.test && - conditionalParent.test.range[ 0 ] === binaryParent.range[ 0 ] && - conditionalParent.test.range[ 1 ] === binaryParent.range[ 1 ] - ) { - return; - } - - context.report( - node, - 'The `GUTENBERG_PHASE` constant should only be used as part of the condition in an if statement or ternary expression.' - ); -} - -module.exports = { - meta: { - type: 'problem', - schema: [], - deprecated: true, - replacedBy: '@wordpress/is-gutenberg-plugin', - }, - create( context ) { - return { - Identifier( node ) { - // Bypass any identifiers with a node name different to `GUTENBERG_PHASE`. - if ( node.name !== 'GUTENBERG_PHASE' ) { - return; - } - - testIsAccessedViaProcessEnv( node, context ); - testIsUsedInStrictBinaryExpression( node, context ); - testIsUsedInIfOrTernary( node, context ); - }, - Literal( node ) { - // Bypass any identifiers with a node value different to `GUTENBERG_PHASE`. - if ( node.value !== 'GUTENBERG_PHASE' ) { - return; - } - - if ( node.parent && node.parent.type === 'MemberExpression' ) { - testIsAccessedViaProcessEnv( node, context ); - } - }, - }; - }, -}; diff --git a/packages/eslint-plugin/rules/is-gutenberg-plugin.js b/packages/eslint-plugin/rules/is-gutenberg-plugin.js deleted file mode 100644 index 00475545e6158c..00000000000000 --- a/packages/eslint-plugin/rules/is-gutenberg-plugin.js +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Traverse up through the chain of parent AST nodes returning the first parent - * the predicate returns a truthy value for. - * - * @param {Object} sourceNode The AST node to search from. - * @param {Function} predicate A predicate invoked for each parent. - * - * @return {Object | undefined} The first encountered parent node where the predicate - * returns a truthy value. - */ -function findParent( sourceNode, predicate ) { - if ( ! sourceNode.parent ) { - return; - } - - if ( predicate( sourceNode.parent ) ) { - return sourceNode.parent; - } - - return findParent( sourceNode.parent, predicate ); -} - -/** - * Tests whether the IS_GUTENBERG_PLUGIN variable is used as the condition for an - * if statement or ternary, triggering a violation if not. - * - * @example - * ```js - * // good - * if ( process.env.IS_GUTENBERG_PLUGIN ) { - * - * // bad - * const isFeatureActive = process.env.IS_GUTENBERG_PLUGIN; - * ``` - * - * @param {Object} node The IS_GUTENBERG_PLUGIN identifier node. - * @param {Object} context The eslint context object. - */ -function isUsedInConditional( node, context ) { - const conditionalParent = findParent( node, ( candidate ) => - [ 'IfStatement', 'ConditionalExpression' ].includes( candidate.type ) - ); - - if ( ! conditionalParent ) { - return false; - } - - // Allow for whitespace as prettier sometimes breaks this on separate lines. - const textRegex = /^\s*!?\s*process\s*\.\s*env\s*\.\s*IS_GUTENBERG_PLUGIN$/; - const testSource = context.getSource( conditionalParent.test ); - - if ( ! textRegex.test( testSource ) ) { - return false; - } - - return true; -} - -const ERROR_MESSAGE = - 'The `process.env.IS_GUTENBERG_PLUGIN` constant should only be used as the condition in an if statement or ternary expression.'; - -module.exports = { - meta: { - type: 'problem', - schema: [], - }, - create( context ) { - return { - Identifier( node ) { - // Bypass any identifiers with a node name different to `IS_GUTENBERG_PLUGIN`. - if ( node.name !== 'IS_GUTENBERG_PLUGIN' ) { - return; - } - - if ( ! isUsedInConditional( node, context ) ) { - context.report( node, ERROR_MESSAGE ); - } - }, - // Check for literals, e.g. when 'IS_GUTENBERG_PLUGIN' is used as a string via something like 'window[ 'IS_GUTENBERG_PLUGIN' ]'. - Literal( node ) { - // Bypass any identifiers with a node value different to `IS_GUTENBERG_PLUGIN`. - if ( node.value !== 'IS_GUTENBERG_PLUGIN' ) { - return; - } - - if ( node.parent && node.parent.type === 'MemberExpression' ) { - if ( ! isUsedInConditional( node, context ) ) { - context.report( node, ERROR_MESSAGE ); - } - } - }, - }; - }, -}; diff --git a/packages/eslint-plugin/rules/no-wp-process-env.js b/packages/eslint-plugin/rules/no-wp-process-env.js new file mode 100644 index 00000000000000..55aca44b92ba74 --- /dev/null +++ b/packages/eslint-plugin/rules/no-wp-process-env.js @@ -0,0 +1,93 @@ +const NAMES = new Set( + /** @type {const} */ ( [ + 'GUTENBERG_PHASE', + 'IS_GUTENBERG_PLUGIN', + 'IS_WORDPRESS_CORE', + 'SCRIPT_DEBUG', + ] ) +); + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + schema: [], + fixable: true, + messages: { + useGlobalThis: + '`{{ name }}` should not be accessed from process.env. Use `globalThis.{{name}}`.', + noGutenbergPhase: + 'The GUTENBERG_PHASE environement variable is no longer available. Use IS_GUTENBERG_PLUGIN (boolean).', + }, + }, + create( context ) { + return { + MemberExpression( node ) { + const propertyNameOrValue = memberProperty( node ); + if ( ! propertyNameOrValue ) { + return; + } + if ( ! NAMES.has( propertyNameOrValue ) ) { + return; + } + + if ( node.object.type !== 'MemberExpression' ) { + return; + } + + const obj = node.object; + const envCandidateProperty = memberProperty( obj ); + if ( envCandidateProperty !== 'env' ) { + return; + } + + if ( + obj.object.type !== 'Identifier' || + obj.object.name !== 'process' + ) { + return; + } + + if ( propertyNameOrValue === 'GUTENBERG_PHASE' ) { + context.report( { + node, + messageId: 'noGutenbergPhase', + } ); + return; + } + + context.report( { + node, + messageId: 'useGlobalThis', + data: { name: propertyNameOrValue }, + fix( fixer ) { + return fixer.replaceText( + node, + `globalThis.${ propertyNameOrValue }` + ); + }, + } ); + }, + }; + }, +}; + +/** + * @param {import('estree').MemberExpression} node + */ +function memberProperty( node ) { + switch ( node.property.type ) { + case 'Identifier': + return node.property.name; + case 'Literal': + return node.property.value; + case 'TemplateLiteral': + if ( + ! node.property.expressions.length && + node.property.quasis.length === 1 + ) { + return node.property.quasis[ 0 ].value.raw; + } + } + return null; +} diff --git a/packages/eslint-plugin/rules/wp-global-usage.js b/packages/eslint-plugin/rules/wp-global-usage.js new file mode 100644 index 00000000000000..c6c75d99331238 --- /dev/null +++ b/packages/eslint-plugin/rules/wp-global-usage.js @@ -0,0 +1,170 @@ +const NAMES = new Set( + /** @type {const} */ ( [ + 'IS_GUTENBERG_PLUGIN', + 'IS_WORDPRESS_CORE', + 'SCRIPT_DEBUG', + ] ) +); + +/** + * Tests whether the IS_GUTENBERG_PLUGIN variable is used as the condition for an + * if statement or ternary, triggering a violation if not. + * + * @example + * ```js + * // good + * if ( process.env.IS_GUTENBERG_PLUGIN ) { + * + * // bad + * const isFeatureActive = process.env.IS_GUTENBERG_PLUGIN; + * ``` + * + * @param {import('estree').Node} node The IS_GUTENBERG_PLUGIN identifier node. + */ +function isUsedInConditional( node ) { + /** @type {import('estree').Node|undefined} */ + let current = node; + + // Simple negation is the only expresion allowed in the conditional: + // if ( ! globalThis.SCRIPT_DEBUG ) {} + // const D = ! globalThis.SCRIPT_DEBUG ? 'yes' : 'no'; + if ( + current.parent.type === 'UnaryExpression' && + current.parent.operator === '!' + ) { + current = current.parent; + } + + // Check if the current node is the test of a conditional + + /** @type {import('estree').Node|undefined} */ + const parent = current.parent; + + if ( parent.type === 'IfStatement' && parent.test === current ) { + return true; + } + if ( parent.type === 'ConditionalExpression' && parent.test === current ) { + return true; + } + return false; +} + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + schema: [], + fixable: true, + messages: { + usedOutsideConditional: + '`globalThis.{{ name }}` should only be used as the condition in an if statement or ternary expression.', + usedWithoutGlobalThis: + '`{{ name }}` should not be used directly. Use `globalThis.{{ name }}`.', + }, + }, + create( context ) { + return { + Identifier( node ) { + // Bypass any identifiers with a node name different to `IS_GUTENBERG_PLUGIN`. + if ( ! NAMES.has( node.name ) ) { + return; + } + + if ( node.parent.type === 'Property' ) { + return; + } + + if ( node.parent.type !== 'MemberExpression' ) { + context.report( { + node, + messageId: 'usedWithoutGlobalThis', + data: { name: node.name }, + fix( fixer ) { + return fixer.replaceText( + node, + `globalThis.${ node.name }` + ); + }, + } ); + + if ( ! isUsedInConditional( node ) ) { + context.report( { + node, + messageId: 'usedOutsideConditional', + data: { + name: node.name, + }, + } ); + } + return; + } + + if ( + node.parent.object.type === 'Identifier' && + node.parent.object.name !== 'globalThis' + ) { + context.report( { + node, + messageId: 'usedWithoutGlobalThis', + data: { name: node.name }, + fix( fixer ) { + if ( node.parent.object.name === 'window' ) { + return fixer.replaceText( + node.parent, + `globalThis.${ node.name }` + ); + } + }, + } ); + } else if ( ! isUsedInConditional( node.parent ) ) { + context.report( { + node, + messageId: 'usedOutsideConditional', + data: { + name: node.name, + }, + } ); + } + }, + + // Check for literals, e.g. when 'IS_GUTENBERG_PLUGIN' is used as a string via something like 'window[ 'IS_GUTENBERG_PLUGIN' ]'. + Literal( node ) { + // Bypass any identifiers with a node value different to `IS_GUTENBERG_PLUGIN`. + if ( ! NAMES.has( node.value ) ) { + return; + } + + if ( node.parent.type !== 'MemberExpression' ) { + return; + } + + if ( + node.parent.object.type === 'Identifier' && + node.parent.object.name !== 'globalThis' + ) { + context.report( { + node, + messageId: 'usedWithoutGlobalThis', + data: { name: node.value }, + fix( fixer ) { + if ( node.parent.object.name === 'window' ) { + return fixer.replaceText( + node.parent, + `globalThis.${ node.value }` + ); + } + }, + } ); + } else if ( ! isUsedInConditional( node.parent ) ) { + context.report( { + node, + messageId: 'usedOutsideConditional', + data: { + name: node.value, + }, + } ); + } + }, + }; + }, +}; diff --git a/packages/interactivity-router/CHANGELOG.md b/packages/interactivity-router/CHANGELOG.md index bae126d0023182..496d76e7bdecc0 100644 --- a/packages/interactivity-router/CHANGELOG.md +++ b/packages/interactivity-router/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 1.8.0 (2024-05-16) ## 1.7.0 (2024-05-02) diff --git a/packages/interactivity-router/src/index.ts b/packages/interactivity-router/src/index.ts index 09f484131b62ee..79b67eeb98e656 100644 --- a/packages/interactivity-router/src/index.ts +++ b/packages/interactivity-router/src/index.ts @@ -85,8 +85,7 @@ const fetchPage = async ( url: string, { html }: { html: string } ) => { const regionsToVdom: RegionsToVdom = async ( dom, { vdom } = {} ) => { const regions = { body: undefined }; let head: HTMLElement[]; - // @ts-ignore - if ( process.env.IS_GUTENBERG_PLUGIN ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { if ( navigationMode === 'fullPage' ) { head = await fetchHeadAssets( dom, headElements ); regions.body = vdom @@ -111,8 +110,7 @@ const regionsToVdom: RegionsToVdom = async ( dom, { vdom } = {} ) => { // Render all interactive regions contained in the given page. const renderRegions = ( page: Page ) => { batch( () => { - // @ts-ignore - if ( process.env.IS_GUTENBERG_PLUGIN ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { if ( navigationMode === 'fullPage' ) { // Once this code is tested and more mature, the head should be updated for region based navigation as well. updateHead( page.head ); @@ -169,8 +167,7 @@ window.addEventListener( 'popstate', async () => { // Initialize the router and cache the initial page using the initial vDOM. // Once this code is tested and more mature, the head should be updated for // region based navigation as well. -// @ts-ignore -if ( process.env.IS_GUTENBERG_PLUGIN ) { +if ( globalThis.IS_GUTENBERG_PLUGIN ) { if ( navigationMode === 'fullPage' ) { // Cache the scripts. Has to be called before fetching the assets. [].map.call( document.querySelectorAll( 'script[src]' ), ( script ) => { @@ -367,8 +364,7 @@ export const { state, actions } = store( 'core/router', { } ); // Add click and prefetch to all links. -// @ts-ignore -if ( process.env.IS_GUTENBERG_PLUGIN ) { +if ( globalThis.IS_GUTENBERG_PLUGIN ) { if ( navigationMode === 'fullPage' ) { // Navigate on click. document.addEventListener( diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md index c976f26dc7136c..bcc1ffcf8a3fce 100644 --- a/packages/interactivity/CHANGELOG.md +++ b/packages/interactivity/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 5.7.0 (2024-05-16) ### Enhancements @@ -10,9 +14,9 @@ ### Bug Fixes -- Allow multiple event handlers for the same type with `data-wp-on-document` and `data-wp-on-window`. ([#61009](https://github.com/WordPress/gutenberg/pull/61009)) -- Prevent wrong written directives from killing the runtime ([#61249](https://github.com/WordPress/gutenberg/pull/61249)) -- Prevent empty namespace or different namespaces from killing the runtime ([#61409](https://github.com/WordPress/gutenberg/pull/61409)) +- Allow multiple event handlers for the same type with `data-wp-on-document` and `data-wp-on-window`. ([#61009](https://github.com/WordPress/gutenberg/pull/61009)) +- Prevent wrong written directives from killing the runtime ([#61249](https://github.com/WordPress/gutenberg/pull/61249)) +- Prevent empty namespace or different namespaces from killing the runtime ([#61409](https://github.com/WordPress/gutenberg/pull/61409)) ## 5.6.0 (2024-05-02) @@ -20,7 +24,7 @@ ### Enhancements -- Improve data-wp-context debugging by validating it as a stringified JSON Object. ([#61045](https://github.com/WordPress/gutenberg/pull/61045)) +- Improve data-wp-context debugging by validating it as a stringified JSON Object. ([#61045](https://github.com/WordPress/gutenberg/pull/61045)) ### Bug Fixes diff --git a/packages/interactivity/src/utils.ts b/packages/interactivity/src/utils.ts index ecf22899309e97..e709ba1ce3b0ee 100644 --- a/packages/interactivity/src/utils.ts +++ b/packages/interactivity/src/utils.ts @@ -316,8 +316,7 @@ const logged: Set< string > = new Set(); * @param message Message to show in the warning. */ export const warn = ( message: string ): void => { - // @ts-expect-error - if ( typeof SCRIPT_DEBUG !== 'undefined' && SCRIPT_DEBUG === true ) { + if ( globalThis.SCRIPT_DEBUG ) { if ( logged.has( message ) ) { return; } diff --git a/packages/jest-preset-default/CHANGELOG.md b/packages/jest-preset-default/CHANGELOG.md index d709168df37041..f891b892ed8bae 100644 --- a/packages/jest-preset-default/CHANGELOG.md +++ b/packages/jest-preset-default/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 11.29.0 (2024-05-16) ## 11.28.0 (2024-05-02) @@ -64,7 +68,7 @@ ### Breaking Changes -- Started requiring Jest v29 instead of v27 as a peer dependency. See [breaking changes in Jest 28](https://jestjs.io/blog/2022/04/25/jest-28) and [in jest 29](https://jestjs.io/blog/2022/08/25/jest-29) ([#47388](https://github.com/WordPress/gutenberg/pull/47388)) +- Started requiring Jest v29 instead of v27 as a peer dependency. See [breaking changes in Jest 28](https://jestjs.io/blog/2022/04/25/jest-28) and [in jest 29](https://jestjs.io/blog/2022/08/25/jest-29) ([#47388](https://github.com/WordPress/gutenberg/pull/47388)) ## 10.9.0 (2023-03-01) diff --git a/packages/jest-preset-default/scripts/setup-globals.js b/packages/jest-preset-default/scripts/setup-globals.js index abd99f620dc893..37922a3302e70f 100644 --- a/packages/jest-preset-default/scripts/setup-globals.js +++ b/packages/jest-preset-default/scripts/setup-globals.js @@ -1,5 +1,6 @@ // Run all tests with development tools enabled. -global.SCRIPT_DEBUG = true; +// eslint-disable-next-line @wordpress/wp-global-usage +globalThis.SCRIPT_DEBUG = true; // These are necessary to load TinyMCE successfully. global.URL = window.URL; diff --git a/packages/plugins/CHANGELOG.md b/packages/plugins/CHANGELOG.md index 7f9cdc62909fa2..49ff11a846d8e8 100644 --- a/packages/plugins/CHANGELOG.md +++ b/packages/plugins/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 6.26.0 (2024-05-16) ## 6.25.0 (2024-05-02) diff --git a/packages/plugins/tsconfig.json b/packages/plugins/tsconfig.json index d20dcecf0d613b..9a0da807348b21 100644 --- a/packages/plugins/tsconfig.json +++ b/packages/plugins/tsconfig.json @@ -4,8 +4,7 @@ "compilerOptions": { "rootDir": "src", "declarationDir": "build-types", - "types": [ "gutenberg-env" ], - "skipLibCheck": true + "types": [ "gutenberg-env" ] }, "references": [ { "path": "../components" }, diff --git a/packages/private-apis/CHANGELOG.md b/packages/private-apis/CHANGELOG.md index f1564b6ef5db1c..07c8caaa3f8fe3 100644 --- a/packages/private-apis/CHANGELOG.md +++ b/packages/private-apis/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 0.40.0 (2024-05-16) ## 0.39.0 (2024-05-02) diff --git a/packages/private-apis/src/implementation.js b/packages/private-apis/src/implementation.js index a31fd91ce094dd..bea820226fe332 100644 --- a/packages/private-apis/src/implementation.js +++ b/packages/private-apis/src/implementation.js @@ -66,7 +66,7 @@ let allowReRegistration; // 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; + allowReRegistration = globalThis.IS_WORDPRESS_CORE ? false : true; } catch ( error ) { allowReRegistration = true; } diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 8c5aa8c623ef09..124a5e99bfdf60 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -5,6 +5,7 @@ ### Breaking Changes - Use React's automatic runtime to transform JSX ([#61692](https://github.com/WordPress/gutenberg/pull/61692)). +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). ## 27.9.0 (2024-05-16) diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index b7f61eb215eaf0..f306fbd54a8a6b 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -307,7 +307,8 @@ const scriptConfig = { plugins: [ new webpack.DefinePlugin( { // Inject the `SCRIPT_DEBUG` global, used for development features flagging. - SCRIPT_DEBUG: ! isProduction, + 'globalThis.SCRIPT_DEBUG': JSON.stringify( ! isProduction ), + SCRIPT_DEBUG: JSON.stringify( ! isProduction ), } ), // If we run a modules build, the 2 compilations can "clean" each other's output @@ -456,7 +457,8 @@ if ( hasExperimentalModulesFlag ) { plugins: [ new webpack.DefinePlugin( { // Inject the `SCRIPT_DEBUG` global, used for development features flagging. - SCRIPT_DEBUG: ! isProduction, + 'globalThis.SCRIPT_DEBUG': JSON.stringify( ! isProduction ), + SCRIPT_DEBUG: JSON.stringify( ! isProduction ), } ), // The WP_BUNDLE_ANALYZER global variable enables a utility that represents // bundle content as a convenient interactive zoomable treemap. diff --git a/packages/warning/CHANGELOG.md b/packages/warning/CHANGELOG.md index 90c1ca7e83f6bd..6eee478e47dbfd 100644 --- a/packages/warning/CHANGELOG.md +++ b/packages/warning/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Breaking Changes + +- Variables like `process.env.IS_GUTENBERG_PLUGIN` have been replaced by `globalThis.IS_GUTENBERG_PLUGIN`. Build systems using `process.env` should be updated ([#61486](https://github.com/WordPress/gutenberg/pull/61486)). + ## 2.58.0 (2024-05-16) ## 2.57.0 (2024-05-02) diff --git a/packages/warning/babel-plugin.js b/packages/warning/babel-plugin.js index 02c466b5de24ec..3cf32d311bd6cd 100644 --- a/packages/warning/babel-plugin.js +++ b/packages/warning/babel-plugin.js @@ -14,24 +14,15 @@ const pkg = require( './package.json' ); function babelPlugin( { types: t } ) { const seen = Symbol(); - const typeofProcessExpression = t.binaryExpression( - '!==', - t.unaryExpression( 'typeof', t.identifier( 'SCRIPT_DEBUG' ), false ), - t.stringLiteral( 'undefined' ) - ); - const scriptDebugCheckExpression = t.binaryExpression( '===', - t.identifier( 'SCRIPT_DEBUG' ), + t.memberExpression( + t.identifier( 'globalThis' ), + t.identifier( 'SCRIPT_DEBUG' ) + ), t.booleanLiteral( true ) ); - const logicalExpression = t.logicalExpression( - '&&', - typeofProcessExpression, - scriptDebugCheckExpression - ); - return { visitor: { ImportDeclaration( path, state ) { @@ -70,7 +61,7 @@ function babelPlugin( { types: t } ) { node[ seen ] = true; path.replaceWith( t.ifStatement( - logicalExpression, + scriptDebugCheckExpression, t.blockStatement( [ t.expressionStatement( node ), ] ) diff --git a/packages/warning/src/index.js b/packages/warning/src/index.js index 89ce71db112a21..5744e99d94fd27 100644 --- a/packages/warning/src/index.js +++ b/packages/warning/src/index.js @@ -4,7 +4,8 @@ import { logged } from './utils'; function isDev() { - return typeof SCRIPT_DEBUG !== 'undefined' && SCRIPT_DEBUG === true; + // eslint-disable-next-line @wordpress/wp-global-usage + return globalThis.SCRIPT_DEBUG === true; } /** diff --git a/packages/warning/src/test/index.js b/packages/warning/src/test/index.js index a32e5f1e0fae4e..5fdf67b05360c6 100644 --- a/packages/warning/src/test/index.js +++ b/packages/warning/src/test/index.js @@ -4,28 +4,31 @@ import warning from '..'; import { logged } from '../utils'; +// eslint-disable-next-line eslint-comments/disable-enable-pair +/* eslint-disable @wordpress/wp-global-usage */ + describe( 'warning', () => { - const initialScriptDebug = global.SCRIPT_DEBUG; + const initialScriptDebug = globalThis.SCRIPT_DEBUG; afterEach( () => { - global.SCRIPT_DEBUG = initialScriptDebug; + globalThis.SCRIPT_DEBUG = initialScriptDebug; logged.clear(); } ); it( 'logs to console.warn when SCRIPT_DEBUG is set to `true`', () => { - global.SCRIPT_DEBUG = true; + globalThis.SCRIPT_DEBUG = true; warning( 'warning' ); expect( console ).toHaveWarnedWith( 'warning' ); } ); it( 'does not log to console.warn if SCRIPT_DEBUG not set to `true`', () => { - global.SCRIPT_DEBUG = false; + globalThis.SCRIPT_DEBUG = false; warning( 'warning' ); expect( console ).not.toHaveWarned(); } ); it( 'should show a message once', () => { - global.SCRIPT_DEBUG = true; + globalThis.SCRIPT_DEBUG = true; warning( 'warning' ); warning( 'warning' ); diff --git a/packages/warning/test/babel-plugin.js b/packages/warning/test/babel-plugin.js index a3c4bd55745efd..c845d79d700caa 100644 --- a/packages/warning/test/babel-plugin.js +++ b/packages/warning/test/babel-plugin.js @@ -28,7 +28,7 @@ describe( 'babel-plugin', () => { ); const expected = join( 'import warning from "@wordpress/warning";', - 'typeof SCRIPT_DEBUG !== "undefined" && SCRIPT_DEBUG === true ? warning("a") : void 0;' + 'globalThis.SCRIPT_DEBUG === true ? warning("a") : void 0;' ); expect( transformCode( input ) ).toEqual( expected ); @@ -45,7 +45,7 @@ describe( 'babel-plugin', () => { const input = 'warning("a");'; const options = { callee: 'warning' }; const expected = - 'typeof SCRIPT_DEBUG !== "undefined" && SCRIPT_DEBUG === true ? warning("a") : void 0;'; + 'globalThis.SCRIPT_DEBUG === true ? warning("a") : void 0;'; expect( transformCode( input, options ) ).toEqual( expected ); } ); @@ -59,9 +59,9 @@ describe( 'babel-plugin', () => { ); const expected = join( 'import warning from "@wordpress/warning";', - 'typeof SCRIPT_DEBUG !== "undefined" && SCRIPT_DEBUG === true ? warning("a") : void 0;', - 'typeof SCRIPT_DEBUG !== "undefined" && SCRIPT_DEBUG === true ? warning("b") : void 0;', - 'typeof SCRIPT_DEBUG !== "undefined" && SCRIPT_DEBUG === true ? warning("c") : void 0;' + 'globalThis.SCRIPT_DEBUG === true ? warning("a") : void 0;', + 'globalThis.SCRIPT_DEBUG === true ? warning("b") : void 0;', + 'globalThis.SCRIPT_DEBUG === true ? warning("c") : void 0;' ); expect( transformCode( input ) ).toEqual( expected ); @@ -76,9 +76,9 @@ describe( 'babel-plugin', () => { ); const expected = join( 'import warn from "@wordpress/warning";', - 'typeof SCRIPT_DEBUG !== "undefined" && SCRIPT_DEBUG === true ? warn("a") : void 0;', - 'typeof SCRIPT_DEBUG !== "undefined" && SCRIPT_DEBUG === true ? warn("b") : void 0;', - 'typeof SCRIPT_DEBUG !== "undefined" && SCRIPT_DEBUG === true ? warn("c") : void 0;' + 'globalThis.SCRIPT_DEBUG === true ? warn("a") : void 0;', + 'globalThis.SCRIPT_DEBUG === true ? warn("b") : void 0;', + 'globalThis.SCRIPT_DEBUG === true ? warn("c") : void 0;' ); expect( transformCode( input ) ).toEqual( expected ); @@ -93,9 +93,9 @@ describe( 'babel-plugin', () => { ); const expected = join( 'import warn from "@wordpress/warning";', - 'typeof SCRIPT_DEBUG !== "undefined" && SCRIPT_DEBUG === true ? warn("a") : void 0;', - 'typeof SCRIPT_DEBUG !== "undefined" && SCRIPT_DEBUG === true ? warn("b") : void 0;', - 'typeof SCRIPT_DEBUG !== "undefined" && SCRIPT_DEBUG === true ? warn("c") : void 0;' + 'globalThis.SCRIPT_DEBUG === true ? warn("a") : void 0;', + 'globalThis.SCRIPT_DEBUG === true ? warn("b") : void 0;', + 'globalThis.SCRIPT_DEBUG === true ? warn("c") : void 0;' ); expect( transformCode( input ) ).toEqual( expected ); diff --git a/test/integration/full-content/full-content.test.js b/test/integration/full-content/full-content.test.js index f825de04771442..4522dd7b88e451 100644 --- a/test/integration/full-content/full-content.test.js +++ b/test/integration/full-content/full-content.test.js @@ -82,7 +82,7 @@ describe( 'full post content fixture', () => { formSubmissionNotification, ] ); - if ( process.env.IS_GUTENBERG_PLUGIN ) { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { __experimentalRegisterExperimentalCoreBlocks( { enableFSEBlocks: true, } ); diff --git a/test/unit/config/gutenberg-env.js b/test/unit/config/gutenberg-env.js index a6a1b81f740970..f6ea2d16c1c79d 100644 --- a/test/unit/config/gutenberg-env.js +++ b/test/unit/config/gutenberg-env.js @@ -1,32 +1,13 @@ -global.process.env = { - ...global.process.env, - /* - Inject the `IS_GUTENBERG_PLUGIN` global, used for feature flagging. - - The conversion to boolean is required here. Why? Package.json defines - IS_GUTENBERG_PLUGIN as follows: - - "config": { - "IS_GUTENBERG_PLUGIN": true - } - - Webpack then replaces references to IS_GUTENBERG_PLUGIN with a literal value `true`. - The file you are reading right now, however, receives a string value "true": - - "true" === process.env.npm_package_config_IS_GUTENBERG_PLUGIN - - The code can only work consistently in both environments when the value of - IS_GUTENBERG_PLUGIN is consistent. For this reason, the line below turns the - string representation of IS_GUTENBERG_PLUGIN into a boolean value. - */ - // eslint-disable-next-line @wordpress/is-gutenberg-plugin - IS_GUTENBERG_PLUGIN: - String( process.env.npm_package_config_IS_GUTENBERG_PLUGIN ) === 'true', - /** - * Feature flag guarding features specific to WordPress core. - * It's important to set it to "true" in the test environment - * to ensure the Gutenberg plugin can be cleanly merged into - * WordPress core. - */ - IS_WORDPRESS_CORE: true, -}; +/* + * Feature flag guarding features specific to WordPress core. + * It's important to set it to "true" in the test environment + * to ensure the Gutenberg plugin can be cleanly merged into + * WordPress core. + */ +// eslint-disable-next-line @wordpress/wp-global-usage +globalThis.IS_WORDPRESS_CORE = true; + +// Inject the `IS_GUTENBERG_PLUGIN` global, used for feature flagging. +// eslint-disable-next-line @wordpress/wp-global-usage +globalThis.IS_GUTENBERG_PLUGIN = + String( process.env.npm_package_config_IS_GUTENBERG_PLUGIN ) === 'true'; diff --git a/tools/webpack/shared.js b/tools/webpack/shared.js index debd3fc93f6f6d..f30d3a830f3eb1 100644 --- a/tools/webpack/shared.js +++ b/tools/webpack/shared.js @@ -64,13 +64,15 @@ const plugins = [ process.env.WP_BUNDLE_ANALYZER && new BundleAnalyzerPlugin(), new DefinePlugin( { // Inject the `IS_GUTENBERG_PLUGIN` global, used for feature flagging. - 'process.env.IS_GUTENBERG_PLUGIN': - process.env.npm_package_config_IS_GUTENBERG_PLUGIN, + 'globalThis.IS_GUTENBERG_PLUGIN': JSON.stringify( + Boolean( process.env.npm_package_config_IS_GUTENBERG_PLUGIN ) + ), // Inject the `IS_WORDPRESS_CORE` global, used for feature flagging. - 'process.env.IS_WORDPRESS_CORE': - process.env.npm_package_config_IS_WORDPRESS_CORE, + 'globalThis.IS_WORDPRESS_CORE': JSON.stringify( + Boolean( process.env.npm_package_config_IS_WORDPRESS_CORE ) + ), // Inject the `SCRIPT_DEBUG` global, used for dev versions of JavaScript. - SCRIPT_DEBUG: mode === 'development', + 'globalThis.SCRIPT_DEBUG': JSON.stringify( mode === 'development' ), } ), mode === 'production' && new ReadableJsAssetsWebpackPlugin(), ]; diff --git a/typings/gutenberg-env/index.d.ts b/typings/gutenberg-env/index.d.ts index ecf60a7ca094f9..c2bc71ae435ee2 100644 --- a/typings/gutenberg-env/index.d.ts +++ b/typings/gutenberg-env/index.d.ts @@ -1,11 +1,25 @@ -interface Environment { - NODE_ENV: unknown; - IS_GUTENBERG_PLUGIN?: boolean; - IS_WORDPRESS_CORE?: boolean; +declare namespace NodeJS { + interface ProcessEnv { + readonly NODE_ENV?: 'production' | 'development' | 'test'; + } + interface Process { + env: NodeJS.ProcessEnv; + } } -interface Process { - env: Environment; -} -declare var process: Process; -declare var SCRIPT_DEBUG: boolean; +declare var process: NodeJS.Process; + +/** + * Whether the code is running in WordPress with SCRIPT_DEBUG flag. + */ +declare var SCRIPT_DEBUG: undefined | boolean; + +/** + * Whether code is running within the Gutenberg plugin. + * + * When the codebase is built for the plugin, this variable will be set to `true`. + * When building for WordPress Core, it will be set to `false` or `undefined`. + */ +declare var IS_GUTENBERG_PLUGIN: undefined | boolean; + +declare var IS_WORDPRESS_CORE: undefined | boolean;