Commit ae00acca5f4 for woocommerce
commit ae00acca5f40ca499f6def4fd60b017b3b6a6d21
Author: Luigi Teschio <gigitux@gmail.com>
Date: Tue Jul 28 10:21:34 2026 +0200
Consolidate Woo blocks editor assets into shared bundles (#66200)
* Consolidate Woo blocks editor assets
* restructure code
* add documentation
* improve naming
* lint code
* improve performance test
* fix test
* fix build configuration
* re-run test
* fix css
* fix build
* lint code
* relocate in the right folder
* fix build
* fix webpack
* fix crash build
* avoid restructuring
* WIP
* fix c&c blocks
* add side effect
* fix import
* fix block.json
* clean up changes
* clean up changes
* clean up code
* fix baseline
* externalize packages
* fix warning
* refresh pnpm-lock
* improve bundle
* restore pnpm-lock
* improve logic
* update docs
* restore the previous name for the CSS bundle
* fix lint
* avoid redunant get_script_data
* address feedback
* fix path for is_built function
* update changelog
* use str_starts_with
* add deprecation warning
* update comment
* optimize bundle
* Restore Blocks shared package bundles
* deprecate less packages possible
* implement feature flag
* clean up code
* improve CI
* update webpack configuration
* clean up code
* lint code
* restore changes
* restore changes
* improve conf
* clean up changes
* simplify webpack conf
* clean up code
* improve documentation
* remove old changelog
* fix css bundle
* improve optimization
* update readme
* remove declare type
* improve logic
* update documentation
* fix deps
diff --git a/plugins/woocommerce/changelog/performance-unify-blocks-editor-assets b/plugins/woocommerce/changelog/performance-unify-blocks-editor-assets
new file mode 100644
index 00000000000..5d58f84022d
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-unify-blocks-editor-assets
@@ -0,0 +1,4 @@
+Significance: major
+Type: performance
+
+Unify Woo blocks editor scripts and editor styles into shared editor assets.
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/cart/test/use-store-cart-coupons.tsx b/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/cart/test/use-store-cart-coupons.tsx
index 5ec82c8e90d..3fbbc471ecc 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/cart/test/use-store-cart-coupons.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/cart/test/use-store-cart-coupons.tsx
@@ -10,9 +10,9 @@ import { server, http, HttpResponse } from '@woocommerce/test-utils/msw';
import { useStoreCartCoupons } from '../use-store-cart-coupons';
// Mock the resolvers to avoid actual API calls on cart data store setup.
-jest.mock( '../../../../../data/cart/resolvers', () => {
+jest.mock( '@woocommerce/block-data/cart/resolvers', () => {
return {
- ...jest.requireActual( '../../../../../data/cart/resolvers' ),
+ ...jest.requireActual( '@woocommerce/block-data/cart/resolvers' ),
getCartData: jest
.fn()
.mockResolvedValue(
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/cart/test/use-store-cart-item-quantity.js b/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/cart/test/use-store-cart-item-quantity.js
index 89b4e7c109d..84df7c4da17 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/cart/test/use-store-cart-item-quantity.js
+++ b/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/cart/test/use-store-cart-item-quantity.js
@@ -16,10 +16,20 @@ jest.mock( '../use-store-cart', () => ( {
useStoreCart: jest.fn(),
} ) );
-jest.mock( '@woocommerce/block-data', () => ( {
- __esModule: true,
- ...jest.requireActual( '@woocommerce/block-data' ),
-} ) );
+jest.mock( '@woocommerce/block-data', () => {
+ const cart = jest.requireActual( '@woocommerce/block-data/cart' );
+ const checkout = jest.requireActual( '@woocommerce/block-data/checkout' );
+ const utils = jest.requireActual( '@woocommerce/block-data/utils' );
+
+ return {
+ __esModule: true,
+ CART_STORE_KEY: cart.CART_STORE_KEY,
+ CHECKOUT_STORE_KEY: checkout.CHECKOUT_STORE_KEY,
+ cartStore: cart.store,
+ checkoutStore: checkout.store,
+ processErrorResponse: utils.processErrorResponse,
+ };
+} );
// Make debounce instantaneous.
jest.mock( 'use-debounce', () => ( {
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/cart/test/use-store-cart.jsx b/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/cart/test/use-store-cart.jsx
index 778b04fde5d..ea9f7d0a58c 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/cart/test/use-store-cart.jsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/cart/test/use-store-cart.jsx
@@ -15,10 +15,17 @@ jest.mock( '../../../providers/editor-context', () => ( {
useEditorContext: jest.fn(),
} ) );
-jest.mock( '@woocommerce/block-data', () => ( {
- ...jest.requireActual( '@woocommerce/block-data' ),
- __esModule: true,
-} ) );
+jest.mock( '@woocommerce/block-data', () => {
+ const constants = jest.requireActual( '@woocommerce/block-data/constants' );
+ const cart = jest.requireActual( '@woocommerce/block-data/cart' );
+
+ return {
+ __esModule: true,
+ ...constants,
+ CART_STORE_KEY: cart.CART_STORE_KEY,
+ cartStore: cart.store,
+ };
+} );
describe( 'useStoreCart', () => {
let registry;
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/tsconfig.json b/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/tsconfig.json
index c5b9d46d020..363ec666e1e 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/tsconfig.json
+++ b/plugins/woocommerce/client/blocks/assets/js/base/context/hooks/tsconfig.json
@@ -11,7 +11,7 @@
"../providers/cart-checkout/shipping/index.js",
"../../components/cart-checkout/totals/shipping",
"../../../editor-components/utils/*",
- "../../../data/index.ts"
+ "../../../data/**/*"
],
"exclude": [ "**/test/**" ]
}
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment-methods.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment-methods.tsx
index fc70ecccec6..750676fa111 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment-methods.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment-methods.tsx
@@ -21,14 +21,14 @@ import {
ConfigOf,
CurriedSelectorsOf,
} from '@wordpress/data/build-types/types';
+import { paymentStore } from '@woocommerce/block-data';
/**
* Internal dependencies
*/
+import type { PaymentStoreDescriptor } from '@woocommerce/block-data/payment';
import PaymentMethodErrorBoundary from './payment-method-error-boundary';
-import { STORE_KEY as PAYMENT_STORE_KEY } from '../../../data/payment/constants';
import { useExpressPaymentContext } from '../../cart-checkout-shared/payment-methods/express-payment/express-payment-context';
-import type { PaymentStoreDescriptor } from '../../../data/payment';
import { useExpressPaymentFocus } from './use-express-payment-focus';
const ExpressPaymentMethods = () => {
@@ -48,7 +48,7 @@ const ExpressPaymentMethods = () => {
const { activePaymentMethod, paymentMethodData } = useSelect(
( select ) => {
const store = select(
- PAYMENT_STORE_KEY
+ paymentStore
) as CurriedSelectorsOf< PaymentStoreDescriptor >;
return {
activePaymentMethod: store.getActivePaymentMethod(),
@@ -63,7 +63,7 @@ const ExpressPaymentMethods = () => {
__internalSetPaymentError,
__internalSetPaymentMethodData,
__internalSetExpressPaymentError,
- } = useDispatch( PAYMENT_STORE_KEY ) as ActionCreatorsOf<
+ } = useDispatch( paymentStore ) as ActionCreatorsOf<
ConfigOf< PaymentStoreDescriptor >
>;
const { paymentMethods } = useExpressPaymentMethods();
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/saved-payment-method-options.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/saved-payment-method-options.tsx
index 972f26cdf5d..c92833c462b 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/saved-payment-method-options.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/cart-checkout-shared/payment-methods/saved-payment-method-options.tsx
@@ -20,8 +20,8 @@ import { isNull } from '@woocommerce/types';
/**
* Internal dependencies
*/
-import { getCanMakePaymentArg } from '../../../data/payment/utils/check-payment-methods';
-import { CustomerPaymentMethodConfiguration } from '../../../data/payment/types';
+import { getCanMakePaymentArg } from '@woocommerce/block-data/payment/utils/check-payment-methods';
+import { CustomerPaymentMethodConfiguration } from '@woocommerce/block-data/payment/types';
/**
* Returns the option object for a cc or echeck saved payment method token.
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/test/block.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/test/block.ts
index 260fa0df8e9..4a1df0c205a 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/test/block.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/test/block.ts
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import { type BlockAttributes } from '@wordpress/blocks';
+import { BlockAttributes } from '@wordpress/blocks';
import '@testing-library/jest-dom';
import { act, fireEvent, screen, within } from '@testing-library/react';
@@ -50,7 +50,7 @@ jest.mock( '@woocommerce/settings', () => {
} );
// Mock WooCommerce schema selectors to prevent namespace errors
-jest.mock( '../../../../../data/schema/selectors', () => ( {
+jest.mock( '@woocommerce/block-data/schema/selectors', () => ( {
getRoute: jest.fn( () => null ),
getRoutes: jest.fn( () => ( {
'/wc/store/v1': {},
diff --git a/plugins/woocommerce/client/blocks/assets/js/data/collections/resolvers.js b/plugins/woocommerce/client/blocks/assets/js/data/collections/resolvers.js
index 823e83635c2..df7b96c4876 100644
--- a/plugins/woocommerce/client/blocks/assets/js/data/collections/resolvers.js
+++ b/plugins/woocommerce/client/blocks/assets/js/data/collections/resolvers.js
@@ -8,7 +8,7 @@ import { addQueryArgs } from '@wordpress/url';
* Internal dependencies
*/
import { receiveCollection, receiveCollectionError } from './actions';
-import { STORE_KEY as SCHEMA_STORE_KEY } from '../schema/constants';
+import { SCHEMA_STORE_KEY } from '../schema';
import { STORE_KEY, DEFAULT_EMPTY_ARRAY } from './constants';
import { apiFetchWithHeadersControl } from '../shared-controls';
diff --git a/plugins/woocommerce/client/blocks/assets/js/dependency-detection/utils.ts b/plugins/woocommerce/client/blocks/assets/js/dependency-detection/utils.ts
index 5d529e963f7..8875ca7bc32 100644
--- a/plugins/woocommerce/client/blocks/assets/js/dependency-detection/utils.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/dependency-detection/utils.ts
@@ -28,6 +28,7 @@ export interface WcGlobalExportsMap {
blocksComponents: 'wc-blocks-components';
wcTypes: 'wc-types';
sanitize: 'wc-sanitize';
+ wcEntities: 'wc-entities';
}
/**
diff --git a/plugins/woocommerce/client/blocks/bin/webpack-config-block-editor-unified-assets.js b/plugins/woocommerce/client/blocks/bin/webpack-config-block-editor-unified-assets.js
new file mode 100644
index 00000000000..1a5edb9cdba
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/bin/webpack-config-block-editor-unified-assets.js
@@ -0,0 +1,342 @@
+/**
+ * External dependencies
+ */
+const path = require( 'path' );
+const { omit } = require( 'lodash' );
+const cssnano = require( 'cssnano' );
+const postcss = require( 'postcss' );
+const ProgressBarPlugin = require( 'progress-bar-webpack-plugin' );
+const webpack = require( 'webpack' );
+
+/**
+ * Internal dependencies
+ */
+const { getEntryConfig } = require( './webpack-entries' );
+const {
+ editorStyleEntries,
+ styleEntries,
+} = require( './webpack-interactivity-entries' );
+const {
+ NODE_ENV,
+ getProgressBarPluginConfig,
+ getResolve,
+ requestToExternal,
+ requestToHandle,
+} = require( './webpack-helpers' );
+const { getSharedPlugins, getStylingConfig } = require( './webpack-configs' );
+const { sharedOptimizationConfig } = require( './webpack-shared-config' );
+
+const ROOT_DIR = path.resolve( __dirname, '../../../../../' );
+// Blocks' webpack writes directly to the WooCommerce plugin's
+// `assets/client/blocks/` so PHP can enqueue files from their final location
+// without an intermediate rsync step.
+const BUILD_DIR = path.resolve( __dirname, '../../../assets/client/blocks' );
+const BABEL_CACHE_DIR = path.join(
+ ROOT_DIR,
+ 'node_modules/.cache/babel-loader'
+);
+const isProduction = NODE_ENV === 'production';
+const UNIFIED_EDITOR_STYLE_HANDLE = 'wc-block-library-style';
+const UNIFIED_EDITOR_STYLE_PATTERN = /^wc-block-library-style(?:-rtl)?\.css$/;
+const OPTIMIZE_UNIFIED_EDITOR_STYLES_PLUGIN =
+ 'OptimizeUnifiedEditorStylesPlugin';
+
+const editorExternalPackages = [
+ '@woocommerce/block-data',
+ '@woocommerce/blocks-checkout',
+ '@woocommerce/blocks-checkout-events',
+ '@woocommerce/blocks-components',
+ '@woocommerce/blocks-registry',
+ '@woocommerce/data',
+ '@woocommerce/entities',
+ '@woocommerce/price-format',
+ '@woocommerce/sanitize',
+ '@woocommerce/settings',
+ '@woocommerce/shared-context',
+ '@woocommerce/shared-hocs',
+ '@woocommerce/types',
+];
+
+const shouldBundleWooPackage = ( request ) =>
+ request.startsWith( '@woocommerce/' ) &&
+ ! editorExternalPackages.includes( request );
+
+const requestToUnifiedEditorExternal = ( request ) => {
+ if ( shouldBundleWooPackage( request ) ) {
+ return false;
+ }
+
+ return requestToExternal( request );
+};
+
+const requestToUnifiedEditorHandle = ( request ) => {
+ if ( shouldBundleWooPackage( request ) ) {
+ return false;
+ }
+
+ return requestToHandle( request );
+};
+
+const getUnifiedEditorPackageAliases = () => ( {
+ '@woocommerce/block-data': path.resolve( __dirname, `../assets/js/data` ),
+ '@woocommerce/blocks-checkout': path.resolve(
+ __dirname,
+ `../packages/checkout`
+ ),
+ '@woocommerce/blocks-checkout-events': path.resolve(
+ __dirname,
+ `../assets/js/events`
+ ),
+ '@woocommerce/blocks-components': path.resolve(
+ __dirname,
+ `../packages/components`
+ ),
+ '@woocommerce/blocks-registry': path.resolve(
+ __dirname,
+ `../assets/js/blocks-registry`
+ ),
+ '@woocommerce/data': path.resolve(
+ __dirname,
+ `../../../../../packages/js/data/src/index.ts`
+ ),
+ '@woocommerce/price-format': path.resolve(
+ __dirname,
+ `../packages/prices`
+ ),
+ '@woocommerce/sanitize': path.resolve(
+ __dirname,
+ `../../../../../packages/js/sanitize/src/index.ts`
+ ),
+ '@woocommerce/settings': path.resolve(
+ __dirname,
+ `../assets/js/settings/shared`
+ ),
+ '@woocommerce/shared-context': path.resolve(
+ __dirname,
+ `../assets/js/shared/context/`
+ ),
+ '@woocommerce/shared-hocs': path.resolve(
+ __dirname,
+ `../assets/js/shared/hocs/`
+ ),
+} );
+
+/**
+ * Optimize the combined editor styles after CSS extraction and RTL generation.
+ *
+ * The inherited styling configuration runs PostCSS for every Sass entry before
+ * MiniCssExtractPlugin combines those entries. When multiple entries import the
+ * same shared styles, each entry is minified independently and the duplicate
+ * rules remain in the final combined stylesheet.
+ *
+ * This plugin runs cssnano once more at Webpack's optimize-size stage, after
+ * WebpackRTLPlugin has emitted its derived RTL asset. Processing at that point
+ * allows cssnano to remove duplicates across the complete LTR and RTL bundles
+ * rather than only within individual Sass entries.
+ *
+ * Only the unified editor stylesheet filenames are processed so legacy block
+ * styles and other build outputs remain unchanged. The plugin is added to the
+ * production configuration only; development builds avoid the additional work
+ * to preserve fast rebuilds and their existing source output.
+ */
+class OptimizeUnifiedEditorStylesPlugin {
+ /**
+ * Apply the plugin.
+ *
+ * @param {webpack.Compiler} compiler Webpack compiler.
+ */
+ apply( compiler ) {
+ compiler.hooks.thisCompilation.tap(
+ OPTIMIZE_UNIFIED_EDITOR_STYLES_PLUGIN,
+ ( compilation ) => {
+ compilation.hooks.processAssets.tapPromise(
+ {
+ name: OPTIMIZE_UNIFIED_EDITOR_STYLES_PLUGIN,
+ stage: webpack.Compilation
+ .PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE,
+ },
+ async ( assets ) => {
+ await Promise.all(
+ Object.entries( assets )
+ .filter( ( [ assetName ] ) =>
+ UNIFIED_EDITOR_STYLE_PATTERN.test(
+ assetName
+ )
+ )
+ .map( async ( [ assetName, asset ] ) => {
+ const result = await postcss( [
+ cssnano,
+ ] ).process( asset.source().toString(), {
+ from: undefined,
+ map: false,
+ } );
+
+ compilation.updateAsset(
+ assetName,
+ new webpack.sources.RawSource(
+ result.css
+ )
+ );
+ } )
+ );
+ }
+ );
+ }
+ );
+ }
+}
+
+/**
+ * Reuse the established styling entry graph so the unified bundle includes
+ * transitive and nonstandard SCSS imports as the legacy per-block build does.
+ *
+ * @param {string[]} exclude Entry names to exclude.
+ * @return {Object} Unified styling entries.
+ */
+const getUnifiedEditorStyleEntries = ( exclude = [] ) =>
+ omit(
+ {
+ 'wc-block-library-style-source': [
+ ...Object.values( getEntryConfig( 'styling', exclude ) ).flat(),
+ // Interactivity styles are emitted by a separate frontend build,
+ // so they are excluded from the standard styling entry graph.
+ ...Object.values( styleEntries ).flat(),
+ ...Object.values( editorStyleEntries ).flat(),
+ ],
+ },
+ exclude
+ );
+
+/**
+ * Build config for unified Blocks editor scripts.
+ *
+ * @param {Object} options Build options.
+ */
+const getUnifiedMainConfig = ( options = {} ) => {
+ const { alias, resolvePlugins = [] } = options;
+ const resolve = getResolve( {
+ alias: {
+ ...getUnifiedEditorPackageAliases(),
+ ...alias,
+ },
+ resolvePlugins,
+ } );
+
+ return {
+ entry: omit(
+ {
+ 'wc-block-library': Object.values(
+ getEntryConfig( 'main' )
+ ).flat(),
+ },
+ options.exclude || []
+ ),
+ output: {
+ devtoolNamespace: 'wc',
+ path: BUILD_DIR,
+ // Keep the filename stable for WordPress translations while using
+ // the query string to invalidate cached chunks after a build.
+ chunkFilename: `wc-block-library-[name].js?ver=[contenthash]`,
+ filename: `[name].js`,
+ uniqueName: 'webpackWcBlocksUnifiedMainJsonp',
+ },
+ module: {
+ rules: [
+ {
+ test: /\.(j|t)sx?$/,
+ exclude: [ /[\/\\](node_modules|build|docs|vendor)[\/\\]/ ],
+ use: {
+ loader: 'babel-loader',
+ options: {
+ presets: [ '@wordpress/babel-preset-default' ],
+ plugins: [
+ isProduction
+ ? require.resolve(
+ 'babel-plugin-transform-react-remove-prop-types'
+ )
+ : false,
+ ].filter( Boolean ),
+ cacheDirectory: BABEL_CACHE_DIR,
+ cacheCompression: false,
+ },
+ },
+ },
+ {
+ test: /\.s[c|a]ss$/,
+ use: {
+ loader: 'ignore-loader',
+ },
+ },
+ ],
+ },
+ optimization: {
+ ...sharedOptimizationConfig,
+ splitChunks: false,
+ },
+ plugins: [
+ ...getSharedPlugins( {
+ bundleAnalyzerReportTitle: 'Unified editor',
+ dependencyRequestToExternal: requestToUnifiedEditorExternal,
+ dependencyRequestToHandle: requestToUnifiedEditorHandle,
+ } ),
+ new ProgressBarPlugin(
+ getProgressBarPluginConfig( 'Unified editor' )
+ ),
+ new webpack.optimize.LimitChunkCountPlugin( {
+ maxChunks: 1,
+ } ),
+ ],
+ resolve: {
+ ...resolve,
+ extensions: [ '.js', '.jsx', '.ts', '.tsx' ],
+ },
+ };
+};
+
+/**
+ * Build config for unified Blocks editor styles.
+ *
+ * @param {Object} options Build options.
+ */
+const getUnifiedStylingConfig = ( options = {} ) => {
+ const stylingConfig = getStylingConfig( options );
+
+ return {
+ ...stylingConfig,
+ entry: getUnifiedEditorStyleEntries( options.exclude || [] ),
+ output: {
+ ...stylingConfig.output,
+ uniqueName: 'webpackWcBlocksUnifiedStylingJsonp',
+ },
+ optimization: {
+ ...stylingConfig.optimization,
+ splitChunks: {
+ ...stylingConfig.optimization.splitChunks,
+ cacheGroups: {
+ // JavaScript is traversed only to discover stylesheet imports.
+ // Do not emit the inherited JavaScript split chunks.
+ default: false,
+ defaultVendors: false,
+ editorStyle: {
+ test: ( module = {} ) => {
+ return module.type.includes( 'css' );
+ },
+ name: UNIFIED_EDITOR_STYLE_HANDLE,
+ chunks: 'all',
+ enforce: true,
+ priority: 10,
+ },
+ },
+ },
+ },
+ plugins: [
+ ...stylingConfig.plugins,
+ isProduction && new OptimizeUnifiedEditorStylesPlugin(),
+ ].filter( Boolean ),
+ };
+};
+
+module.exports = {
+ getUnifiedMainConfig,
+ getUnifiedStylingConfig,
+};
diff --git a/plugins/woocommerce/client/blocks/bin/webpack-configs.js b/plugins/woocommerce/client/blocks/bin/webpack-configs.js
index 527fedcf83b..3622b78085f 100644
--- a/plugins/woocommerce/client/blocks/bin/webpack-configs.js
+++ b/plugins/woocommerce/client/blocks/bin/webpack-configs.js
@@ -52,6 +52,8 @@ let initialBundleAnalyzerPort = 8888;
const getSharedPlugins = ( {
bundleAnalyzerReportTitle,
checkCircularDeps = true,
+ dependencyRequestToExternal = requestToExternal,
+ dependencyRequestToHandle = requestToHandle,
} ) =>
[
CHECK_CIRCULAR_DEPS === 'true' && checkCircularDeps !== false
@@ -72,8 +74,8 @@ const getSharedPlugins = ( {
injectPolyfill: true,
combineAssets: ASSET_CHECK,
outputFormat: ASSET_CHECK ? 'json' : 'php',
- requestToExternal,
- requestToHandle,
+ requestToExternal: dependencyRequestToExternal,
+ requestToHandle: dependencyRequestToHandle,
} ),
// Substitute the `__i18n_text_domain__` identifier used by the
// @woocommerce/email-editor package with the WooCommerce text
@@ -884,6 +886,7 @@ const getCartAndCheckoutFrontendConfig = ( options = {} ) => {
};
module.exports = {
+ getSharedPlugins,
getCoreConfig,
getFrontConfig,
getMainConfig,
diff --git a/plugins/woocommerce/client/blocks/bin/webpack-helpers.js b/plugins/woocommerce/client/blocks/bin/webpack-helpers.js
index 41aa08d30d9..6d3958d5490 100644
--- a/plugins/woocommerce/client/blocks/bin/webpack-helpers.js
+++ b/plugins/woocommerce/client/blocks/bin/webpack-helpers.js
@@ -8,9 +8,12 @@ const NODE_ENV = process.env.NODE_ENV || 'development';
const CHECK_CIRCULAR_DEPS = process.env.CHECK_CIRCULAR_DEPS || false;
const ASSET_CHECK = process.env.ASSET_CHECK === 'true';
-// See also @woocommerce/dependency-extraction-webpack-plugin/assets/packages. It will backfill any missing
-// mapping here and any duplicates are because of switched between Woo and WordPress versions of the plugin.
-// As of 2026 it's Woo version to address pnpm peer dependencies related issues to support filesystem cache.
+// See also @woocommerce/dependency-extraction-webpack-plugin/assets/packages and
+// docs/internal-developers/enqueueable-packages/README.md. They should stay in sync with this map.
+// The dependency extraction plugin will backfill any missing mapping here. Duplicates exist because
+// this file has switched between Woo and WordPress versions of the plugin.
+// As of 2026, it uses the Woo version to address pnpm peer dependency issues and support
+// filesystem cache.
const wcDepMap = {
'@woocommerce/tracks': false, // Bundle; do not externalize
'@woocommerce/blocks-registry': [ 'wc', 'wcBlocksRegistry' ],
@@ -25,6 +28,7 @@ const wcDepMap = {
'@woocommerce/blocks-components': [ 'wc', 'blocksComponents' ],
'@woocommerce/types': [ 'wc', 'wcTypes' ],
'@woocommerce/sanitize': [ 'wc', 'sanitize' ],
+ '@woocommerce/entities': [ 'wc', 'wcEntities' ],
};
const wcHandleMap = {
'@woocommerce/tracks': false, // Bundle; no PHP handle needed
@@ -40,6 +44,7 @@ const wcHandleMap = {
'@woocommerce/blocks-components': 'wc-blocks-components',
'@woocommerce/types': 'wc-types',
'@woocommerce/sanitize': 'wc-sanitize',
+ '@woocommerce/entities': 'wc-entities',
};
const getAlias = ( options = {} ) => {
@@ -74,6 +79,14 @@ const getAlias = ( options = {} ) => {
__dirname,
`../assets/js/${ pathPart }base/utils/`
),
+ '@woocommerce/block-data': path.resolve(
+ __dirname,
+ `../assets/js/data`
+ ),
+ '@woocommerce/blocks-components': path.resolve(
+ __dirname,
+ `../packages/components`
+ ),
'@woocommerce/blocks': path.resolve(
__dirname,
`../assets/js/${ pathPart }/blocks`
diff --git a/plugins/woocommerce/client/blocks/docs/internal-developers/block-client-apis/README.md b/plugins/woocommerce/client/blocks/docs/internal-developers/block-client-apis/README.md
index cbcc6763e1a..c5450aabfb5 100644
--- a/plugins/woocommerce/client/blocks/docs/internal-developers/block-client-apis/README.md
+++ b/plugins/woocommerce/client/blocks/docs/internal-developers/block-client-apis/README.md
@@ -2,17 +2,18 @@
This folder contains documentation for API interfaces for Blocks. In _most cases_, these docs describe APIs and interfaces that are _internal_ only, and thus are provided to assist with developing the blocks in this repository. Documentation will tend to focus on high level overviews.
-| Document | Description |
-| -------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
-| [Checkout API interface](checkout/checkout-api.md) | This doc goes into some detail about some of the API specifics for the checkout block architecture. |
-| [Notices](notices.md) | Explains how the notices system works and which methods are available to add an remove them. |
+| Document | Description |
+| --- | --- |
+| [Checkout API interface](checkout/checkout-api.md) | Describes API details for the Checkout block architecture. |
+| [Unified editor assets](../enqueueable-packages/README.md) | Explains the default and experimental editor asset configurations, external package boundaries, and compatibility handles. |
+| [Notices](notices.md) | Explains how the notices system works and which methods can add and remove notices. |
Some of the resources from this directory were migrated to WooCommerce Developer Documentation:
-- [Checkout Flow and Events](https://developer.woocommerce.com/docs/cart-and-checkout-checkout-flow-and-events/)
+- [Checkout Flow and Events](https://developer.woocommerce.com/docs/cart-and-checkout-checkout-flow-and-events/)
For more details about extensibility points in the blocks, you can reference the extensibility docs:
-- [Data Store](../../third-party-developers/extensibility/data-store/README.md)
-- [Hooks](../../third-party-developers/extensibility/hooks/README.md)
-- [Extending the Store API](../../third-party-developers/extensibility/rest-api/README.md)
+- [Data Store](../../third-party-developers/extensibility/data-store/README.md)
+- [Hooks](../../third-party-developers/extensibility/hooks/README.md)
+- [Extending the Store API](../../third-party-developers/extensibility/rest-api/README.md)
diff --git a/plugins/woocommerce/client/blocks/docs/internal-developers/enqueueable-packages/README.md b/plugins/woocommerce/client/blocks/docs/internal-developers/enqueueable-packages/README.md
new file mode 100644
index 00000000000..6efb16d3024
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/docs/internal-developers/enqueueable-packages/README.md
@@ -0,0 +1,60 @@
+# WooCommerce Blocks editor assets
+
+WooCommerce includes an experimental configuration that replaces its per-block editor scripts and styles with shared editor assets. The experiment is disabled by default, so existing per-block assets and handles continue to work unchanged.
+
+Enable **Unified block editor assets** under **WooCommerce > Settings > Advanced > Features** to test the shared configuration. The feature flag is `block_editor_unified_assets`, its setting is stored in `woocommerce_feature_block_editor_unified_assets_enabled`, and changes take effect on the next request.
+
+## Asset configurations
+
+### Default
+
+When the experiment is disabled, WooCommerce uses:
+
+- Per-block scripts such as `wc-cart-block` and `wc-checkout-block`.
+- Per-block styles declared by block metadata.
+- The `wc-blocks-vendors` and `wc-blocks` bundles.
+- The `wc-blocks-editor-style` stylesheet.
+
+### Unified
+
+When the experiment is enabled, WooCommerce block types use:
+
+| Handle | Type | What it contains |
+| --- | --- | --- |
+| `wc-block-library` | Script | WooCommerce block editor entry points and internal package imports. |
+| `wc-block-library-style` | Stylesheet | Editor styles for blocks shipped by WooCommerce. Styles registered by third-party blocks remain separate. |
+
+These assets replace WooCommerce's per-block editor assets. They do not combine every script loaded by the editor: shared WordPress dependencies and the enqueueable WooCommerce packages below remain external.
+
+## External package dependencies
+
+The generated asset file for `wc-block-library` declares packages that remain separate scripts:
+
+| Package import | Script handle | Global | Why it stays external |
+| --- | --- | --- | --- |
+| `@woocommerce/block-data` | `wc-blocks-data-store` | `wc.wcBlocksData` | Registers WooCommerce Blocks data stores once and shares them with editor extensions and checkout APIs. |
+| `@woocommerce/blocks-checkout` | `wc-blocks-checkout` | `wc.blocksCheckout` | Shares checkout filters, slot/fill APIs, and checkout registry helpers with extensions. |
+| `@woocommerce/blocks-checkout-events` | `wc-blocks-checkout-events` | `wc.blocksCheckoutEvents` | Shares the checkout lifecycle event emitter between subscribers and emitters. |
+| `@woocommerce/blocks-components` | `wc-blocks-components` | `wc.blocksComponents` | Shares components used by WooCommerce blocks and extensions. |
+| `@woocommerce/blocks-registry` | `wc-blocks-registry` | `wc.wcBlocksRegistry` | Keeps block, payment method, and product collection registrations in shared registries. |
+| `@woocommerce/data` | `wc-store-data` | `wc.data` | Shares WooCommerce Admin data stores without re-registering them from the editor bundle. |
+| `@woocommerce/entities` | `wc-entities` | `wc.wcEntities` (deprecated) | Registers WooCommerce entities once. The global helper exports remain temporarily for backward compatibility and must not be used as a public extension API. |
+| `@woocommerce/price-format` | `wc-price-format` | `wc.priceFormat` | Shares price and currency formatting across editor, frontend, and extension code. |
+| `@woocommerce/sanitize` | `wc-sanitize` | `wc.sanitize` | Shares HTML sanitization with the Components and Checkout package bundles. |
+| `@woocommerce/settings` | `wc-settings` | `wc.wcSettings` | Shares one runtime settings object across the editor and package bundles. |
+| `@woocommerce/shared-context` | `wc-blocks-shared-context` | `wc.wcBlocksSharedContext` | Shares React contexts across separately built scripts. |
+| `@woocommerce/shared-hocs` | `wc-blocks-shared-hocs` | `wc.wcBlocksSharedHocs` | Shares higher-order components across separately built scripts. |
+| `@woocommerce/types` | `wc-types` | `wc.wcTypes` | Shares runtime type guards used by the external package bundles. |
+
+This list is defined by `editorExternalPackages` in `bin/webpack-config-block-editor-unified-assets.js`. Other `@woocommerce/*` imports are bundled into `wc-block-library` for the unified editor build. Their standalone handles may still be registered for frontend assets or third-party scripts.
+
+`wc-blocks-middleware` is registered separately and loaded by both `wc-block-library` and `wc-blocks-data-store`. It does not map to an enqueueable `@woocommerce/*` package.
+
+### Cart and Checkout frontend chunks
+
+The `wc-blocks-checkout` and `wc-blocks-components` package bundles are built with the Cart and Checkout frontend configuration. Their generated asset metadata depends on:
+
+- `wc-cart-checkout-base`
+- `wc-cart-checkout-vendors`
+
+WordPress therefore loads `wc-cart-checkout-base-frontend.js` and `wc-cart-checkout-vendors-frontend.js` when the unified editor needs either package. The `-frontend` suffix identifies the build configuration that produced the files; it does not restrict them to storefront requests. Removing these dependencies without changing how the packages are built would leave required modules unavailable.
diff --git a/plugins/woocommerce/client/blocks/tests/js/config/global-mocks.js b/plugins/woocommerce/client/blocks/tests/js/config/global-mocks.js
index b53055f5755..f79cd52c7a2 100644
--- a/plugins/woocommerce/client/blocks/tests/js/config/global-mocks.js
+++ b/plugins/woocommerce/client/blocks/tests/js/config/global-mocks.js
@@ -390,6 +390,6 @@ jest.mock( 'client-zip', () => ( {
* store may be registered in the test environment without an actual editor
* context. Individual tests can override this mock if needed.
*/
-jest.mock( '../../../assets/js/data/utils/is-editor', () => ( {
+jest.mock( '@woocommerce/block-data/utils/is-editor', () => ( {
isEditor: jest.fn().mockReturnValue( false ),
} ) );
diff --git a/plugins/woocommerce/client/blocks/tests/js/jest.config.js b/plugins/woocommerce/client/blocks/tests/js/jest.config.js
index 259d2b9fa11..7f8d9e7b512 100644
--- a/plugins/woocommerce/client/blocks/tests/js/jest.config.js
+++ b/plugins/woocommerce/client/blocks/tests/js/jest.config.js
@@ -74,6 +74,7 @@ module.exports = {
'@woocommerce/base-hocs(.*)$': 'assets/js/base/hocs/$1',
'@woocommerce/base-hooks(.*)$': 'assets/js/base/hooks/$1',
'@woocommerce/base-utils(.*)$': 'assets/js/base/utils',
+ '@woocommerce/block-data/(.*)$': 'assets/js/data/$1',
'@woocommerce/block-data': 'assets/js/data',
'@woocommerce/resource-previews': 'assets/js/previews',
'@woocommerce/shared-context': 'assets/js/shared/context',
diff --git a/plugins/woocommerce/client/blocks/tsconfig.base.json b/plugins/woocommerce/client/blocks/tsconfig.base.json
index b5192f38bf3..08ee775ad3c 100644
--- a/plugins/woocommerce/client/blocks/tsconfig.base.json
+++ b/plugins/woocommerce/client/blocks/tsconfig.base.json
@@ -85,6 +85,9 @@
"@woocommerce/block-data": [
"assets/js/data"
],
+ "@woocommerce/block-data/*": [
+ "assets/js/data/*"
+ ],
"@woocommerce/block-hocs": [
"assets/js/hocs"
],
diff --git a/plugins/woocommerce/client/blocks/tsconfig.json b/plugins/woocommerce/client/blocks/tsconfig.json
index 3771cbe9813..6e2aa1c19c3 100644
--- a/plugins/woocommerce/client/blocks/tsconfig.json
+++ b/plugins/woocommerce/client/blocks/tsconfig.json
@@ -4,9 +4,13 @@
"./typings/**/*",
"./assets/js/**/*",
"./assets/js/**/*.json",
+ "./assets/js/blocks-registry/**/*",
"./packages/checkout/**/*",
"./packages/components/**/*",
+ "./assets/js/entities/**/*",
"./packages/prices/**/*",
+ "./assets/js/data/**/*",
+ "./assets/js/middleware/**/*",
"./assets/js/blocks/**/block.json",
"./assets/js/atomic/blocks/**/block.json",
"./assets/js/blocks/mini-cart/mini-cart-contents/inner-blocks/**/block.json",
diff --git a/plugins/woocommerce/client/blocks/webpack.config.js b/plugins/woocommerce/client/blocks/webpack.config.js
index e428ee262ab..4ccbdc832be 100644
--- a/plugins/woocommerce/client/blocks/webpack.config.js
+++ b/plugins/woocommerce/client/blocks/webpack.config.js
@@ -17,6 +17,10 @@ const {
getStylingConfig,
getCartAndCheckoutFrontendConfig,
} = require( './bin/webpack-configs.js' );
+const {
+ getUnifiedMainConfig,
+ getUnifiedStylingConfig,
+} = require( './bin/webpack-config-block-editor-unified-assets.js' );
const interactivityBlocksConfig = require( './bin/webpack-config-interactive-blocks.js' );
const dependencyDetectionConfig = require( './bin/webpack-config-dependency-detection.js' );
@@ -90,6 +94,13 @@ const MainConfig = {
...getMainConfig( { alias: getAlias() } ),
};
+// Unified Blocks config enabled at runtime by a WooCommerce feature flag.
+const UnifiedMainConfig = {
+ ...sharedConfig,
+ cache: getCacheConfig( 'unified-main', [] ),
+ ...getUnifiedMainConfig( { alias: getAlias() } ),
+};
+
// Frontend config for scripts used in the store itself.
const FrontendConfig = {
...sharedConfig,
@@ -124,9 +135,14 @@ const StylingConfig = {
...getStylingConfig( { alias: getAlias() } ),
};
-/**
- * Config to generate the site editor scripts.
- */
+// Unified editor styles enabled at runtime by a WooCommerce feature flag.
+const UnifiedStylingConfig = {
+ ...sharedConfig,
+ cache: getCacheConfig( 'unified-styling', [] ),
+ ...getUnifiedStylingConfig( { alias: getAlias() } ),
+};
+
+// Scripts used exclusively in the Site Editor by the legacy asset path.
const SiteEditorConfig = {
...sharedConfig,
cache: getCacheConfig( 'site-editor', [] ),
@@ -157,11 +173,13 @@ module.exports = [
CartAndCheckoutFrontendConfig,
CoreConfig,
MainConfig,
+ UnifiedMainConfig,
FrontendConfig,
ExtensionsConfig,
PaymentsConfig,
SiteEditorConfig,
StylingConfig,
+ UnifiedStylingConfig,
InteractivityBlocksConfig,
DependencyDetectionConfig,
];
diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json
index cb1b571e900..37b6ede4386 100644
--- a/plugins/woocommerce/package.json
+++ b/plugins/woocommerce/package.json
@@ -78,6 +78,7 @@
"test:e2e:default": "pnpm test:e2e:install && pnpm test:e2e:with-env default",
"test:e2e:install": "pnpm playwright install chromium",
"test:e2e:blocks": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --test tests/e2e/utils/blocks/*.test.mjs && pnpm playwright test --config=tests/e2e/playwright.config.ts --project=blocks-chromium",
+ "test:e2e:blocks:unified-editor-assets": "pnpm wp-env:e2e run cli wp option update woocommerce_feature_block_editor_unified_assets_enabled yes && pnpm test:e2e:blocks",
"test:e2e:blocks:performance": "pnpm playwright test --config=tests/e2e/playwright.performance.config.ts --project=blocks-performance",
"test:e2e:email-update-propagation:pr": "pnpm test:e2e:default --project=core-serial --project=core-parallel --grep @pr tests/email-editor/update-propagation",
"test:e2e:email-update-propagation:nightly": "pnpm test:e2e:default --project=core-serial --project=core-parallel tests/email-editor/update-propagation",
@@ -97,6 +98,7 @@
"test:php:watch": "sh ./client/blocks/bin/copy-blocks-json.sh && ./vendor/bin/phpunit-watcher watch",
"test:metrics": "USE_WP_ENV=1 pnpm playwright test --config=tests/metrics/playwright.config.js",
"test:metrics:ci": "../../.github/workflows/scripts/run-metrics.sh",
+ "test:metrics:ci:unified-editor-assets": "pnpm wp-env:e2e run cli wp option update woocommerce_feature_block_editor_unified_assets_enabled yes && pnpm test:metrics:ci",
"test:php:env": "sh ./client/blocks/bin/copy-blocks-json.sh && pnpm wp-env:test run --env-cwd='wp-content/plugins/woocommerce' cli tests/php/bin/run-phpunit.sh -c phpunit.xml --verbose",
"test:php:env:hpos-off": "sh ./client/blocks/bin/copy-blocks-json.sh && pnpm wp-env:test run --env-cwd='wp-content/plugins/woocommerce' cli env DISABLE_HPOS=1 tests/php/bin/run-phpunit.sh -c phpunit.xml --verbose",
"test:php:env:watch": "sh ./client/blocks/bin/copy-blocks-json.sh && pnpm wp-env:test run --env-cwd='wp-content/plugins/woocommerce' cli vendor/bin/phpunit-watcher watch --verbose",
@@ -780,6 +782,38 @@
"resultsPath": "../../tools/compare-perf/artifacts/"
}
},
+ {
+ "name": "Metrics - unified editor assets",
+ "testType": "performance",
+ "command": "test:metrics:ci:unified-editor-assets",
+ "optional": true,
+ "changes": [
+ "composer.json",
+ "composer.lock",
+ "includes/**/*.php",
+ "patterns/**/*.php",
+ "src/**/*.php",
+ "templates/**/*.php",
+ "templates/**/*.html",
+ "tests/metrics/**",
+ ".wp-env.e2e.json"
+ ],
+ "onlyForDependencies": [
+ "@woocommerce/admin-library",
+ "@woocommerce/block-library",
+ "@woocommerce/classic-assets"
+ ],
+ "testEnv": {
+ "start": "env:perf"
+ },
+ "events": [
+ "pull_request"
+ ],
+ "report": {
+ "resultsBlobName": "core-metrics-unified-editor-assets-report",
+ "resultsPath": "../../tools/compare-perf/artifacts/"
+ }
+ },
{
"name": "Blocks e2e tests",
"testType": "e2e",
@@ -837,6 +871,58 @@
"allure": true
}
},
+ {
+ "name": "Blocks e2e tests - unified editor assets",
+ "testType": "e2e",
+ "usesSharedPluginBuild": true,
+ "command": "test:e2e:blocks:unified-editor-assets",
+ "shardingArguments": [
+ "--shard=1/10",
+ "--shard=2/10",
+ "--shard=3/10",
+ "--shard=4/10",
+ "--shard=5/10",
+ "--shard=6/10",
+ "--shard=7/10",
+ "--shard=8/10",
+ "--shard=9/10",
+ "--shard=10/10"
+ ],
+ "changes": [
+ "patterns/**/*.php",
+ "includes/**/*.php",
+ "src/Blocks/**/*.php",
+ "src/Internal/Features/BlockEditorUnifiedAssets.php",
+ "templates/**/*.php",
+ "templates/**/*.html",
+ "tests/e2e/bin/blocks/**",
+ "tests/e2e/fixtures/blocks-setup.ts",
+ "tests/e2e/playwright.config.ts",
+ "tests/e2e/content-templates/blocks/**",
+ "tests/e2e/test-data/blocks/**",
+ "tests/e2e/test-plugins/blocks/**",
+ "tests/e2e/tests/blocks/**",
+ "tests/e2e/themes/blocks/**",
+ "tests/e2e/utils/blocks/**"
+ ],
+ "onlyForDependencies": [
+ "@woocommerce/block-library"
+ ],
+ "testEnv": {
+ "start": "env:start:blocks",
+ "config": {
+ "phpVersion": "7.4"
+ }
+ },
+ "events": [
+ "nightly-checks"
+ ],
+ "report": {
+ "resultsBlobName": "blocks-e2e-unified-editor-assets-report",
+ "resultsPath": "tests/e2e/test-results",
+ "allure": true
+ }
+ },
{
"name": "Blocks e2e tests - WP pre-release",
"optional": true,
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index d2a79439e1f..6a2ce350f6c 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -49641,18 +49641,6 @@ parameters:
count: 1
path: src/Blocks/BlockTypes/AbstractBlock.php
- -
- message: '#^Parameter \#1 \$arr1 of function array_merge expects array, array\|string given\.$#'
- identifier: argument.type
- count: 1
- path: src/Blocks/BlockTypes/AbstractBlock.php
-
- -
- message: '#^Parameter \#1 \$arr1 of function array_merge expects array, array\|string\|null given\.$#'
- identifier: argument.type
- count: 1
- path: src/Blocks/BlockTypes/AbstractBlock.php
-
-
message: '#^Parameter \#1 \$file_or_folder of function register_block_type_from_metadata expects string, string\|true given\.$#'
identifier: argument.type
@@ -49665,36 +49653,12 @@ parameters:
count: 1
path: src/Blocks/BlockTypes/AbstractBlock.php
- -
- message: '#^Parameter \#1 \$handle of method Automattic\\WooCommerce\\Blocks\\Assets\\Api\:\:register_script\(\) expects string, array\|string given\.$#'
- identifier: argument.type
- count: 1
- path: src/Blocks/BlockTypes/AbstractBlock.php
-
- -
- message: '#^Parameter \#1 \$handle of method Automattic\\WooCommerce\\Blocks\\Assets\\Api\:\:register_script\(\) expects string, array\|string\|null given\.$#'
- identifier: argument.type
- count: 1
- path: src/Blocks/BlockTypes/AbstractBlock.php
-
-
message: '#^Parameter \#1 \$object_or_string of function is_a expects object, array\|WP_Block given\.$#'
identifier: argument.type
count: 1
path: src/Blocks/BlockTypes/AbstractBlock.php
- -
- message: '#^Parameter \#1 \$relative_src of method Automattic\\WooCommerce\\Blocks\\Assets\\Api\:\:get_script_data\(\) expects string, array\|string given\.$#'
- identifier: argument.type
- count: 1
- path: src/Blocks/BlockTypes/AbstractBlock.php
-
- -
- message: '#^Parameter \#1 \$relative_src of method Automattic\\WooCommerce\\Blocks\\Assets\\Api\:\:get_script_data\(\) expects string, array\|string\|null given\.$#'
- identifier: argument.type
- count: 1
- path: src/Blocks/BlockTypes/AbstractBlock.php
-
-
message: '#^Parameter \#1 \$request of method WP_REST_Server\:\:get_namespace_index\(\) expects WP_REST_Request, array\<string, string\> given\.$#'
identifier: argument.type
@@ -49719,18 +49683,6 @@ parameters:
count: 1
path: src/Blocks/BlockTypes/AbstractBlock.php
- -
- message: '#^Parameter \#2 \$relative_src of method Automattic\\WooCommerce\\Blocks\\Assets\\Api\:\:register_script\(\) expects string, array\|string given\.$#'
- identifier: argument.type
- count: 1
- path: src/Blocks/BlockTypes/AbstractBlock.php
-
- -
- message: '#^Parameter \#2 \$relative_src of method Automattic\\WooCommerce\\Blocks\\Assets\\Api\:\:register_script\(\) expects string, array\|string\|null given\.$#'
- identifier: argument.type
- count: 1
- path: src/Blocks/BlockTypes/AbstractBlock.php
-
-
message: '#^Parameter \#3 \$block of method Automattic\\WooCommerce\\Blocks\\BlockTypes\\AbstractBlock\:\:enqueue_assets\(\) expects WP_Block, WP_Block\|null given\.$#'
identifier: argument.type
@@ -50247,12 +50199,6 @@ parameters:
count: 1
path: src/Blocks/BlockTypes/Cart.php
- -
- message: '#^PHPDoc tag @return has invalid value \(array\|string;\)\: Unexpected token ";", expected TOKEN_HORIZONTAL_WS at offset 148 on line 5$#'
- identifier: phpDoc.parseError
- count: 1
- path: src/Blocks/BlockTypes/Cart.php
-
-
message: '#^Parameter \#1 \$arr1 of function array_merge expects array, array\<string\>\|null given\.$#'
identifier: argument.type
@@ -50397,12 +50343,6 @@ parameters:
count: 1
path: src/Blocks/BlockTypes/Checkout.php
- -
- message: '#^PHPDoc tag @return has invalid value \(array\|string;\)\: Unexpected token ";", expected TOKEN_HORIZONTAL_WS at offset 148 on line 5$#'
- identifier: phpDoc.parseError
- count: 1
- path: src/Blocks/BlockTypes/Checkout.php
-
-
message: '#^Parameter \#1 \$arr1 of function array_merge expects array, array\<string\>\|null given\.$#'
identifier: argument.type
@@ -50925,12 +50865,6 @@ parameters:
count: 1
path: src/Blocks/BlockTypes/MiniCart.php
- -
- message: '#^PHPDoc tag @return has invalid value \(array\|string;\)\: Unexpected token ";", expected TOKEN_HORIZONTAL_WS at offset 148 on line 5$#'
- identifier: phpDoc.parseError
- count: 1
- path: src/Blocks/BlockTypes/MiniCart.php
-
-
message: '#^Parameter \#1 \$content of function do_blocks expects string, string\|false given\.$#'
identifier: argument.type
@@ -51051,12 +50985,6 @@ parameters:
count: 1
path: src/Blocks/BlockTypes/MiniCartContents.php
- -
- message: '#^PHPDoc tag @return has invalid value \(array\|string;\)\: Unexpected token ";", expected TOKEN_HORIZONTAL_WS at offset 152 on line 6$#'
- identifier: phpDoc.parseError
- count: 1
- path: src/Blocks/BlockTypes/MiniCartContents.php
-
-
message: '#^Method Automattic\\WooCommerce\\Blocks\\BlockTypes\\MiniCartFooterBlock\:\:enqueue_data\(\) has no return type specified\.$#'
identifier: missingType.return
diff --git a/plugins/woocommerce/src/Blocks/AssetsController.php b/plugins/woocommerce/src/Blocks/AssetsController.php
index ef3e4499d66..5f650bee231 100644
--- a/plugins/woocommerce/src/Blocks/AssetsController.php
+++ b/plugins/woocommerce/src/Blocks/AssetsController.php
@@ -6,6 +6,7 @@ namespace Automattic\WooCommerce\Blocks;
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
use Automattic\WooCommerce\Blocks\Utils\Utils;
+use Automattic\WooCommerce\Internal\Features\BlockEditorUnifiedAssets;
/**
* AssetsController class.
@@ -72,18 +73,22 @@ final class AssetsController {
$this->register_style( 'wc-blocks-packages-style', plugins_url( $this->api->get_block_asset_build_path( 'packages-style', 'css' ), dirname( __DIR__ ) ), array(), 'all', true );
$this->register_style( 'wc-blocks-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks', 'css' ), dirname( __DIR__ ) ), array(), 'all', true );
$this->register_style( 'wc-blocks-editor-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks-editor-style', 'css' ), dirname( __DIR__ ) ), array( 'wp-edit-blocks' ), 'all', true );
+ if ( BlockEditorUnifiedAssets::is_enabled() ) {
+ $this->register_style( 'wc-block-library-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-block-library-style', 'css' ), dirname( __DIR__ ) ), array( 'wp-edit-blocks' ), 'all', true );
+ }
$this->api->register_script( 'wc-types', $this->api->get_block_asset_build_path( 'wc-types' ), array(), false );
$this->api->register_script( 'wc-entities', 'assets/client/blocks/wc-entities.js', array(), false );
$this->api->register_script( 'wc-blocks-middleware', 'assets/client/blocks/wc-blocks-middleware.js', array(), false );
$this->api->register_script( 'wc-blocks-data-store', 'assets/client/blocks/wc-blocks-data.js', array( 'wc-blocks-middleware' ) );
- $this->api->register_script( 'wc-blocks-vendors', $this->api->get_block_asset_build_path( 'wc-blocks-vendors' ), array(), false );
$this->api->register_script( 'wc-blocks-registry', 'assets/client/blocks/wc-blocks-registry.js', array(), false );
- $this->api->register_script( 'wc-blocks', $this->api->get_block_asset_build_path( 'wc-blocks' ), array( 'wc-blocks-vendors' ), false );
$this->api->register_script( 'wc-blocks-shared-context', 'assets/client/blocks/wc-blocks-shared-context.js' );
$this->api->register_script( 'wc-blocks-shared-hocs', 'assets/client/blocks/wc-blocks-shared-hocs.js', array(), false );
+ $this->api->register_script( 'wc-blocks-components', 'assets/client/blocks/blocks-components.js' );
+ $this->register_editor_scripts();
- // The price package is shared externally so has no blocks prefix.
+ // Keep price-format as a dedicated shared package: editor and frontend/runtime assets depend on it, and
+ // externalizing it avoids duplicating price formatting helpers across bundles.
$this->api->register_script( 'wc-price-format', 'assets/client/blocks/price-format.js', array(), false );
// Vendor scripts for blocks frontends (not including cart and checkout).
@@ -94,7 +99,6 @@ final class AssetsController {
$this->api->register_script( 'wc-cart-checkout-base', $this->api->get_block_asset_build_path( 'wc-cart-checkout-base-frontend' ), array(), true );
$this->api->register_script( 'wc-blocks-checkout', 'assets/client/blocks/blocks-checkout.js' );
$this->api->register_script( 'wc-blocks-checkout-events', 'assets/client/blocks/blocks-checkout-events.js' );
- $this->api->register_script( 'wc-blocks-components', 'assets/client/blocks/blocks-components.js' );
$this->api->register_script( 'wc-schema-parser', 'assets/client/blocks/wc-schema-parser.js', array(), false );
// Sanitize.
@@ -116,6 +120,65 @@ final class AssetsController {
);
}
+ /**
+ * Register scripts for the active block editor asset configuration.
+ */
+ private function register_editor_scripts(): void {
+ if ( ! BlockEditorUnifiedAssets::is_enabled() ) {
+ $this->api->register_script( 'wc-blocks-vendors', $this->api->get_block_asset_build_path( 'wc-blocks-vendors' ), array(), false );
+ $this->api->register_script( 'wc-blocks', $this->api->get_block_asset_build_path( 'wc-blocks' ), array( 'wc-blocks-vendors' ), false );
+ return;
+ }
+
+ $this->api->register_script(
+ 'wc-block-library',
+ $this->api->get_block_asset_build_path( 'wc-block-library' ),
+ array( 'wc-blocks-middleware', 'wc-entities' ),
+ true
+ );
+ $this->register_deprecated_script_handles();
+ }
+
+ /**
+ * Register deprecated script handles for backward compatibility.
+ */
+ private function register_deprecated_script_handles(): void {
+ wp_register_script( 'wc-blocks-vendors', false, array(), $this->api->wc_version, true );
+ wp_register_script( 'wc-blocks', false, array( 'wc-blocks-vendors' ), $this->api->wc_version, true );
+
+ $this->add_deprecated_script_handle_warnings(
+ array(
+ 'wc-blocks-vendors',
+ 'wc-blocks',
+ )
+ );
+ }
+
+ /**
+ * Add console warnings for deprecated script handles.
+ *
+ * @param array $handles Deprecated script handles.
+ */
+ private function add_deprecated_script_handle_warnings( array $handles ): void {
+ foreach ( $handles as $handle ) {
+ $message = wp_json_encode(
+ sprintf(
+ '[WooCommerce] The "%s" script handle is deprecated and kept for backward compatibility only. See the enqueueable packages documentation for the supported WooCommerce Blocks package handles: https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce/client/blocks/docs/internal-developers/enqueueable-packages',
+ $handle
+ )
+ );
+
+ if ( false === $message ) {
+ continue;
+ }
+
+ $this->api->add_inline_script(
+ $handle,
+ sprintf( 'console.warn( %s );', $message )
+ );
+ }
+ }
+
/**
* Defines resource hints to help speed up the loading of some critical blocks.
*
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/AbstractBlock.php b/plugins/woocommerce/src/Blocks/BlockTypes/AbstractBlock.php
index 3b76c0849b7..43057813918 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/AbstractBlock.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/AbstractBlock.php
@@ -1,4 +1,5 @@
<?php
+
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Admin\Features\Features;
@@ -6,6 +7,7 @@ use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
use Automattic\WooCommerce\Enums\ProductStatus;
+use Automattic\WooCommerce\Internal\Features\BlockEditorUnifiedAssets;
use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
use WP_Block;
@@ -146,36 +148,98 @@ abstract class AbstractBlock {
* This registers the scripts; it does not enqueue them.
*/
protected function register_block_type_assets() {
- if ( null !== $this->get_block_type_editor_script() ) {
- $data = $this->asset_api->get_script_data( $this->get_block_type_editor_script( 'path' ) );
- $has_i18n = in_array( 'wp-i18n', $data['dependencies'], true );
+ $editor_script = $this->get_block_type_editor_script();
+ if ( is_array( $editor_script ) ) {
+ $this->register_editor_script_asset( $editor_script );
+ }
- $this->asset_api->register_script(
- $this->get_block_type_editor_script( 'handle' ),
- $this->get_block_type_editor_script( 'path' ),
+ $frontend_script = $this->get_block_type_script();
+ if ( is_array( $frontend_script ) ) {
+ $this->register_frontend_script_asset( $frontend_script );
+ }
+ }
+
+ /**
+ * Register the editor script asset.
+ *
+ * @param array $editor_script The editor script data.
+ * @return void
+ */
+ private function register_editor_script_asset( $editor_script ) {
+ $handle = (string) $editor_script['handle'];
+ $path = (string) $editor_script['path'];
+ $dependencies = array_values(
+ array_unique(
array_merge(
- $this->get_block_type_editor_script( 'dependencies' ),
+ (array) $editor_script['dependencies'],
$this->integration_registry->get_all_registered_editor_script_handles()
- ),
- $has_i18n
- );
- }
- if ( null !== $this->get_block_type_script() ) {
- $data = $this->asset_api->get_script_data( $this->get_block_type_script( 'path' ) );
+ )
+ )
+ );
+
+ if ( wp_script_is( $handle, 'registered' ) ) {
+ $this->add_script_dependencies( $handle, $dependencies );
+ } else {
+ $data = $this->asset_api->get_script_data( $path );
$has_i18n = in_array( 'wp-i18n', $data['dependencies'], true );
$this->asset_api->register_script(
- $this->get_block_type_script( 'handle' ),
- $this->get_block_type_script( 'path' ),
- array_merge(
- $this->get_block_type_script( 'dependencies' ),
- $this->integration_registry->get_all_registered_script_handles()
- ),
+ $handle,
+ $path,
+ $dependencies,
$has_i18n
);
}
}
+ /**
+ * Register the frontend script asset.
+ *
+ * @param array $frontend_script The frontend script data.
+ * @return void
+ */
+ private function register_frontend_script_asset( $frontend_script ) {
+ $handle = (string) $frontend_script['handle'];
+ $path = (string) $frontend_script['path'];
+ $dependencies = array_merge(
+ (array) $frontend_script['dependencies'],
+ $this->integration_registry->get_all_registered_script_handles()
+ );
+ $data = $this->asset_api->get_script_data( $path );
+ $has_i18n = in_array( 'wp-i18n', $data['dependencies'], true );
+
+ $this->asset_api->register_script(
+ $handle,
+ $path,
+ $dependencies,
+ $has_i18n
+ );
+ }
+
+ /**
+ * Add dependencies to a registered script.
+ *
+ * @param string $handle The script handle.
+ * @param array $dependencies The dependencies to add.
+ * @return void
+ */
+ private function add_script_dependencies( $handle, $dependencies ) {
+ $wp_scripts = wp_scripts();
+
+ if ( ! isset( $wp_scripts->registered[ $handle ] ) ) {
+ return;
+ }
+
+ $wp_scripts->registered[ $handle ]->deps = array_values(
+ array_unique(
+ array_merge(
+ $wp_scripts->registered[ $handle ]->deps,
+ $dependencies
+ )
+ )
+ );
+ }
+
/**
* Injects Chunk Translations into the page so translations work for lazy loaded components.
*
@@ -302,11 +366,17 @@ abstract class AbstractBlock {
* @return array|string
*/
protected function get_block_type_editor_script( $key = null ) {
- $script = [
- 'handle' => 'wc-' . $this->block_name . '-block',
- 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
- 'dependencies' => [ 'wc-blocks' ],
- ];
+ $script = BlockEditorUnifiedAssets::is_enabled()
+ ? array(
+ 'handle' => 'wc-block-library',
+ 'path' => $this->asset_api->get_block_asset_build_path( 'wc-block-library' ),
+ 'dependencies' => array(),
+ )
+ : array(
+ 'handle' => 'wc-' . $this->block_name . '-block',
+ 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
+ 'dependencies' => array( 'wc-blocks' ),
+ );
return $key ? $script[ $key ] : $script;
}
@@ -317,7 +387,7 @@ abstract class AbstractBlock {
* @return string|null
*/
protected function get_block_type_editor_style() {
- return 'wc-blocks-editor-style';
+ return BlockEditorUnifiedAssets::is_enabled() ? 'wc-block-library-style' : 'wc-blocks-editor-style';
}
/**
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/Cart.php b/plugins/woocommerce/src/Blocks/BlockTypes/Cart.php
index 5b6d27a60af..aea17c68d7d 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/Cart.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/Cart.php
@@ -110,21 +110,6 @@ class Cart extends AbstractBlock {
);
}
- /**
- * Get the editor script handle for this block type.
- *
- * @param string $key Data to get, or default to everything.
- * @return array|string;
- */
- protected function get_block_type_editor_script( $key = null ) {
- $script = [
- 'handle' => 'wc-' . $this->block_name . '-block',
- 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
- 'dependencies' => [ 'wc-blocks' ],
- ];
- return $key ? $script[ $key ] : $script;
- }
-
/**
* Get the frontend script handle for this block type.
*
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/Checkout.php b/plugins/woocommerce/src/Blocks/BlockTypes/Checkout.php
index ce2792016fb..5d48876aaf4 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/Checkout.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/Checkout.php
@@ -159,21 +159,6 @@ class Checkout extends AbstractBlock {
);
}
- /**
- * Get the editor script handle for this block type.
- *
- * @param string $key Data to get, or default to everything.
- * @return array|string;
- */
- protected function get_block_type_editor_script( $key = null ) {
- $script = [
- 'handle' => 'wc-' . $this->block_name . '-block',
- 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
- 'dependencies' => [ 'wc-blocks' ],
- ];
- return $key ? $script[ $key ] : $script;
- }
-
/**
* Get the frontend script handle for this block type.
*
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/CouponCode.php b/plugins/woocommerce/src/Blocks/BlockTypes/CouponCode.php
index daaa050e8df..079dfc0fcd9 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/CouponCode.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/CouponCode.php
@@ -46,21 +46,6 @@ class CouponCode extends AbstractBlock {
'letter-spacing' => '1px',
);
- /**
- * Get the editor script handle for this block type.
- *
- * @param string|null $key Data to get. Valid keys: "handle", "path", "dependencies".
- * @return array|string|null
- */
- protected function get_block_type_editor_script( $key = null ) {
- $script = array(
- 'handle' => 'wc-' . $this->block_name . '-block',
- 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
- 'dependencies' => array( 'wc-blocks' ),
- );
- return null === $key ? $script : ( $script[ $key ] ?? null );
- }
-
/**
* Get the frontend style handle for this block type.
*
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/EmailContent.php b/plugins/woocommerce/src/Blocks/BlockTypes/EmailContent.php
index 33e590b3824..2151bfc871d 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/EmailContent.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/EmailContent.php
@@ -27,21 +27,6 @@ class EmailContent extends AbstractBlock {
return null;
}
- /**
- * Get the editor script handle for this block type.
- *
- * @param string $key Data to get, or default to everything.
- * @return array|string
- */
- protected function get_block_type_editor_script( $key = null ) {
- $script = [
- 'handle' => 'wc-' . $this->block_name . '-block',
- 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
- 'dependencies' => [ 'wc-blocks' ],
- ];
- return $key ? $script[ $key ] : $script;
- }
-
/**
* Get the frontend script handle for this block type.
*
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/MiniCart.php b/plugins/woocommerce/src/Blocks/BlockTypes/MiniCart.php
index 98e9bb03e52..fce4b28a622 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/MiniCart.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/MiniCart.php
@@ -131,21 +131,6 @@ class MiniCart extends AbstractBlock {
return $parsed_hooked_block;
}
- /**
- * Get the editor script handle for this block type.
- *
- * @param string $key Data to get, or default to everything.
- * @return array|string;
- */
- protected function get_block_type_editor_script( $key = null ) {
- $script = array(
- 'handle' => 'wc-' . $this->block_name . '-block',
- 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
- 'dependencies' => array( 'wc-blocks' ),
- );
- return $key ? $script[ $key ] : $script;
- }
-
/**
* Get the frontend script handle for this block type.
*
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/MiniCartContents.php b/plugins/woocommerce/src/Blocks/BlockTypes/MiniCartContents.php
index 695b99e0071..18d7d9a5c97 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/MiniCartContents.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/MiniCartContents.php
@@ -16,22 +16,6 @@ class MiniCartContents extends AbstractBlock {
*/
protected $block_name = 'mini-cart-contents';
- /**
- * Get the editor script handle for this block type.
- *
- * @param string $key Data to get, or default to everything.
- *
- * @return array|string;
- */
- protected function get_block_type_editor_script( $key = null ) {
- $script = [
- 'handle' => 'wc-' . $this->block_name . '-block',
- 'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
- 'dependencies' => [ 'wc-blocks' ],
- ];
- return $key ? $script[ $key ] : $script;
- }
-
/**
* Get the frontend script handle for this block type.
*
diff --git a/plugins/woocommerce/src/Blocks/BlockTypesController.php b/plugins/woocommerce/src/Blocks/BlockTypesController.php
index ec886d56cf6..f59b01ed6a6 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypesController.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypesController.php
@@ -9,6 +9,7 @@ use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
use Automattic\WooCommerce\Blocks\BlockTypes\Cart;
use Automattic\WooCommerce\Blocks\BlockTypes\Checkout;
use Automattic\WooCommerce\Blocks\BlockTypes\MiniCartContents;
+use Automattic\WooCommerce\Internal\Features\BlockEditorUnifiedAssets;
use Automattic\WooCommerce\Internal\ShopperLists\ShopperListsController;
/**
@@ -62,6 +63,7 @@ final class BlockTypesController {
add_filter( 'render_block', array( $this, 'add_data_attributes' ), 10, 2 );
add_action( 'woocommerce_login_form_end', array( $this, 'redirect_to_field' ) );
add_filter( 'widget_types_to_hide_from_legacy_widget_block', array( $this, 'hide_legacy_widgets_with_block_equivalent' ) );
+ add_filter( 'block_type_metadata_settings', array( $this, 'use_single_block_editor_style' ), 10, 2 );
add_filter( 'register_block_type_args', array( $this, 'enqueue_block_style_for_classic_themes' ), 10, 2 );
add_filter( 'block_core_breadcrumbs_post_type_settings', array( $this, 'set_product_breadcrumbs_preferred_taxonomy' ), 10, 3 );
add_filter( 'block_core_breadcrumbs_items', array( $this, 'apply_woocommerce_breadcrumb_filters' ), 10, 1 );
@@ -585,7 +587,64 @@ final class BlockTypesController {
}
/**
- * Set the preferred taxonomy and term for product breadcrumbs.
+ * Use one shared editor stylesheet for WooCommerce blocks.
+ *
+ * WordPress loads `style` handles in both the frontend and editor. WooCommerce
+ * keeps those per-block handles for frontend performance, but removes them in
+ * admin so the block editor loads the combined stylesheet only.
+ *
+ * @internal
+ *
+ * @param array $settings Block settings.
+ * @param array $metadata Block metadata.
+ *
+ * @return array Block settings.
+ */
+ public function use_single_block_editor_style( $settings, $metadata ) {
+ if (
+ ! BlockEditorUnifiedAssets::is_enabled() ||
+ ! is_admin() ||
+ ! $this->is_woocommerce_block_metadata( $metadata ) ) {
+ return $settings;
+ }
+
+ $settings['style_handles'] = array();
+ $settings['style'] = array();
+ $settings['editor_style_handles'] = array( 'wc-block-library-style' );
+ $settings['editor_style'] = array( 'wc-block-library-style' );
+
+ return $settings;
+ }
+
+ /**
+ * Check whether block metadata belongs to a block bundled with WooCommerce.
+ *
+ * @param array $metadata Block metadata.
+ *
+ * @return bool Whether the metadata file is in the WooCommerce blocks directory.
+ */
+ private function is_woocommerce_block_metadata( $metadata ) {
+ static $blocks_path = null;
+
+ if ( null === $blocks_path ) {
+ $resolved_path = realpath( WC_ABSPATH . 'assets/client/blocks' );
+ $blocks_path = false === $resolved_path
+ ? ''
+ : trailingslashit( wp_normalize_path( $resolved_path ) );
+ }
+
+ if ( '' === $blocks_path || empty( $metadata['file'] ) ) {
+ return false;
+ }
+
+ return str_starts_with(
+ wp_normalize_path( $metadata['file'] ),
+ $blocks_path
+ );
+ }
+
+ /**
+ * Set the preferred taxonomy and term for the breadcrumbs block on the product post type.
*
* @internal
*
diff --git a/plugins/woocommerce/src/Blocks/DependencyDetection.php b/plugins/woocommerce/src/Blocks/DependencyDetection.php
index 719517b9dbb..324731ace2c 100644
--- a/plugins/woocommerce/src/Blocks/DependencyDetection.php
+++ b/plugins/woocommerce/src/Blocks/DependencyDetection.php
@@ -53,6 +53,7 @@ final class DependencyDetection {
'blocksComponents' => 'wc-blocks-components',
'wcTypes' => 'wc-types',
'sanitize' => 'wc-sanitize',
+ 'wcEntities' => 'wc-entities',
);
/**
diff --git a/plugins/woocommerce/src/Blocks/Domain/Bootstrap.php b/plugins/woocommerce/src/Blocks/Domain/Bootstrap.php
index bbbad044980..b19c178e88b 100644
--- a/plugins/woocommerce/src/Blocks/Domain/Bootstrap.php
+++ b/plugins/woocommerce/src/Blocks/Domain/Bootstrap.php
@@ -37,6 +37,7 @@ use Automattic\WooCommerce\StoreApi\SchemaController;
use Automattic\WooCommerce\StoreApi\StoreApi;
use Automattic\WooCommerce\Blocks\Shipping\ShippingController;
use Automattic\WooCommerce\Blocks\TemplateOptions;
+use Automattic\WooCommerce\Internal\Features\BlockEditorUnifiedAssets;
/**
@@ -170,8 +171,10 @@ class Bootstrap {
* @return bool
*/
protected function is_built() {
+ $editor_asset = BlockEditorUnifiedAssets::is_enabled() ? 'wc-block-library.js' : 'featured-product.js';
+
return file_exists(
- $this->package->get_path( 'assets/client/blocks/featured-product.js' )
+ $this->package->get_path( 'assets/client/blocks/' . $editor_asset )
);
}
diff --git a/plugins/woocommerce/src/Blocks/Payments/Api.php b/plugins/woocommerce/src/Blocks/Payments/Api.php
index 095c3285c43..82a36757438 100644
--- a/plugins/woocommerce/src/Blocks/Payments/Api.php
+++ b/plugins/woocommerce/src/Blocks/Payments/Api.php
@@ -7,6 +7,7 @@ use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer;
use Automattic\WooCommerce\Blocks\Payments\Integrations\CashOnDelivery;
use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque;
use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal;
+use Automattic\WooCommerce\Internal\Features\BlockEditorUnifiedAssets;
/**
* The Api class provides an interface to payment method registration.
@@ -59,10 +60,33 @@ class Api {
* @return array
*/
public function add_payment_method_script_dependencies( $dependencies, $handle ) {
- if ( ! in_array( $handle, [ 'wc-checkout-block', 'wc-checkout-block-frontend', 'wc-cart-block', 'wc-cart-block-frontend' ], true ) ) {
+ if ( ! in_array( $handle, $this->get_cart_checkout_script_handles(), true ) ) {
return $dependencies;
}
- return array_merge( $dependencies, $this->payment_method_registry->get_all_active_payment_method_script_dependencies() );
+ return array_values(
+ array_unique(
+ array_merge(
+ $dependencies,
+ $this->payment_method_registry->get_all_active_payment_method_script_dependencies()
+ )
+ )
+ );
+ }
+
+ /**
+ * Get Cart and Checkout script handles for the active editor asset configuration.
+ *
+ * @return string[] Script handles.
+ */
+ private function get_cart_checkout_script_handles(): array {
+ $editor_handles = BlockEditorUnifiedAssets::is_enabled()
+ ? array( 'wc-block-library' )
+ : array( 'wc-checkout-block', 'wc-cart-block' );
+
+ return array_merge(
+ $editor_handles,
+ array( 'wc-cart-block-frontend', 'wc-checkout-block-frontend' )
+ );
}
/**
@@ -160,7 +184,7 @@ class Api {
sprintf( 'console.error( "%s" );', $error_message )
);
- $cart_checkout_scripts = [ 'wc-cart-block', 'wc-cart-block-frontend', 'wc-checkout-block', 'wc-checkout-block-frontend' ];
+ $cart_checkout_scripts = $this->get_cart_checkout_script_handles();
foreach ( $cart_checkout_scripts as $script_handle ) {
if (
! array_key_exists( $script_handle, $wp_scripts->registered ) ||
diff --git a/plugins/woocommerce/src/Internal/Features/BlockEditorUnifiedAssets.php b/plugins/woocommerce/src/Internal/Features/BlockEditorUnifiedAssets.php
new file mode 100644
index 00000000000..ecb9328212a
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Features/BlockEditorUnifiedAssets.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * BlockEditorUnifiedAssets class file.
+ */
+
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Internal\Features;
+
+use Automattic\WooCommerce\Utilities\FeaturesUtil;
+
+/**
+ * Feature gate for unified WooCommerce block editor assets.
+ */
+final class BlockEditorUnifiedAssets {
+
+ /**
+ * Feature Name.
+ */
+ public const FEATURE_NAME = 'block_editor_unified_assets';
+
+ /**
+ * Option that stores whether the feature is enabled.
+ */
+ public const OPTION_NAME = 'woocommerce_feature_block_editor_unified_assets_enabled';
+
+ /**
+ * Check whether unified block editor assets are enabled.
+ *
+ * @return bool True when unified block editor assets are enabled.
+ *
+ * @since 11.1.0
+ */
+ public static function is_enabled(): bool {
+ return FeaturesUtil::feature_is_enabled( self::FEATURE_NAME );
+ }
+}
diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php
index a12b1d51c7a..4b0c0a13b67 100644
--- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php
+++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php
@@ -295,7 +295,7 @@ class FeaturesController {
$tracking_enabled = WC_Site_Tracking::is_tracking_enabled();
$legacy_features = array(
- 'analytics' => array(
+ 'analytics' => array(
'name' => __( 'WooCommerce Analytics', 'woocommerce' ),
'description' => __( 'Enable WooCommerce Analytics to track your store\'s key metrics and view them in a detailed dashboard. All data stays within your store.', 'woocommerce' ),
'option_key' => Analytics::TOGGLE_OPTION_NAME,
@@ -305,7 +305,7 @@ class FeaturesController {
'skip_compatibility_checks' => true,
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
),
- ProductMediaGallery::FEATURE_ID => array(
+ ProductMediaGallery::FEATURE_ID => array(
'name' => __( 'Product gallery videos', 'woocommerce' ),
'description' => __( 'Enable videos in product galleries.', 'woocommerce' ),
'option_key' => ProductMediaGallery::ENABLE_OPTION_NAME,
@@ -315,14 +315,14 @@ class FeaturesController {
'skip_compatibility_checks' => true,
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
),
- 'cart_checkout_blocks' => array(
+ 'cart_checkout_blocks' => array(
'name' => __( 'Cart & Checkout Blocks', 'woocommerce' ),
'description' => __( 'Optimize for faster checkout', 'woocommerce' ),
'is_experimental' => false,
'disable_ui' => true,
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
),
- 'rate_limit_checkout' => array(
+ 'rate_limit_checkout' => array(
'name' => __( 'Rate limit Checkout', 'woocommerce' ),
'description' => sprintf(
// translators: %s is the URL to the rate limiting documentation.
@@ -335,7 +335,7 @@ class FeaturesController {
'skip_compatibility_checks' => true,
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
),
- 'marketplace' => array(
+ 'marketplace' => array(
'name' => __( 'Marketplace', 'woocommerce' ),
'description' => __(
'New, faster way to find extensions and themes for your WooCommerce store',
@@ -351,7 +351,7 @@ class FeaturesController {
),
// Marked as a legacy feature to avoid compatibility checks, which aren't really relevant to this feature.
// https://github.com/woocommerce/woocommerce/pull/39701#discussion_r1376976959.
- 'order_attribution' => array(
+ 'order_attribution' => array(
'name' => __( 'Order Attribution', 'woocommerce' ),
'description' => __(
'Enable this feature to track and credit channels and campaigns that contribute to orders on your site',
@@ -363,7 +363,7 @@ class FeaturesController {
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
'is_experimental' => false,
),
- 'site_visibility_badge' => array(
+ 'site_visibility_badge' => array(
'name' => __( 'Site visibility badge', 'woocommerce' ),
'description' => __(
'Enable the site visibility badge in the WordPress admin bar',
@@ -376,7 +376,7 @@ class FeaturesController {
'is_experimental' => false,
'disabled' => false,
),
- 'hpos_fts_indexes' => array(
+ 'hpos_fts_indexes' => array(
'name' => __( 'HPOS Full text search indexes', 'woocommerce' ),
'description' => __(
'Create and use full text search indexes for orders. This feature only works with high-performance order storage.',
@@ -388,7 +388,7 @@ class FeaturesController {
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
'option_key' => CustomOrdersTableController::HPOS_FTS_INDEX_OPTION,
),
- 'hpos_datastore_caching' => array(
+ 'hpos_datastore_caching' => array(
'name' => __( 'HPOS Data Caching', 'woocommerce' ),
'description' => __(
'Enable order data caching in the datastore. This feature only works with high-performance order storage and is recommended for stores using object caching.',
@@ -401,7 +401,7 @@ class FeaturesController {
'disable_ui' => false,
'option_key' => CustomOrdersTableController::HPOS_DATASTORE_CACHING_ENABLED_OPTION,
),
- 'remote_logging' => array(
+ 'remote_logging' => array(
'name' => __( 'Remote Logging', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s: opening link tag, %2$s: closing link tag */
@@ -436,7 +436,7 @@ class FeaturesController {
},
),
),
- 'deferred_transactional_emails' => array(
+ 'deferred_transactional_emails' => array(
'name' => __( 'Deferred emails', 'woocommerce' ),
'description' => __(
'Send transactional emails asynchronously via Action Scheduler instead of during the current request.',
@@ -446,7 +446,7 @@ class FeaturesController {
'enabled_by_default' => false,
'is_experimental' => false,
),
- 'customer_review_request' => array(
+ 'customer_review_request' => array(
'name' => __( 'Customer review request (beta)', 'woocommerce' ),
'description' => __(
'Send customers a transactional email after order completion inviting them to review the products they bought, and host the per-order Review Order landing page.',
@@ -458,7 +458,7 @@ class FeaturesController {
'enabled_by_default' => false,
'is_experimental' => false,
),
- 'order_withdrawal' => array(
+ 'order_withdrawal' => array(
'name' => __( 'Order withdrawal', 'woocommerce' ),
'description' => __( 'Enable the public order withdrawal endpoint for stakeholder testing.', 'woocommerce' ),
'enabled_by_default' => false,
@@ -466,7 +466,7 @@ class FeaturesController {
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
'is_experimental' => true,
),
- 'abandoned_cart_recovery' => array(
+ 'abandoned_cart_recovery' => array(
'name' => __( 'Abandoned cart recovery', 'woocommerce' ),
'description' => __(
'Send a reminder email to shoppers who didn\'t finish checking out.',
@@ -477,7 +477,7 @@ class FeaturesController {
'enabled_by_default' => false,
'is_experimental' => true,
),
- 'email_improvements' => array(
+ 'email_improvements' => array(
'name' => __( 'Email improvements', 'woocommerce' ),
'description' => __(
'Enable modern email design for transactional emails',
@@ -497,7 +497,7 @@ class FeaturesController {
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
'is_experimental' => false,
),
- 'blueprint' => array(
+ 'blueprint' => array(
'name' => __( 'Blueprint (beta)', 'woocommerce' ),
'description' => __(
'Enable blueprint to import and export settings in bulk',
@@ -518,7 +518,7 @@ class FeaturesController {
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
'is_experimental' => false,
),
- 'block_email_editor' => array(
+ 'block_email_editor' => array(
'name' => __( 'Block Email Editor (alpha)', 'woocommerce' ),
'description' => __(
'Enable the block-based email editor for transactional emails.',
@@ -550,7 +550,7 @@ class FeaturesController {
'skip_compatibility_checks' => true,
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
),
- 'wc-visual-attribute' => array(
+ 'wc-visual-attribute' => array(
'name' => __( 'Color swatches for attributes', 'woocommerce' ),
'description' => __(
'Add color swatches to product attribute values.',
@@ -563,7 +563,7 @@ class FeaturesController {
'skip_compatibility_checks' => true,
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
),
- 'point_of_sale' => array(
+ 'point_of_sale' => array(
'name' => __( 'Point of Sale', 'woocommerce' ),
'description' => __(
'Enable Point of Sale functionality in the WooCommerce mobile apps.',
@@ -577,7 +577,7 @@ class FeaturesController {
'deprecated_since' => '11.0.0',
'deprecated_value' => true,
),
- 'point_of_sale_staff' => array(
+ 'point_of_sale_staff' => array(
'name' => __( 'POS staff', 'woocommerce' ),
'description' => __(
'Experimental: POS staff management, roles, and order attribution.',
@@ -591,7 +591,7 @@ class FeaturesController {
'skip_compatibility_checks' => true,
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
),
- 'fulfillments' => array(
+ 'fulfillments' => array(
'name' => __( 'Order Fulfillments', 'woocommerce' ),
'description' => __(
'Enable the Order Fulfillments feature to manage order fulfillment and shipping.',
@@ -602,7 +602,7 @@ class FeaturesController {
'is_experimental' => false,
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
),
- 'mcp_integration' => array(
+ 'mcp_integration' => array(
'name' => __( 'WooCommerce MCP', 'woocommerce' ),
'description' => $this->get_mcp_integration_description(),
'enabled_by_default' => false,
@@ -611,7 +611,7 @@ class FeaturesController {
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
'is_legacy' => false,
),
- 'destroy-empty-sessions' => array(
+ 'destroy-empty-sessions' => array(
'name' => __( 'Clear Customer Sessions When Empty', 'woocommerce' ),
'description' => __(
'[Performance] Removes session cookies for non-logged in customers when session data is empty, improving page caching performance. May cause compatibility issues with extensions that depend on the session cookie without using session data.',
@@ -622,7 +622,7 @@ class FeaturesController {
'disable_ui' => false,
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
),
- 'agentic_checkout' => array(
+ 'agentic_checkout' => array(
'name' => __( 'Agentic Checkout API', 'woocommerce' ),
'description' => __(
'Enable the Agentic Checkout API for AI-powered checkout experiences (e.g., ChatGPT). This adds REST API endpoints that allow AI agents to create and manage checkout sessions.',
@@ -634,7 +634,7 @@ class FeaturesController {
'skip_compatibility_checks' => true,
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
),
- 'dual_code_graphql_api' => array(
+ 'dual_code_graphql_api' => array(
'name' => __( 'Dual Code & GraphQL API', 'woocommerce' ),
'description' => __(
'Experimental code-first API for WooCommerce with automatic GraphQL endpoint generation. Requires PHP 8.1 or later.',
@@ -646,7 +646,7 @@ class FeaturesController {
'skip_compatibility_checks' => true,
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
),
- PushNotifications::FEATURE_NAME => array(
+ PushNotifications::FEATURE_NAME => array(
'name' => __( 'Push Notifications', 'woocommerce' ),
'description' => __(
'Enable push notifications for the WooCommerce mobile apps to receive order notifications and store updates.',
@@ -660,7 +660,7 @@ class FeaturesController {
'deprecated_since' => '10.9.2',
'deprecated_value' => true,
),
- 'rest_api_caching' => array(
+ 'rest_api_caching' => array(
'name' => __( 'REST API Caching', 'woocommerce' ),
'description' => sprintf(
/* translators: %1$s and %2$s are opening and closing <a> tags */
@@ -674,7 +674,7 @@ class FeaturesController {
'skip_compatibility_checks' => true,
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
),
- 'cart_save_for_later' => array(
+ 'cart_save_for_later' => array(
'name' => __( 'Save for Later in Cart', 'woocommerce' ),
'description' => __(
'Let shoppers save cart items to a list to purchase later.',
@@ -687,7 +687,7 @@ class FeaturesController {
'option_key' => 'woocommerce_cart_save_for_later_enabled',
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
),
- 'product_wishlist' => array(
+ 'product_wishlist' => array(
'name' => __( 'Wishlists', 'woocommerce' ),
'description' => __(
'Let shoppers save products to a wishlist from product pages. Requires the Add to Cart + Options block on the single-product template.',
@@ -698,7 +698,7 @@ class FeaturesController {
'option_key' => 'woocommerce_product_wishlist_enabled',
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
),
- ProductCacheController::FEATURE_NAME => array(
+ ProductCacheController::FEATURE_NAME => array(
'name' => __( 'Cache Product Objects', 'woocommerce' ),
'description' => __(
'[Performance] Speeds up your store by caching product objects during each request, preventing duplicate product loads. Can improve page load times on product-heavy pages.',
@@ -709,6 +709,15 @@ class FeaturesController {
'is_experimental' => true,
'disable_ui' => false,
),
+ BlockEditorUnifiedAssets::FEATURE_NAME => array(
+ 'name' => __( 'Unified block editor assets', 'woocommerce' ),
+ 'description' => __( 'Load WooCommerce block editor scripts and styles from shared bundles to improve editor performance.', 'woocommerce' ),
+ 'option_key' => BlockEditorUnifiedAssets::OPTION_NAME,
+ 'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
+ 'enabled_by_default' => false,
+ 'is_experimental' => true,
+ 'disable_ui' => false,
+ ),
);
if ( ! $tracking_enabled ) {
@@ -1905,8 +1914,9 @@ class FeaturesController {
$incompatible_features_count = count( $incompatible_features );
if ( $incompatible_features_count > 0 ) {
- $columns_count = $wp_list_table->get_column_count();
- $is_active = true; // For now we are showing active plugins in the "Incompatible with..." view.
+ $columns_count = $wp_list_table->get_column_count();
+ $is_active = true;
+ // For now we are showing active plugins in the "Incompatible with..." view.
$is_active_class = $is_active ? 'active' : 'inactive';
$is_active_td_style = $is_active ? " style='border-left: 4px solid #72aee6;'" : '';