From ec8bd31365d26ebcf79cbcdbadb5445d2940c685 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Tue, 27 Feb 2024 16:07:53 +0800 Subject: [PATCH] Improve webpack cache-busting version parameter by using file contents hash (#44838) * Update webpack config to use file content hash for chunks and generate asset php for styles * Use StyleAssetPlugin to generate style.asset.php * Remove unneed ?ver= code * Use file hash from asset file when SCRIPT_DEBUG is off - Use file hash to load scripts/styles ?ver= - Add register_style() method to WC_Admin_Assets * Load payment method promotions in admin_enqueue_scripts * Add changefile(s) from automation for the following project(s): @woocommerce/product-editor, woocommerce * Add json2php * Update doc * Update pnpm-lock.yaml * Fix add_print_shipping_label_script * Add a comment to style-asset-plugin.js * Change register_style to use WC_ADMIN_DIST_CSS_FOLDER * Reset the outputNormal object to avoid duplicate files * Fix type error --------- Co-authored-by: github-actions --- packages/js/internal-style-build/index.js | 5 +- packages/js/internal-style-build/package.json | 1 + .../style-asset-plugin.js | 219 ++++++++++++++++++ .../changelog/44838-dev-use-content-hash | 4 + packages/js/product-editor/webpack.config.js | 6 +- plugins/woocommerce-admin/client/index.js | 23 -- plugins/woocommerce-admin/unminify.js | 6 +- plugins/woocommerce-admin/webpack.config.js | 5 +- .../changelog/44838-dev-use-content-hash | 4 + .../includes/admin/class-wc-admin-assets.php | 2 +- .../src/Admin/Features/Features.php | 22 +- .../src/Admin/Features/Navigation/Init.php | 9 +- .../src/Internal/Admin/Coupons.php | 12 +- .../Internal/Admin/ShippingLabelBanner.php | 9 +- .../src/Internal/Admin/WCAdminAssets.php | 172 ++++++++------ .../Internal/Admin/WCPayPromotion/Init.php | 20 +- pnpm-lock.yaml | 4 +- tools/code-analyzer/src/lib/scan-changes.ts | 2 +- 18 files changed, 361 insertions(+), 164 deletions(-) create mode 100644 packages/js/internal-style-build/style-asset-plugin.js create mode 100644 packages/js/product-editor/changelog/44838-dev-use-content-hash create mode 100644 plugins/woocommerce/changelog/44838-dev-use-content-hash diff --git a/packages/js/internal-style-build/index.js b/packages/js/internal-style-build/index.js index a3f03feb3e0..48c32e9bd34 100644 --- a/packages/js/internal-style-build/index.js +++ b/packages/js/internal-style-build/index.js @@ -6,6 +6,7 @@ const path = require( 'path' ); const WebpackRTLPlugin = require( 'webpack-rtl-plugin' ); const RemoveEmptyScriptsPlugin = require( 'webpack-remove-empty-scripts' ); const postcssPlugins = require( '@wordpress/postcss-plugins-preset' ); +const StyleAssetPlugin = require( './style-asset-plugin' ); const NODE_ENV = process.env.NODE_ENV || 'development'; @@ -69,12 +70,14 @@ module.exports = { new RemoveEmptyScriptsPlugin(), new MiniCssExtractPlugin( { filename: '[name]/style.css', - chunkFilename: 'chunks/[id].style.css', + chunkFilename: 'chunks/[id].style.css?ver=[contenthash]', } ), new WebpackRTLPlugin( { filename: '[name]/style-rtl.css', minify: NODE_ENV === 'development' ? false : { safe: true }, } ), + new StyleAssetPlugin(), ], }, + StyleAssetPlugin, }; diff --git a/packages/js/internal-style-build/package.json b/packages/js/internal-style-build/package.json index df5ffaae73f..7a2ec4f165d 100644 --- a/packages/js/internal-style-build/package.json +++ b/packages/js/internal-style-build/package.json @@ -41,6 +41,7 @@ "@wordpress/base-styles": "wp-6.0", "@wordpress/postcss-plugins-preset": "wp-6.0", "css-loader": "^3.6.0", + "json2php": "^0.0.7", "mini-css-extract-plugin": "^2.7.6", "postcss-loader": "^4.3.0", "sass-loader": "^10.5.0", diff --git a/packages/js/internal-style-build/style-asset-plugin.js b/packages/js/internal-style-build/style-asset-plugin.js new file mode 100644 index 00000000000..12206785ca4 --- /dev/null +++ b/packages/js/internal-style-build/style-asset-plugin.js @@ -0,0 +1,219 @@ +/** + * Add an asset file for each entry point that contains the current version calculated for the current source code. + * + * This is modified from WP dependency-extraction-webpack-plugin plugin: + * https://github.com/WordPress/gutenberg/tree/a04a8e94e8b93ba60441c6534e21f4c3c26ff1bc/packages/dependency-extraction-webpack-plugin + * + * We can contribute this back to the original plugin in the future and remove this file. + */ + +/** + * External dependencies + */ +const path = require( 'path' ); +const webpack = require( 'webpack' ); +const json2php = require( 'json2php' ); +const { createHash } = webpack.util; + +const { RawSource } = webpack.sources; +const { AsyncDependenciesBlock } = webpack; + +class AssetDataPlugin { + constructor( options ) { + this.options = Object.assign( + { + combineAssets: false, + combinedOutputFile: null, + outputFormat: 'php', + outputFilename: null, + }, + options + ); + } + + /** + * @param {any} asset Asset Data + * @return {string} Stringified asset data suitable for output + */ + stringify( asset ) { + if ( this.options.outputFormat === 'php' ) { + return ` { + compilation.hooks.processAssets.tap( + { + name: this.constructor.name, + stage: compiler.webpack.Compilation + .PROCESS_ASSETS_STAGE_ANALYSE, + }, + () => this.addAssets( compilation ) + ); + } + ); + } + + /** @param {webpack.Compilation} compilation */ + addAssets( compilation ) { + const { + combineAssets, + combinedOutputFile, + outputFormat, + outputFilename, + } = this.options; + + const combinedAssetsData = {}; + + // Accumulate all entrypoint chunks, some of them shared + const entrypointChunks = new Set(); + for ( const entrypoint of compilation.entrypoints.values() ) { + for ( const chunk of entrypoint.chunks ) { + entrypointChunks.add( chunk ); + } + } + + // Process each entrypoint chunk independently + for ( const chunk of entrypointChunks ) { + const chunkFiles = Array.from( chunk.files ); + + const styleExtensionRegExp = /\.s?css$/i; + + const chunkStyleFile = chunkFiles.find( ( f ) => + styleExtensionRegExp.test( f ) + ); + if ( ! chunkStyleFile ) { + // No style file, skip + continue; + } + + // Go through the assets and hash the sources. We can't just use + // `chunk.contentHash` because that's not updated when + // assets are minified. In practice the hash is updated by + // `RealContentHashPlugin` after minification, but it only modifies + // already-produced asset filenames and the updated hash is not + // available to plugins. + const { hashFunction, hashDigest, hashDigestLength } = + compilation.outputOptions; + + const contentHash = chunkFiles + .sort() + .reduce( ( hash, filename ) => { + const asset = compilation.getAsset( filename ); + return hash.update( asset.source.buffer() ); + }, createHash( hashFunction ) ) + .digest( hashDigest ) + .slice( 0, hashDigestLength ); + + const assetData = { + version: contentHash, + }; + + if ( combineAssets ) { + combinedAssetsData[ chunkStyleFile ] = assetData; + continue; + } + + let assetFilename; + if ( outputFilename ) { + assetFilename = compilation.getPath( outputFilename, { + chunk, + filename: chunkStyleFile, + contentHash, + } ); + } else { + const suffix = + '.asset.' + ( outputFormat === 'php' ? 'php' : 'json' ); + assetFilename = compilation + .getPath( '[file]', { filename: chunkStyleFile } ) + .replace( styleExtensionRegExp, suffix ); + } + + // Add source and file into compilation for webpack to output. + compilation.assets[ assetFilename ] = new RawSource( + this.stringify( assetData ) + ); + chunk.files.add( assetFilename ); + } + + if ( combineAssets ) { + const outputFolder = compilation.outputOptions.path; + + const assetsFilePath = path.resolve( + outputFolder, + combinedOutputFile || + 'assets.' + ( outputFormat === 'php' ? 'php' : 'json' ) + ); + const assetsFilename = path.relative( + outputFolder, + assetsFilePath + ); + + // Add source into compilation for webpack to output. + compilation.assets[ assetsFilename ] = new RawSource( + this.stringify( combinedAssetsData ) + ); + } + } + + /** + * Can we trace a line of static dependencies from an entry to a module + * + * @param {webpack.Compilation} compilation + * @param {webpack.DependenciesBlock} block + * + * @return {boolean} True if there is a static import path to the root + */ + static hasStaticDependencyPathToRoot( compilation, block ) { + const incomingConnections = [ + ...compilation.moduleGraph.getIncomingConnections( block ), + ].filter( + ( connection ) => + // Library connections don't have a dependency, this is a root + connection.dependency && + // Entry dependencies are another root + connection.dependency.constructor.name !== 'EntryDependency' + ); + + // If we don't have non-entry, non-library incoming connections, + // we've reached a root of + if ( ! incomingConnections.length ) { + return true; + } + + const staticDependentModules = incomingConnections.flatMap( + ( connection ) => { + const { dependency } = connection; + const parentBlock = + compilation.moduleGraph.getParentBlock( dependency ); + + return parentBlock.constructor.name !== + AsyncDependenciesBlock.name + ? [ compilation.moduleGraph.getParentModule( dependency ) ] + : []; + } + ); + + // All the dependencies were Async, the module was reached via a dynamic import + if ( ! staticDependentModules.length ) { + return false; + } + + // Continue to explore any static dependencies + return staticDependentModules.some( ( parentStaticDependentModule ) => + AssetDataPlugin.hasStaticDependencyPathToRoot( + compilation, + parentStaticDependentModule + ) + ); + } +} + +module.exports = AssetDataPlugin; diff --git a/packages/js/product-editor/changelog/44838-dev-use-content-hash b/packages/js/product-editor/changelog/44838-dev-use-content-hash new file mode 100644 index 00000000000..22534758575 --- /dev/null +++ b/packages/js/product-editor/changelog/44838-dev-use-content-hash @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Improve webpack cache-busting version parameter by using file contents hash \ No newline at end of file diff --git a/packages/js/product-editor/webpack.config.js b/packages/js/product-editor/webpack.config.js index ae2cba9f015..655a6e91e73 100644 --- a/packages/js/product-editor/webpack.config.js +++ b/packages/js/product-editor/webpack.config.js @@ -10,7 +10,10 @@ const WebpackRTLPlugin = require( 'webpack-rtl-plugin' ); /** * Internal dependencies */ -const { webpackConfig } = require( '@woocommerce/internal-style-build' ); +const { + webpackConfig, + StyleAssetPlugin, +} = require( '@woocommerce/internal-style-build' ); const { blockEntryPoints, getBlockMetaData, @@ -69,5 +72,6 @@ module.exports = { }, ], } ), + new StyleAssetPlugin(), ], }; diff --git a/plugins/woocommerce-admin/client/index.js b/plugins/woocommerce-admin/client/index.js index 07a2190c324..61a92d3cd99 100644 --- a/plugins/woocommerce-admin/client/index.js +++ b/plugins/woocommerce-admin/client/index.js @@ -21,29 +21,6 @@ import { WcAdminConflictErrorSlot } from './settings/conflict-error-slotfill.js' import './xstate.js'; import { deriveWpAdminBackgroundColours } from './utils/derive-wp-admin-background-colours'; -// Modify webpack pubilcPath at runtime based on location of WordPress Plugin. -// eslint-disable-next-line no-undef,camelcase -__webpack_public_path__ = global.wcAdminAssets.path; - -// Modify webpack to append the ?ver parameter to JS chunk -// https://webpack.js.org/api/module-variables/#__webpack_get_script_filename__-webpack-specific -// eslint-disable-next-line no-undef,camelcase -const oldGetScriptFileNameFn = __webpack_get_script_filename__; -// eslint-disable-next-line no-undef,camelcase -__webpack_get_script_filename__ = ( chunk ) => { - const filename = oldGetScriptFileNameFn( chunk ); - return `${ filename }?ver=${ window.wcAdminAssets.version }`; -}; - -// Modify webpack to append the ?ver parameter to CSS chunk hrefs generated by mini-css-extract-plugin -// eslint-disable-next-line no-undef,camelcase -const oldMinCssFn = __webpack_require__.miniCssF; -// eslint-disable-next-line no-undef,camelcase -__webpack_require__.miniCssF = ( chunkId ) => { - const filename = oldMinCssFn( chunkId ); - return `${ filename }?ver=${ window.wcAdminAssets.version }`; -}; - const appRoot = document.getElementById( 'root' ); const embeddedRoot = document.getElementById( 'woocommerce-embedded-root' ); const settingsGroup = 'wc_admin'; diff --git a/plugins/woocommerce-admin/unminify.js b/plugins/woocommerce-admin/unminify.js index 760c3cae391..c80ed8f7b7c 100644 --- a/plugins/woocommerce-admin/unminify.js +++ b/plugins/woocommerce-admin/unminify.js @@ -8,7 +8,7 @@ * 2. Remove check for development mode - we always want unminified files. * 3. Remove BannerPlugin support - we don't use it. * 4. Remove the 'min' suffix from the chunk loaded in the new `mainEntry` option. - * 5. Hook into compilation later so we're running after Source Map generation. (https://webpack.js.org/api/compilation-hooks/: PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE) + * 5. Hook into compilation later so we're running after Source Map generation. (https://webpack.js.org/api/compilation-hooks/: PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE) */ const path = require( 'path' ); const ModuleFilenameHelpers = require( 'webpack/lib/ModuleFilenameHelpers' ); @@ -39,7 +39,7 @@ class UnminifyWebpackPlugin { apply( compiler ) { const options = this.options; - const outputNormal = {}; + let outputNormal = {}; compiler.hooks.compilation.tap( 'UnminifyWebpackPlugin', @@ -107,6 +107,8 @@ class UnminifyWebpackPlugin { value.filename, new webpack.sources.RawSource( value.content ) ); + // Reset the outputNormal object to avoid writing to file that only differs in casing or query string from already written file. + outputNormal = {}; } } ); diff --git a/plugins/woocommerce-admin/webpack.config.js b/plugins/woocommerce-admin/webpack.config.js index 75f97393962..7b46103ba93 100644 --- a/plugins/woocommerce-admin/webpack.config.js +++ b/plugins/woocommerce-admin/webpack.config.js @@ -101,7 +101,7 @@ const webpackConfig = { ? `wp-admin-scripts/[name]${ outputSuffix }.js` : `[name]/index${ outputSuffix }.js`; }, - chunkFilename: `chunks/[name]${ outputSuffix }.js`, + chunkFilename: `chunks/[name]${ outputSuffix }.js?ver=[contenthash]`, path: path.join( __dirname, '/build' ), library: { // Expose the exports of entry points so we can consume the libraries in window.wc.[modulename] with WooCommerceDependencyExtractionWebpackPlugin. @@ -200,7 +200,8 @@ const webpackConfig = { // The package build process doesn't handle extracting CSS from JS files, so we copy them separately. new CopyWebpackPlugin( { patterns: wcAdminPackages.map( ( packageName ) => ( { - from: `../../packages/js/${ packageName }/build-style/*.css`, + // Copy css and style.asset.php files. + from: `../../packages/js/${ packageName }/build-style/*.{css,php}`, to: `./${ packageName }/[name][ext]`, noErrorOnMissing: true, // Overwrites files already in compilation.assets to ensure we use the assets from the build-style. diff --git a/plugins/woocommerce/changelog/44838-dev-use-content-hash b/plugins/woocommerce/changelog/44838-dev-use-content-hash new file mode 100644 index 00000000000..22534758575 --- /dev/null +++ b/plugins/woocommerce/changelog/44838-dev-use-content-hash @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Improve webpack cache-busting version parameter by using file contents hash \ No newline at end of file diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php index f9d5e9f0076..fc2d933c93e 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-assets.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-assets.php @@ -586,7 +586,7 @@ if ( ! class_exists( 'WC_Admin_Assets', false ) ) : 'wc-admin-' . $script_name, WCAdminAssets::get_url( $script_path_name . '/' . $script_name, 'js' ), $script_assets['dependencies'], - WCAdminAssets::get_file_version( 'js' ), + WCAdminAssets::get_file_version( 'js', $script_assets['version'] ), true ); } diff --git a/plugins/woocommerce/src/Admin/Features/Features.php b/plugins/woocommerce/src/Admin/Features/Features.php index d522d630857..d09549657ac 100644 --- a/plugins/woocommerce/src/Admin/Features/Features.php +++ b/plugins/woocommerce/src/Admin/Features/Features.php @@ -81,7 +81,7 @@ class Features { * @return array */ public static function get_optional_feature_options() { - $features = []; + $features = array(); foreach ( array_keys( self::$optional_features ) as $optional_feature_key ) { $feature_class = self::get_feature_class( $optional_feature_key ); @@ -148,7 +148,7 @@ class Features { public static function get_available_features() { $features = self::get_features(); $optional_feature_keys = array_keys( self::$optional_features ); - $optional_features_unavailable = []; + $optional_features_unavailable = array(); /** * Filter allowing WooCommerce Admin optional features to be disabled. @@ -292,22 +292,8 @@ class Features { return; } - $rtl = is_rtl() ? '.rtl' : ''; - - wp_enqueue_style( - 'wc-admin-beta-features-tracking-modal', - WCAdminAssets::get_url( "beta-features-tracking-modal/style{$rtl}", 'css' ), - array( 'wp-components' ), - WCAdminAssets::get_file_version( 'css' ) - ); - - wp_enqueue_script( - 'wc-admin-beta-features-tracking-modal', - WCAdminAssets::get_url( 'wp-admin-scripts/beta-features-tracking-modal', 'js' ), - array( 'wp-i18n', 'wp-element', WC_ADMIN_APP ), - WCAdminAssets::get_file_version( 'js' ), - true - ); + WCAdminAssets::register_style( 'beta-features-tracking-modal', 'style', array( 'wp-components' ) ); + WCAdminAssets::register_script( 'wp-admin-scripts', 'beta-features-tracking-modal', array( 'wp-i18n', 'wp-element', WC_ADMIN_APP ) ); } /** diff --git a/plugins/woocommerce/src/Admin/Features/Navigation/Init.php b/plugins/woocommerce/src/Admin/Features/Navigation/Init.php index 0b832f24515..2d0cd85142e 100644 --- a/plugins/woocommerce/src/Admin/Features/Navigation/Init.php +++ b/plugins/woocommerce/src/Admin/Features/Navigation/Init.php @@ -123,14 +123,7 @@ class Init { return; } - $rtl = is_rtl() ? '.rtl' : ''; - wp_enqueue_style( - 'wc-admin-navigation-opt-out', - WCAdminAssets::get_url( "navigation-opt-out/style{$rtl}", 'css' ), - array( 'wp-components' ), - WCAdminAssets::get_file_version( 'css' ) - ); - + WCAdminAssets::register_style( 'navigation-opt-out', 'style', array( 'wp-components' ) ); WCAdminAssets::register_script( 'wp-admin-scripts', 'navigation-opt-out', true ); wp_localize_script( 'wc-admin-navigation-opt-out', diff --git a/plugins/woocommerce/src/Internal/Admin/Coupons.php b/plugins/woocommerce/src/Internal/Admin/Coupons.php index 4f0a78be69e..2d9649c7eec 100644 --- a/plugins/woocommerce/src/Internal/Admin/Coupons.php +++ b/plugins/woocommerce/src/Internal/Admin/Coupons.php @@ -73,7 +73,7 @@ class Coupons { __( 'Coupons', 'woocommerce' ), 'manage_options', 'coupons-moved', - [ $this, 'coupon_menu_moved' ] + array( $this, 'coupon_menu_moved' ) ); } @@ -117,15 +117,7 @@ class Coupons { return; } - $rtl = is_rtl() ? '-rtl' : ''; - - wp_enqueue_style( - 'wc-admin-marketing-coupons', - WCAdminAssets::get_url( "marketing-coupons/style{$rtl}", 'css' ), - array(), - WCAdminAssets::get_file_version( 'css' ) - ); - + WCAdminAssets::register_style( 'marketing-coupons', 'style' ); WCAdminAssets::register_script( 'wp-admin-scripts', 'marketing-coupons', true ); } } diff --git a/plugins/woocommerce/src/Internal/Admin/ShippingLabelBanner.php b/plugins/woocommerce/src/Internal/Admin/ShippingLabelBanner.php index c3bf383d86f..ff3f2916528 100644 --- a/plugins/woocommerce/src/Internal/Admin/ShippingLabelBanner.php +++ b/plugins/woocommerce/src/Internal/Admin/ShippingLabelBanner.php @@ -128,14 +128,7 @@ class ShippingLabelBanner { * @param string $hook current page hook. */ public function add_print_shipping_label_script( $hook ) { - $rtl = is_rtl() ? '.rtl' : ''; - wp_enqueue_style( - 'print-shipping-label-banner-style', - WCAdminAssets::get_url( "print-shipping-label-banner/style{$rtl}", 'css' ), - array( 'wp-components' ), - WCAdminAssets::get_file_version( 'css' ) - ); - + WCAdminAssets::register_style( 'print-shipping-label-banner', 'style', array( 'wp-components' ) ); WCAdminAssets::register_script( 'wp-admin-scripts', 'print-shipping-label-banner', true ); $payload = array( diff --git a/plugins/woocommerce/src/Internal/Admin/WCAdminAssets.php b/plugins/woocommerce/src/Internal/Admin/WCAdminAssets.php index db7435d9e60..bbdb013d547 100644 --- a/plugins/woocommerce/src/Internal/Admin/WCAdminAssets.php +++ b/plugins/woocommerce/src/Internal/Admin/WCAdminAssets.php @@ -98,15 +98,22 @@ class WCAdminAssets { } /** - * Gets the file modified time as a cache buster if we're in dev mode, or the plugin version otherwise. + * Gets the file modified time as a cache buster if we're in dev mode, + * or the asset version (file content hash) if exists, or the WooCommerce version. * - * @param string $ext File extension. + * @param string $ext File extension. + * @param string|null $asset_version Optional. The version from the asset file. * @return string The cache buster value to use for the given file. */ - public static function get_file_version( $ext ) { + public static function get_file_version( $ext, $asset_version = null ) { if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { return filemtime( WC_ADMIN_ABSPATH . self::get_path( $ext ) ); } + + if ( ! empty( $asset_version ) ) { + return $asset_version; + } + return WC_VERSION; } @@ -253,9 +260,7 @@ class WCAdminAssets { return; } - $js_file_version = self::get_file_version( 'js' ); - $css_file_version = self::get_file_version( 'css' ); - + // Register the JS scripts. $scripts = array( 'wc-admin-layout', 'wc-explat', @@ -299,6 +304,7 @@ class WCAdminAssets { try { $script_assets_filename = self::get_script_asset_filename( $script_path_name, 'index' ); $script_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . $script_path_name . '/' . $script_assets_filename; + $script_version = self::get_file_version( 'js', $script_assets['version'] ); global $wp_version; if ( 'app' === $script_path_name && version_compare( $wp_version, '6.3', '<' ) ) { @@ -320,92 +326,83 @@ class WCAdminAssets { wp_register_script( $script, self::get_url( $script_path_name . '/index', 'js' ), - $script_assets ['dependencies'], - $js_file_version, + $script_assets['dependencies'], + $script_version, true ); if ( in_array( $script, $translated_scripts, true ) ) { wp_set_script_translations( $script, 'woocommerce' ); } + + if ( WC_ADMIN_APP === $script ) { + wp_localize_script( + WC_ADMIN_APP, + 'wcAdminAssets', + array( + 'path' => plugins_url( self::get_path( 'js' ), WC_ADMIN_PLUGIN_FILE ), + 'version' => $script_version, + ) + ); + } } catch ( \Exception $e ) { // Avoid crashing WordPress if an asset file could not be loaded. wc_caught_exception( $e, __CLASS__ . '::' . __FUNCTION__, $script_path_name ); } } - wp_register_style( - 'wc-admin-layout', - self::get_url( 'admin-layout/style', 'css' ), - array(), - $css_file_version - ); - wp_style_add_data( 'wc-admin-layout', 'rtl', 'replace' ); - - wp_register_style( - 'wc-components', - self::get_url( 'components/style', 'css' ), - array(), - $css_file_version - ); - wp_style_add_data( 'wc-components', 'rtl', 'replace' ); - - wp_register_style( - 'wc-block-templates', - self::get_url( 'block-templates/style', 'css' ), - array(), - $css_file_version - ); - wp_style_add_data( 'wc-block-templates', 'rtl', 'replace' ); - - wp_register_style( - 'wc-product-editor', - self::get_url( 'product-editor/style', 'css' ), - array(), - $css_file_version - ); - wp_style_add_data( 'wc-product-editor', 'rtl', 'replace' ); - - wp_register_style( - 'wc-customer-effort-score', - self::get_url( 'customer-effort-score/style', 'css' ), - array(), - $css_file_version - ); - wp_style_add_data( 'wc-customer-effort-score', 'rtl', 'replace' ); - - wp_register_style( - 'wc-experimental', - self::get_url( 'experimental/style', 'css' ), - array(), - $css_file_version - ); - wp_style_add_data( 'wc-experimental', 'rtl', 'replace' ); - - wp_localize_script( - WC_ADMIN_APP, - 'wcAdminAssets', + // Register the CSS styles. + $styles = array( array( - 'path' => plugins_url( self::get_path( 'js' ), WC_ADMIN_PLUGIN_FILE ), - 'version' => $js_file_version, - ) + 'handle' => 'wc-admin-layout', + ), + array( + 'handle' => 'wc-components', + ), + array( + 'handle' => 'wc-block-templates', + ), + array( + 'handle' => 'wc-product-editor', + ), + array( + 'handle' => 'wc-customer-effort-score', + ), + array( + 'handle' => 'wc-experimental', + ), + array( + 'handle' => WC_ADMIN_APP, + 'dependencies' => array( 'wc-components', 'wc-admin-layout', 'wc-customer-effort-score', 'wc-product-editor', 'wp-components', 'wc-experimental' ), + ), + array( + 'handle' => 'wc-onboarding', + ), ); - wp_register_style( - WC_ADMIN_APP, - self::get_url( 'app/style', 'css' ), - array( 'wc-components', 'wc-admin-layout', 'wc-customer-effort-score', 'wc-product-editor', 'wp-components', 'wc-experimental' ), - $css_file_version - ); - wp_style_add_data( WC_ADMIN_APP, 'rtl', 'replace' ); + $css_file_version = self::get_file_version( 'css' ); + foreach ( $styles as $style ) { + $handle = $style['handle']; + $style_path_name = isset( $scripts_map[ $handle ] ) ? $scripts_map[ $handle ] : str_replace( 'wc-', '', $handle ); - wp_register_style( - 'wc-onboarding', - self::get_url( 'onboarding/style', 'css' ), - array(), - $css_file_version - ); - wp_style_add_data( 'wc-onboarding', 'rtl', 'replace' ); + try { + $style_assets_filename = self::get_script_asset_filename( $style_path_name, 'style' ); + $style_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_JS_FOLDER . $style_path_name . '/' . $style_assets_filename; + $version = $style_assets['version']; + } catch ( \Throwable $e ) { + // Use the default version if the asset file could not be loaded. + $version = $css_file_version; + } + + $dependencies = isset( $style['dependencies'] ) ? $style['dependencies'] : array(); + wp_register_style( + $handle, + self::get_url( $style_path_name . '/style', 'css' ), + $dependencies, + self::get_file_version( 'css', $version ), + ); + wp_style_add_data( $handle, 'rtl', 'replace' ); + } } /** @@ -475,11 +472,32 @@ class WCAdminAssets { 'wc-admin-' . $script_name, self::get_url( $script_path_name . '/' . $script_name, 'js' ), array_merge( array( WC_ADMIN_APP ), $script_assets ['dependencies'], $dependencies ), - self::get_file_version( 'js' ), + self::get_file_version( 'js', $script_assets['version'] ), true ); if ( $need_translation ) { wp_set_script_translations( 'wc-admin-' . $script_name, 'woocommerce' ); } } + + /** + * Loads a style + * + * @param string $style_path_name The style path name. + * @param string $style_name Filename of the style to load. + * @param array $dependencies Array of any extra dependencies. + */ + public static function register_style( $style_path_name, $style_name, $dependencies = array() ) { + $style_assets_filename = self::get_script_asset_filename( $style_path_name, $style_name ); + $style_assets = require WC_ADMIN_ABSPATH . WC_ADMIN_DIST_CSS_FOLDER . $style_path_name . '/' . $style_assets_filename; + + $handle = 'wc-admin-' . $style_name; + wp_enqueue_style( + $handle, + self::get_url( $style_path_name . '/' . $style_name, 'css' ), + $dependencies, + self::get_file_version( 'css', $style_assets['version'] ), + ); + wp_style_add_data( $handle, 'rtl', 'replace' ); + } } diff --git a/plugins/woocommerce/src/Internal/Admin/WCPayPromotion/Init.php b/plugins/woocommerce/src/Internal/Admin/WCPayPromotion/Init.php index 0664ba18942..7d4f5c21819 100644 --- a/plugins/woocommerce/src/Internal/Admin/WCPayPromotion/Init.php +++ b/plugins/woocommerce/src/Internal/Admin/WCPayPromotion/Init.php @@ -31,17 +31,7 @@ class Init extends RemoteSpecsEngine { add_filter( 'woocommerce_payment_gateways', array( __CLASS__, 'possibly_register_pre_install_wc_pay_promotion_gateway' ) ); add_filter( 'option_woocommerce_gateway_order', array( __CLASS__, 'set_gateway_top_of_list' ) ); add_filter( 'default_option_woocommerce_gateway_order', array( __CLASS__, 'set_gateway_top_of_list' ) ); - - $rtl = is_rtl() ? '.rtl' : ''; - - wp_enqueue_style( - 'wc-admin-payment-method-promotions', - WCAdminAssets::get_url( "payment-method-promotions/style{$rtl}", 'css' ), - array( 'wp-components' ), - WCAdminAssets::get_file_version( 'css' ) - ); - - WCAdminAssets::register_script( 'wp-admin-scripts', 'payment-method-promotions', true ); + add_action( 'admin_enqueue_scripts', array( __CLASS__, 'load_payment_method_promotions' ) ); } /** @@ -161,5 +151,13 @@ class Init extends RemoteSpecsEngine { } return WCPayPromotionDataSourcePoller::get_instance()->get_specs_from_data_sources(); } + + /** + * Loads the payment method promotions scripts and styles. + */ + public static function load_payment_method_promotions() { + WCAdminAssets::register_style( 'payment-method-promotions', 'style', array( 'wp-components' ) ); + WCAdminAssets::register_script( 'wp-admin-scripts', 'payment-method-promotions', true ); + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f37931a483b..82b49978bc6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2163,6 +2163,9 @@ importers: css-loader: specifier: ^3.6.0 version: 3.6.0(webpack@5.89.0) + json2php: + specifier: ^0.0.7 + version: 0.0.7 mini-css-extract-plugin: specifier: ^2.7.6 version: 2.7.6(webpack@5.89.0) @@ -39280,7 +39283,6 @@ packages: /json2php@0.0.7: resolution: {integrity: sha512-dnSoUiLAoVaMXxFsVi4CrPVYMKOuDBXTghXSmMINX44RZ8WM9cXlY7UqrQnlAcODCVO7FV3+8t/5nDKAjimLfg==} - dev: true /json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} diff --git a/tools/code-analyzer/src/lib/scan-changes.ts b/tools/code-analyzer/src/lib/scan-changes.ts index 40235036253..bb1c60dff3e 100644 --- a/tools/code-analyzer/src/lib/scan-changes.ts +++ b/tools/code-analyzer/src/lib/scan-changes.ts @@ -116,7 +116,7 @@ export const scanForChanges = async ( base: string, outputStyle: 'cli' | 'github', clonedPath?: string, - exclude?: string[] + exclude: string[] = [] ) => { Logger.startTask( `Making temporary clone of ${ source }...` );