Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Boost: refactor Critical CSS: Part 1 #22163

Merged
merged 121 commits into from
Jan 31, 2022
Merged

Conversation

pyronaur
Copy link
Member

@pyronaur pyronaur commented Dec 22, 2021

Refactoring the Critical CSS class without modifying the plugin behavior.

It's going to take some time to fully refactor Critical CSS, but this is at least a good first step.

Fixes #21725

Changes in this pull request:

  • The following functionalities are moved to separate classes/files:
    • Critical CSS generation management-related logic.
    • Site path providers.
    • All Critical CSS REST API logic.
    • Critical CSS display in the frontend.
    • Recommendations dismissal and reset.
    • AMP compatibility is moved to a separate file.
  • A new base class to handle the options array is added.
  • Recommendation admin-ajax calls are converted to REST API calls
  • Admin bar compatibility.
  • Regeneration admin notices.

Jetpack product discussion

pc9hqz-cz-p2

Does this pull request change what data or activity we track or use?

No

Testing instructions:

  • Check out this branch - refactor/boost-critical-css
  • Run jetpack build plugins/boost
    • This will install all the dependencies and will build the plugin.
    • (This is needed because a lot of class names/filenames have changed.)
  • Run composer phpcs:lint
    • Make sure that there are no phpcs errors under projects/plugins/boost
  • Run all end to end testing as per the instructions in
    • (Handy commands: pnpm run env-start && pnpm run tunnel-on and pnpm run test-e2e)
    • Make sure that all the tests pass
  • Perform extensive manual testing to make sure the plugin works as expected

karthikax and others added 30 commits December 7, 2021 17:13
Signed-off-by: pyronaur <hi@pyronaur.com>
Signed-off-by: pyronaur <hi@pyronaur.com>
Signed-off-by: pyronaur <hi@pyronaur.com>
Added Notification base class and extended to Recommendation class to cleanup the current design.
Change module constructors to on_prepare so that the actions calls are not duplicated.
Signed-off-by: pyronaur <hi@pyronaur.com>
Signed-off-by: pyronaur <hi@pyronaur.com>
Creating an absolute mess out of the Critical CSS Class.

Signed-off-by: pyronaur <hi@pyronaur.com>
Signed-off-by: pyronaur <hi@pyronaur.com>
Signed-off-by: pyronaur <hi@pyronaur.com>
Signed-off-by: pyronaur <hi@pyronaur.com>
Signed-off-by: pyronaur <hi@pyronaur.com>
Signed-off-by: pyronaur <hi@pyronaur.com>
`! is_admin()` guard clause was protecting against showing Critical CSS anywhere else but the admin :)

Signed-off-by: pyronaur <hi@pyronaur.com>
Signed-off-by: pyronaur <hi@pyronaur.com>
Move providers to `critical-css/path-providers/providers`

