Repository: Automattic/jetpack (PHP)

Issue:
CSS Concatenating: jetpack.css loads all styles, even if they've been manually dequeued
As of this weekend, I'm now seeing `jetpack.css` loading on the front-end, even though the only feature I'm using in Markdown. This file contains CSS for all Jetpack features.

I'm currently overriding this behavior using this snippet in `functions.php`, but this feels like it may be a bug:

``` php
add_filter( 'jetpack_implode_frontend_css', '__return_false' );
```

More info: https://www.twirlingumbrellas.com/wordpress/remove-deregister-jetpack-contact-form-styles/



Discussion: Yes, that's a known issue with Jetpack 3.2. Jetpack now concatenates its CSS into a `jetpack.css` file, overwriting any custom changes you may have made by manually dequeuing one of Jetpack's CSS files. We'll get this fixed.

You can follow our progress in #1235. I'll close this issue as it's a duplicate.

@jeherve - Thanks Jeremy. Didn't catch that when I opened the ticket. Cheers!

Reopening this since #1235 was closed when fixing the CSS specificity issue. This bug is regarding how we still include all stylesheets in `jetpack.css`, even when that stylesheet has been manually dequeued.

Is there a core action we can track for when a stylesheet is dequeued?

Can we track all `wp_dequeue_style` that may be hooked into `wp_enqueue_scripts`?

Just checked, there isn't an action that does this.

Also reported here:
https://wordpress.org/support/topic/too-much-css-loading/
Related suggestion:

> Concatenated jetpack.css should only include CSS for active modules

#7656
<p>This issue has been marked as stale. This happened because:</p>
<ul>
  <li>It has been inactive in the past 6 months.</li>
  <li>It hasn’t been labeled `[Pri] Blocker`, `[Pri] High`.</li>
</ul>
<p>No further action is needed. But it's worth checking if this ticket has clear reproduction steps and it is still reproducible. Feel free to close this issue if you think it's not valid anymore — if you do, please add a brief explanation.</p>

<p>This issue has been marked as stale. This happened because:</p>
<ul>
  <li>It has been inactive in the past 6 months.</li>
  <li>It hasn’t been labeled `[Pri] Blocker`, `[Pri] High`.</li>
</ul>
<p>No further action is needed. But it's worth checking if this ticket has clear reproduction steps and it is still reproducible. Feel free to close this issue if you think it's not valid anymore — if you do, please add a brief explanation.</p>

This was brought up again in #11123.
<p>This issue has been marked as stale. This happened because:</p>
<ul>
  <li>It has been inactive in the past 6 months.</li>
  <li>It hasn’t been labeled `[Pri] Blocker`, `[Pri] High`.</li>
</ul>
<p>No further action is needed. But it's worth checking if this ticket has clear reproduction steps and it is still reproducible. Feel free to close this issue if you think it's not valid anymore — if you do, please add a brief explanation.</p>