Signed-off-by: pyronaur <hi@pyronaur.com>
Signed-off-by: pyronaur <hi@pyronaur.com>
Signed-off-by: pyronaur <hi@pyronaur.com>
Signed-off-by: pyronaur <hi@pyronaur.com>
Signed-off-by: pyronaur <hi@pyronaur.com>
@@ -54,21 +54,13 @@ class Admin {
*/
private $speed_score;

/**
* Initialize the class and set its properties.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😲 Is THAT what a constructor does?!

Mark George added 2 commits January 28, 2022 12:28
Copy link
Member

@thingalon thingalon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a couple of improvements:

  • b7401e5 adds an exception to the "unused variable" rule, to allow any that start with an _. That way we won't need linter warts any time an interface implementor doesn't want to use a function parameter specified by the interface.
  • 8893c9c ensures 500 errors which occur during critical css generation initialization aren't ignored (one crashed the UI during my testing)

This is beautiful work. I particularly love the interfaces, and how they are implemented and used. Let's get this thing merged.

@@ -9,14 +9,24 @@ import { writable } from 'svelte/store';
import config from './config';
import { setModuleState } from '../api/modules';

export type Optimizations = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Optimization a synonym for Module?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimization is a feature, but a feature doesn't necessarily mean optimization.

This came out with a brainstorming call with @eAdnan007 where we had this idea that Boost has other "features" that aren't optimizations, and so they're hanging in the air somewhere.

I actually think this is going to need a follow-up PR to get rid of the "Module" terminology entirely, at least when it comes to describing Optimizations and Features. I think that distinction makes it a lot easier to reason about what belongs where.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems reasonable to me, and I'm fine towards replacing Module with Feature / Optimization.

@@ -0,0 +1,11 @@
<?php

namespace Automattic\Jetpack_Boost\Contracts;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this namespace and what it represents.

use Automattic\Jetpack_Boost\REST_API\Endpoints\Recommendations_Reset;
use Automattic\Jetpack_Boost\REST_API\REST_API;

class Critical_CSS implements Feature, Has_Endpoints {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really, really love this contract syntax which tells you in the header some common ways you can interact with the class.

use Automattic\Jetpack_Boost\REST_API\Contracts\Endpoint;
use Automattic\Jetpack_Boost\REST_API\Permissions\Current_User_Admin;

class Generator_Request implements Endpoint {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nice to include a comment with each endpoint which says its HTTP method, path, and purpose. e.g.:

/***
 * POST critical-css/request-generate
 *
 * Ask the plugin to start a new Critical CSS Generation process. Returns the updated state of the Critical CSS system.
 */

Just to make it easier when browsing endpoints.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 That makes sense

* Check whether the Jetpack Lazy Images module is enabled.
*/
public static function is_jetpack_lazy_images_module_enabled() {
return class_exists( 'Jetpack' ) && Jetpack::is_module_active( self::MODULE_SLUG );
Copy link
Member

@thingalon thingalon Jan 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I just imported my changes from my fixes branch, and there are two tests failing. It is not keeping the "lazy-images" feature in lock-step between Jetpack and Boost. This line is why -- the Modules used to be able to override the default status behavior.

I started down the road to fix this -- I created a new JetpackDeferredStatus class which inherits from Status and overrides its is_enabled and update methods to defer to Jetpack's module status.

But then how do we make some Optimizations use Status, and others use JetpackDeferredStatus?

I could see a few solutions. Among them, this one felt the least messy to me:

  1. Change Feature from an interface to a base class, so that it can define default methods,
  2. Add a create_status_manager method to the Feature base class, which returns a new Status object, and override it for lazy images to return a new JetpackDeferredStatus object instead.
  3. Profit!

But that's a big enough design change that I didn't want to do it without consulting with @pyronaur first.

Alternately, we could:

  • Add a FeatureDefaults base class which each feature inherits in addition to using the Feature interface, so that we have somewhere to park defaults which apply to most features - but also maintain the clean Feature contract.
  • Add a create_status_manager method to every feature. That's kind of tiresome.
  • Add an empty interface like UsesJetpackStatus to tag the Lazy_Images class as wanting to defer its status to Jetpack, and checking for it in the Optimization class constructor. But that feels like magic-at-a-distance which will trip us up later.
  • Add the deferral logic to the Status class, and have it know a bunch of slugs to defer to Jetpack's status. That feels like it puts the intelligence in the wrong place.
  • Add a special case to the Optimization class and have it know which Features need special status treatment. Same problem with intelligence going in the wrong place.

None of those options seem clean to me.

So @pyronaur, how would you go about this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started down the road to fix this -- I created a new JetpackDeferredStatus class which inherits from Status and overrides its is_enabled and update methods to defer to Jetpack's module status.

This is certainly one way of doing things. Although, whenever possible - I think we should try to think how we can do this with composition first (instead of inhertiance)

But then how do we make some Optimizations use Status, and others use JetpackDeferredStatus?

Not entirely sure either, but I think it's worth exploring adding a status contract where both Jetpack_Status and Boost_Status both subscribe to that contract and become interchangeable. At that point, whoever is instantiating a status can decide which type of status is needed (for example, depending on whether Jetpack is active)

But there's a problem with this approach still - because both plugins can be activated without the other, so if the place of storage changes, there's no easy way to silently sync and migrate the option.

I have to look closer at the lazy images module - but I think maybe that module itself should be able to know whether it's active or not? I'm not sure, but it would be a way to neatly fix this.

Alternately, we could:

Hm. I need to do a little bit of thinking on this, but it seems that if we use any type of inheritance it's automatically going to tie down the parent class.

Thinking from a single-responsibility angle - what's the single responsibility we're missing here? Sounds like we're talking about where to store the status - either in Jetpack options or Boost options. So I think that's the hint of the direction we need to go in.

No other classes should be aware that we have a compatibility layer with Jetpack. Where to place it exactly - I'm not entirely sure. I think it needs a bit of thought as a part of #22542

thingalon
thingalon previously approved these changes Jan 28, 2022
@thingalon thingalon enabled auto-merge (squash) January 31, 2022 04:09
@thingalon thingalon merged commit 1af92d6 into master Jan 31, 2022
@thingalon thingalon deleted the refactor/boost-critical-css branch January 31, 2022 04:14
@github-actions github-actions bot removed [Status] Needs Author Reply We would need you to make some changes or provide some more details about your PR. Thank you! [Status] In Progress labels Jan 31, 2022
@github-actions github-actions bot added this to the boost/1.3.2 milestone Jan 31, 2022
jeherve added a commit that referenced this pull request Feb 9, 2023
Boost modules were moved to a features/ directory in #22163, but this task wasn't updated accordingly. This should fix things.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Admin Page React-powered dashboard under the Jetpack menu [Boost Feature] Critical CSS Issues involving the Critical CSS feature in Boost [Boost Feature] Defer JS issues related to the Defer JS feature in Jetpack Boost [Boost Feature] Lazy Images E2E Tests [Focus] Compatibility Ensuring our products play well with third-parties [Plugin] Boost A feature to speed up the site and improve performance.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Boost: Refactor Critical CSS class
4 participants