Reopening this, as it is still a valid enhancement.
I came across this thread after a google search led me to [issue #15734](https://github.com/Automattic/jetpack/issues/15734), which is closed because it's basically a duplicate of this issue.

I'm glad this is still open because it is a valid enhancement, what I am working on now to address it is [adding a filter to disable the concatenation](https://developer.jetpack.com/hooks/jetpack_implode_frontend_css/) and then using the "Asset CleanUp" plugin to disable the files I don't need and letting my caching plugin concatenate everything else into as few files as possible.

Hopefully others will find this approach helpful.
<p>This issue has been marked as stale. This happened because:</p>

<ul>
  <li>It has been inactive in the past 6 months.</li>
  <li>It hasn’t been labeled `[Pri] Blocker`, `[Pri] High`, etc.</li>
</ul>

<p>No further action is needed. But it's worth checking if this ticket has clear
reproduction steps and it is still reproducible. Feel free to close this issue
if you think it's not valid anymore — if you do, please add a brief
explanation.</p>
This was also brought up in [this post](https://felix-arntz.me/blog/rebuilding-my-website-using-a-block-theme/):

> The only feature of Jetpack that I use in the frontend of my site is the Subscribe block that you can see in my site footer. There’s no way that simple text input and button needs 21.5 KB of CSS.

As we had discussed in #7656, we should try to find a way to not include everything in the concatenated file if it is not needed.
I'd like to come back to this issue and summarize the conversations above a bit:

We currently use [this](https://github.com/Automattic/jetpack/blob/9721658f081d51c97efdc79cc0940c34d135459a/projects/plugins/jetpack/class.jetpack.php#L6151-L6179) to [enqueue a concatenated](https://github.com/Automattic/jetpack/blob/9721658f081d51c97efdc79cc0940c34d135459a/projects/plugins/jetpack/class.jetpack.php#L774-L783) `jetpack.css` file on all frontend views when Jetpack is connected to WordPress.com (as long as you do not use the filter to disable the feature nor have `SCRIPT_DEBUG` set on your site).

That `jetpack.css` file is built off [a list of stylesheets](https://github.com/Automattic/jetpack/blob/9721658f081d51c97efdc79cc0940c34d135459a/projects/plugins/jetpack/class.jetpack.php#L77-L115) from individual modules that are [not enqueued](https://github.com/Automattic/jetpack/blob/9721658f081d51c97efdc79cc0940c34d135459a/projects/plugins/jetpack/class.jetpack.php#L6237-L6245) when the concatenated file is enqueued.

This has a few inconvenients:

1. It doesn't take module status into account; you end up with a full `jetpack.css` even if you don't actually need any of the styles. In that case, not actually doing the whole concatenation would be better. With the advent of http/2, we may also ask ourselves if concatenation is even needed at all anymore.
2. That list of stylesheets also doesn't take stylesheets added by blocks into account. Those are [added individually, only when needed](https://github.com/Automattic/jetpack/blob/0bf40c77c6748e5a57bd6367f59998c728dfbd3e/projects/plugins/jetpack/class.jetpack-gutenberg.php#L501-L510) (when the block is present on the page), and with their potential dependencies. This may not have to be modified right now.

Also of note, this is only about CSS. We do not have anything similar for the different JavaScript files needed by different Jetpack modules.

I'll cc @Automattic/heart-of-gold on this conversation, since they may have some useful input on performance here. 
> With the advent of http/2, we may also ask ourselves if concatenation is even needed at all anymore.

This is pretty key, IMO. There's no benefit to end-users when HTTP/2 is enabled, and it can often be worse than just loading the CSS that's needed.
> This is pretty key, IMO. There's no benefit to end-users when HTTP/2 is enabled, and it can often be worse than just loading the CSS that's needed.

It depends. Concatenating resources can still assist with improving compression, as compressing similar text patterns together is more efficient than separately. Based on a quick test, Brotli compression brings jetpack.css down from 99.7 kB to 17.1 kB.

Reviewing the contents of the uncompressed 99.7 kB, the big parts appear to be:

- 18.3 kB for carousel.
- 16.5 kB for sharedaddy.
- 16.2 kB for contact forms.
- 12.2 kB for swiper.
- 6.0 kB for related posts.
- The remaining ~30 kB is comprised of many files smaller than 5 kB each.

My gut feeling is that it's worth splitting out the bigger items that aren't always needed into their own files (e.g.: carousel), but it's better to keep the tiny files and the non-optional files combined. It's hard to estimate exactly how much of the compressed size would be saved by removing things like carousel, but we can expect at least a few kB savings there, which makes it worthwhile IMHO.

But we should probably test actual compressed sizes with different bundling to verify my gut feeling. Compression is a little hard to predict without testing. :)
> Brotli compression brings jetpack.css down from 99.7 kB to 17.1 kB

17.1kb is not an insignificant amount of code, especially on slower connections and older devices.

> it's better to keep the tiny files and the non-optional files combined

How would you define "non-optional"? I don't use Jetpack for a single front-end feature.
One specific case that we could handle is when no feature using jetpack.css on the front end is activated. Then we should not load it at all.



Fix (patch):
diff --git a/projects/plugins/jetpack/.phan/baseline.php b/projects/plugins/jetpack/.phan/baseline.php
index 61acd515a45fc..9835c9f32cc96 100644
--- a/projects/plugins/jetpack/.phan/baseline.php
+++ b/projects/plugins/jetpack/.phan/baseline.php
@@ -88,7 +88,6 @@
     // PhanPluginInvalidPregRegex : 1 occurrence
     // PhanPluginUseReturnValueInternalKnown : 1 occurrence
     // PhanTypeConversionFromArray : 1 occurrence
-    // PhanTypeVoidArgument : 1 occurrence
     // PhanUndeclaredConstant : 1 occurrence
     // PhanUndeclaredExtendedClass : 1 occurrence
     // PhanUndeclaredTypeReturnType : 1 occurrence
@@ -509,7 +508,7 @@
         'tests/php/general/test_class.jetpack-client-server.php' => ['PhanDeprecatedFunction', 'PhanTypeMismatchArgumentProbablyReal'],
         'tests/php/general/test_class.jetpack-user-agent.php' => ['PhanDeprecatedFunction'],
         'tests/php/general/test_class.jetpack-xmlrpc-server.php' => ['PhanDeprecatedFunction', 'PhanPluginSimplifyExpressionBool', 'PhanRedundantCondition'],
-        'tests/php/general/test_class.jetpack.php' => ['PhanPluginDuplicateAdjacentStatement', 'PhanTypeMismatchPropertyDefault', 'PhanTypeVoidArgument'],
+        'tests/php/general/test_class.jetpack.php' => ['PhanPluginDuplicateAdjacentStatement', 'PhanTypeMismatchPropertyDefault'],
         'tests/php/json-api/test-class.json-api-jetpack-endpoints.php' => ['PhanTypeMismatchArgumentProbablyReal'],
         'tests/php/json-api/test-class.json-api-jetpack-site-endpoints.php' => ['PhanImpossibleConditionInLoop'],
         'tests/php/json-api/test-class.json-api-post-jetpack.php' => ['PhanTypeMismatchArgument'],
diff --git a/projects/plugins/jetpack/changelog/remove-imploding-css-code b/projects/plugins/jetpack/changelog/remove-imploding-css-code
new file mode 100644
index 0000000000000..75864f8829492
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/remove-imploding-css-code
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+Removed the option to use a combined CSS file for Jetpack features. Previous to Jetpack 13.9, this was the default behavior. In Jetpack 13.9, it was made optional to rely on individual CSS files for the features used on each page to improve page loading.
diff --git a/projects/plugins/jetpack/class.jetpack.php b/projects/plugins/jetpack/class.jetpack.php
index 056a96db73f01..8cf17c78fa1a0 100644
--- a/projects/plugins/jetpack/class.jetpack.php
+++ b/projects/plugins/jetpack/class.jetpack.php
@@ -75,73 +75,6 @@ class Jetpack {
 	 */
 	public $xmlrpc_server = null;
 
-	/**
-	 * List of Jetpack modules that have CSS that gets concatenated into jetpack.css.
-	 *
-	 * See $concatenated_style_handles for the list of handles,
-	 * and the implode_frontend_css method for more details.
-	 *
-	 * When updating this list, make sure to update $concatenated_style_handles as well.
-	 *
-	 * @var array List of Jetpack modules.
-	 */
-	public $modules_with_concatenated_css = array(
-		'carousel',
-		'contact-form',
-		'infinite-scroll',
-		'likes',
-		'related-posts',
-		'sharedaddy',
-		'shortcodes',
-		'subscriptions',
-		'tiled-gallery',
-		'widgets',
-	);
-
-	/**
-	 * The handles of styles that are concatenated into jetpack.css.
-	 *
-	 * When making changes to that list,
-	 * you must also update concat_list in tools/webpack.config.css.js,
-	 * and to $modules_with_concatenated_css if necessary.
-	 *
-	 * @var array The handles of styles that are concatenated into jetpack.css.
-	 */
-	public $concatenated_style_handles = array(
-		'jetpack-carousel-swiper-css',
-		'jetpack-carousel',
-		'grunion.css',
-		'the-neverending-homepage',
-		'jetpack_likes',
-		'jetpack_related-posts',
-		'sharedaddy',
-		'jetpack-slideshow',
-		'presentations',
-		'quiz',
-		'jetpack-subscriptions',
-		'jetpack-responsive-videos',
-		'jetpack-social-menu',
-		'tiled-gallery',
-		'jetpack_display_posts_widget',
-		'gravatar-profile-widget',
-		'goodreads-widget',
-		'jetpack_social_media_icons_widget',
-		'jetpack-top-posts-widget',
-		'jetpack_image_widget',
-		'jetpack-my-community-widget',
-		'jetpack-authors-widget',
-		'wordads',
-		'eu-cookie-law-style',
-		'flickr-widget-style',
-		'jetpack-search-widget',
-		'jetpack-simple-payments-widget-style',
-		'jetpack-widget-social-icons-styles',
-		'wpcom_instagram_widget',
-		'milestone-widget',
-		'subscribe-modal-css',
-		'subscribe-overlay-css',
-	);
-
 	/**
 	 * Contains all assets that have had their URL rewritten to minified versions.
 	 *
@@ -763,17 +696,6 @@ function () {
 		// Update the site's Jetpack plan and products from API on heartbeats.
 		add_action( 'jetpack_heartbeat', array( Jetpack_Plan::class, 'refresh_from_wpcom' ) );
 
-		/**
-		 * This is the hack to concatenate all css files into one.
-		 * For description and reasoning see the implode_frontend_css method.
-		 *
-		 * Super late priority so we catch all the registered styles.
-		 */
-		if ( ! is_admin() ) {
-			add_action( 'wp_print_styles', array( $this, 'implode_frontend_css' ), -1 ); // Run first.
-			add_action( 'wp_print_footer_scripts', array( $this, 'implode_frontend_css' ), -1 ); // Run first to trigger before `print_late_styles`.
-		}
-
 		// Actually push the stats on shutdown.
 		if ( ! has_action( 'shutdown', array( $this, 'push_stats' ) ) ) {
 			add_action( 'shutdown', array( $this, 'push_stats' ) );
@@ -5499,6 +5421,8 @@ public function deprecated_hooks() {
 				'replacement' => null,
 				'version'     => 'jetpack-13.4.0',
 			),
+			// jetpack_implode_frontend_css has been removed, but is not listed here. The updated behavior is exactly the only use of the filter.
+			// We can reassess formally deprecating it here later; for now, it would be noise with no functional difference.
 		);
 
 		foreach ( $filter_deprecated_list as $tag => $args ) {
@@ -5630,127 +5554,6 @@ public static function absolutize_css_urls( $css, $css_file_url ) {
 		return $css;
 	}
 
-	/**
-	 * This methods removes all of the registered css files on the front end
-	 * from Jetpack in favor of using a single file. In effect "imploding"
-	 * all the files into one file.
-	 *
-	 * Pros:
-	 * - Uses only ONE css asset connection instead of 15
-	 * - Saves a minimum of 56k
-	 * - Reduces server load
-	 * - Reduces time to first painted byte
-	 *
-	 * Cons:
-	 * - Loads css for ALL modules. However all selectors are prefixed so it
-	 *      should not cause any issues with themes.
-	 * - Plugins/themes dequeuing styles no longer do anything. See
-	 *      jetpack_implode_frontend_css filter for a workaround
-	 *
-	 * For some situations developers may wish to disable css imploding and
-	 * instead operate in legacy mode where each file loads seperately and
-	 * can be edited individually or dequeued. This can be accomplished with
-	 * the following line:
-	 *
-	 * add_filter( 'jetpack_implode_frontend_css', '__return_false' );
-	 *
-	 * @param bool $travis_test Is this a test run.
-	 *
-	 * @since 3.2
-	 * @since 13.9 Default to not imploding. Requires a filter to enable. This may be temporary before dropping completely.
-	 */
-	public function implode_frontend_css( $travis_test = false ) {
-		$do_implode = false;
-		if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
-			// phpcs:ignore Squiz.PHP.CommentedOutCode.Found
-			// $do_implode = false;
-		}
-
-		// Do not implode CSS when the page loads via the AMP plugin.
-		if ( class_exists( Jetpack_AMP_Support::class ) && Jetpack_AMP_Support::is_amp_request() ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
-			// phpcs:ignore Squiz.PHP.CommentedOutCode.Found
-			// $do_implode = false;
-		}
-
-		/*
-		 * Only proceed if at least 2 modules with concatenated CSS are active.
-		 * There is no point in serving a big concatenated CSS file
-		 * if there are no features (or only one) that actually need some CSS loaded.
-		 */
-		$active_modules                = self::get_active_modules();
-		$modules_with_concatenated_css = $this->modules_with_concatenated_css;
-		$active_module_with_css_count  = count( array_intersect( $active_modules, $modules_with_concatenated_css ) );
-		if ( $active_module_with_css_count < 2 ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
-			// phpcs:ignore Squiz.PHP.CommentedOutCode.Found
-			// $do_implode = false;
-		}
-
-		/**
-		 * Allow CSS to be concatenated into a single jetpack.css file.
-		 *
-		 * @since 3.2.0
-		 *
-		 * @param bool $do_implode Should CSS be concatenated? Default to true.
-		 */
-		$do_implode = apply_filters( 'jetpack_implode_frontend_css', $do_implode );
-
-		// Do not use the imploded file when default behavior was altered through the filter.
-		if ( ! $do_implode ) {
-			return;
-		}
-
-		// We do not want to use the imploded file in dev mode, or if not connected.
-		if ( ( new Status() )->is_offline_mode() || ! self::is_connection_ready() ) {
-			if ( ! $travis_test ) {
-				return;
-			}
-		}
-
-		// Do not use the imploded file if sharing css was dequeued via the sharing settings screen.
-		if ( get_option( 'sharedaddy_disable_resources' ) ) {
-			return;
-		}
-
-		/*
-		 * Now we assume Jetpack is connected and able to serve the single
-		 * file.
-		 *
-		 * In the future there will be a check here to serve the file locally
-		 * or potentially from the Jetpack CDN
-		 *
-		 * For now:
-		 * - Enqueue a single imploded css file
-		 * - Zero out the style_loader_tag for the bundled ones
-		 * - Be happy, drink scotch
-		 */
-
-		add_filter( 'style_loader_tag', array( $this, 'concat_remove_style_loader_tag' ), 10, 2 );
-
-		$version = self::is_development_version() ? filemtime( JETPACK__PLUGIN_DIR . 'css/jetpack.css' ) : JETPACK__VERSION;
-
-		wp_enqueue_style( 'jetpack_css', plugins_url( 'css/jetpack.css', __FILE__ ), array(), $version );
-		wp_style_add_data( 'jetpack_css', 'rtl', 'replace' );
-	}
-
-	/**
-	 * Removes styles that are part of concatenated group.
-	 *
-	 * @param string $tag Style tag.
-	 * @param string $handle Style handle.
-	 *
-	 * @return string
-	 */
-	public function concat_remove_style_loader_tag( $tag, $handle ) {
-		if ( in_array( $handle, $this->concatenated_style_handles, true ) ) {
-			$tag = '';
-			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
-				$tag = '<!-- `' . esc_html( $handle ) . "` is included in the concatenated jetpack.css -->\r\n";
-			}
-		}
-
-		return $tag;
-	}
-
 	/**
 	 * Check the heartbeat data
 	 *
diff --git a/projects/plugins/jetpack/tools/webpack.config.css.js b/projects/plugins/jetpack/tools/webpack.config.css.js
index fb0bfd88d6a88..378a743710750 100644
--- a/projects/plugins/jetpack/tools/webpack.config.css.js
+++ b/projects/plugins/jetpack/tools/webpack.config.css.js
@@ -118,43 +118,7 @@ const entries = {
 };
 
 // CSS that needs to have the rtl files renamed using the above RenamerPlugin.
-const weirdRtlEntries = {
-	'css/jetpack': [
-		// When making changes to that list, you must also update $concatenated_style_handles in class.jetpack.php.
-		'modules/carousel/swiper-bundle.css',
-		'modules/carousel/jetpack-carousel.css',
-		'jetpack_vendor/automattic/jetpack-forms/src/contact-form/css/grunion.css',
-		'modules/infinite-scroll/infinity.css',
-		'modules/likes/style.css',
-		'modules/related-posts/related-posts.css',
-		'modules/sharedaddy/sharing.css',
-		'modules/shortcodes/css/slideshow-shortcode.css',
-		'modules/shortcodes/css/style.css', // TODO: Should be renamed to shortcode-presentations
-		'modules/shortcodes/css/quiz.css',
-		'modules/subscriptions/subscriptions.css',
-		'modules/theme-tools/responsive-videos/responsive-videos.css',
-		'modules/theme-tools/social-menu/social-menu.css',
-		'modules/tiled-gallery/tiled-gallery/tiled-gallery.css',
-		'modules/widgets/wordpress-post-widget/style.css',
-		'modules/widgets/gravatar-profile.css',
-		'modules/widgets/goodreads/css/goodreads.css',
-		'modules/widgets/social-media-icons/style.css',
-		'modules/widgets/top-posts/style.css',
-		'modules/widgets/image-widget/style.css',
-		'modules/widgets/my-community/style.css',
-		'modules/widgets/authors/style.css',
-		'modules/wordads/css/style.css',
-		'modules/widgets/eu-cookie-law/style.css',
-		'modules/widgets/flickr/style.css',
-		'modules/widgets/instagram/instagram.css',
-		'jetpack_vendor/automattic/jetpack-search/src/widgets/css/search-widget-frontend.css',
-		'modules/widgets/simple-payments/style.css',
-		'modules/widgets/social-icons/social-icons.css',
-		'modules/widgets/milestone/milestone-widget.css',
-		'modules/subscriptions/subscribe-modal/subscribe-modal.css',
-		'modules/subscriptions/subscribe-overlay/subscribe-overlay.css',
-	].map( n => path.join( __dirname, '..', n ) ),
-};
+const weirdRtlEntries = {};
 
 // Non-minified CSS, that also needs to have the rtl files renamed using the above RenamerPlugin.
 const weirdRtlNominEntries = {};