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

Don't tree-shake allowed child classes of amp-date-picker if it's in the DOM #4339

Merged
merged 12 commits into from
Mar 16, 2020

Conversation

kienstra
Copy link
Contributor

@kienstra kienstra commented Feb 27, 2020

Summary

Prevents tree-shaking of valid child classes of amp-date-picker, if that tag is in the document.

When adding the markup and styling that Weston shared to a Custom HTML block:

<style>
amp-date-picker .CalendarMonth_caption {  
  background-image: linear-gradient(#b8b8b8, #5e5e5e);
  padding: 12px 0px;
}
amp-date-picker .amp-date-picker-calendar-container .CalendarMonth_caption {
  background-image: linear-gradient(#b8b8b8, #5e5e5e);
  padding: 12px 0px;
}
.amp-date-picker-calendar-container .CalendarMonth_caption {
  background-image: linear-gradient(#b8b8b8, #5e5e5e);
  padding: 12px 0px;	
}
</style>
<amp-date-picker layout="fixed-height" height="360"></amp-date-picker>

...it's not tree-shaken:

date-picker-not-tree-shaken

Fixes #4334

Checklist

  • My pull request is addressing an open issue (please create one otherwise).
  • My code is tested and passes existing tests.
  • My code follows the Engineering Guidelines (updates are often made to the guidelines, check it out periodically).

Since this is JS-driven,
child classes won't be in the document
during tree-shaking.
So if amp-date-picker
is in the tags and the class
is an allowed child of it,
don't tree-shake it.
@googlebot googlebot added the cla: yes Signed the Google CLA label Feb 27, 2020
private function is_class_allowed_in_amp_date_picker( $class ) {
// Some prefixes, some full class names.
$allowed_class_beginnings = [
'CalendarDay',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This might be missing some classes, though I tried to be thorough.

I looked at the components that have styles.

This doesn't include components that are just an .svg, like CalendarIcon.

Copy link
Member

Choose a reason for hiding this comment

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

Do any of these component names get carried over to be class names in the DOM? If so, they should be included in this list.

Copy link
Contributor Author

@kienstra kienstra Mar 4, 2020

Choose a reason for hiding this comment

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

Yes, all of these appear as names in the DOM as classes, except for DateInput, DateRangePicker, DateRangePicker:

amp-date-picker

For those 3 that didn't appear, I couldn't find them, when using most of the snippets in the amp-date-picker documentation. I should probably remove those, though maybe I just missed a case where they are used.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The 3 that I couldn't find in the DOM as class names are removed in 5bffd65

Some classes should have exact matches,
like amp-date-picker-calendar-contrainer
The component name is expected to
be followed by a letter or _

For example, CalendarDay
could be CalendarDay_table.
Also, DayPicker could be
DayPickerNavigation.
@kienstra
Copy link
Contributor Author

kienstra commented Feb 27, 2020

Hi @westonruter,
No hurry, could you please review this when you have a chance? Thanks!

There's probably no need to iterate through the classses,
this could simply return in_array().
'SingleDatePicker',
];

if ( preg_match( '#^(' . implode( '|', $class_prefixes ) . ')[a-zA-Z_]+#', $class ) ) {
Copy link
Member

@westonruter westonruter Mar 3, 2020

Choose a reason for hiding this comment

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

This can be made simpler/faster like so:

Suggested change
if ( preg_match( '#^(' . implode( '|', $class_prefixes ) . ')[a-zA-Z_]+#', $class ) ) {
if ( in_array( strtok( $class, '_' ), $class_prefixes, true ) ) ) {

Copy link
Contributor Author

@kienstra kienstra Mar 4, 2020

Choose a reason for hiding this comment

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

Thanks, committed in 36cba7d

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reverted, then re-committed in 5bffd65

return true;
}

return in_array( $class, [ 'amp-date-picker-calendar-container', 'amp-date-picker-resize-bug' ], true );
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return in_array( $class, [ 'amp-date-picker-calendar-container', 'amp-date-picker-resize-bug' ], true );
return 'amp-date-picker-' === substr( $class, 0, 16 );

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Committed in eea565f

private function is_class_allowed_in_amp_date_picker( $class ) {
// Some prefixes, some full class names.
$allowed_class_beginnings = [
'CalendarDay',
Copy link
Member

Choose a reason for hiding this comment

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

Do any of these component names get carried over to be class names in the DOM? If so, they should be included in this list.

kienstra and others added 4 commits March 3, 2020 18:25
Co-Authored-By: Weston Ruter <westonruter@google.com>
This now searches for something like
DayPickerNavigation_,
instead of just verifying that it
starts with DayPicker.
Co-Authored-By: Weston Ruter <westonruter@google.com>
Comment on lines +778 to +784
'CalendarDay',
'CalendarMonth',
'CalendarMonthGrid',
'DayPicker',
'DayPickerKeyboardShortcuts',
'DayPickerNavigation',
'KeyboardShortcutRow',
Copy link
Member

Choose a reason for hiding this comment

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

I'm using this function in the console:

function gatherClassNames( elements ) {
	const classNames = new Set();
	const add = ( className ) => {
		if ( !/^i-amphtml-/.test( className ) ) {
			classNames.add( className.replace( /_.+/, '' ) );
		}
	};
	for ( const element of elements ) {
		element.classList.forEach( add );
		gatherClassNames( element.children ).forEach( add );
	}
	return classNames;
}

And I'm invoking it via:

gatherClassNames(document.querySelectorAll('amp-date-picker'))

I'm not able to find any other class names in use when looking at the amp-date-picker examples.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, great function to get the class names.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The KeyboardShortcutRow class only appears on clicking the ? icon in the bottom right. But so far, I couldn't find more classes that appear via user interaction.

return true;
}

return 'amp-date-picker-' === substr( $class, 0, 16 );
Copy link
Member

Choose a reason for hiding this comment

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

Actually, is this not redundant with:

// Class names for amp-date-picker, see <https://www.ampproject.org/docs/reference/components/amp-date-picker>.
case 'amp-date-picker-':
if ( ! $this->has_used_tag_names( [ 'amp-date-picker' ] ) ) {
return false;
}
continue 2;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I think it is redundant.

What do you think of deleting that snippet you shared?

Otherwise, it might be confusing if that's there, and there's a call below of ->is_class_allowed_in_amp_date_picker()

Copy link
Member

Choose a reason for hiding this comment

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

I'd defer to @schlessera for his recommendation, since he highly optimized this method.

I'd probably rather change this method to:

return in_array( strtok( $class, '_' ), $class_prefixes, true );

Copy link
Collaborator

Choose a reason for hiding this comment

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

Indeed, as explained in my comment above, the logic needs to be integrated into the switch statement that already has the datepicker included, otherwise this is getting a big performance hit again.

This would then take care of the 'amp-date-picker-' === substr( $class, 0, 16 ) bit, which would be a given once you enter here.

Keep in mind that this logic runs 20k to 30k times on a single request for a regular page. It is important to keep this highly optimized when making changes.

Copy link
Member

Choose a reason for hiding this comment

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

@schlessera what do you think of this approach: b6f1a23?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@@ -662,6 +662,12 @@ private function has_used_class_name( $class_names ) {
}
}

// If the document has an amp-date-picker tag, check if this class is an allowed child of it.
// That component's child classes won't be present yet in the document, so prevent tree-shaking valid classes.
if ( $this->has_used_tag_names( [ 'amp-date-picker' ] ) && $this->is_class_allowed_in_amp_date_picker( $class_name ) ) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This needs to be integrated into the switch logic for the datepicker above that starts at line 651. Otherwise, we will always run the optimized loop, and then always run another check for the datepicker, suddenly making the entire loop work twice as much (and thus much slower).

Copy link
Contributor Author

@kienstra kienstra Mar 5, 2020

Choose a reason for hiding this comment

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

Thanks, good point that this would be a big performance cost always running this other check.

Maybe I'm not understanding your suggestion.

But on moving this into the switch for the datepicker at line 651:`

diff --git a/includes/sanitizers/class-amp-style-sanitizer.php b/includes/sanitizers/class-amp-style-sanitizer.php
index e9e2b4be6..5f807791a 100644
--- a/includes/sanitizers/class-amp-style-sanitizer.php
+++ b/includes/sanitizers/class-amp-style-sanitizer.php
@@ -649,7 +649,7 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
                                switch ( substr( $class_name, 0, 16 ) ) {
                                        // Class names for amp-date-picker, see <https://www.ampproject.org/docs/reference/components/amp-date-picker>.
                                        case 'amp-date-picker-':
-                                               if ( ! $this->has_used_tag_names( [ 'amp-date-picker' ] ) ) {
+                                               if ( ! $this->has_used_tag_names( [ 'amp-date-picker' ] ) || ! $this->is_class_allowed_in_amp_date_picker( $class_name ) ) {  // Though this conditional will probably never be false.
                                                        return false;
                                                }
                                                continue 2;
@@ -662,12 +662,6 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer {
                                }
                        }
 
-                       // If the document has an amp-date-picker tag, check if this class is an allowed child of it.
-                       // That component's child classes won't be present yet in the document, so prevent tree-shaking valid classes.
-                       if ( $this->has_used_tag_names( [ 'amp-date-picker' ] ) && $this->is_class_allowed_in_amp_date_picker( $class_name ) ) {
-                               continue;
-                       }
-
                        if ( ! isset( $this->used_class_names[ $class_name ] ) ) {
                                return false;
                        }

...this won't allow the classes in is_class_allowed_in_amp_date_picker()

static $class_prefixes = [
	'CalendarDay',
	'CalendarMonth',
	'CalendarMonthGrid',
	'DayPicker',
	'DayPickerKeyboardShortcuts',
	'DayPickerNavigation',
	'KeyboardShortcutRow',
];

...as they don't begin with amp-date-picker-.

But maybe I'm not understanding your point. And your point about the performance cost is well taken.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here's an example <amp-date-picker>

amp-date-picker

return true;
}

return 'amp-date-picker-' === substr( $class, 0, 16 );
Copy link
Collaborator

Choose a reason for hiding this comment

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

Indeed, as explained in my comment above, the logic needs to be integrated into the switch statement that already has the datepicker included, otherwise this is getting a big performance hit again.

This would then take care of the 'amp-date-picker-' === substr( $class, 0, 16 ) bit, which would be a given once you enter here.

Keep in mind that this logic runs 20k to 30k times on a single request for a regular page. It is important to keep this highly optimized when making changes.

* @return bool Whether the class is allowed as a child of <amp-date-picker>.
*/
private function is_class_allowed_in_amp_date_picker( $class ) {
$class_prefixes = [
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be either a const array, or a static variable. Otherwise, you're creating and filling up a fresh new array every single time this method runs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, this is applied in 01a8e8f

kienstra and others added 3 commits March 4, 2020 20:32
On Alain's suggestion,
as this would have to be recreacted every time.
…picker-tree-shaking

* 'develop' of github.com:ampproject/amp-wp: (285 commits)
  Add PHPStan config files to libraries
  Remove erroneous unset
  Use US spelling analyze instead of analyse
  Remove static analysis step from common scripts section in travis.yml
  Fix missing unset
  Readd condition for running php-stan
  Raise PHPStan level in plugin to 2
  Raise PHPStan level in plugin to 1
  Fix conditional running of test steps
  Remove wrong psot-update action in ampproject/common again
  Better attempt at normalizing Composer file
  Normalize Composer file
  Update Composer lock file
  Run PHPStan in Travis
  Fix PHPCS issues
  Fix weird *NEVER* array unset error
  Fix stub sanitizer
  Typehint array being unset
  Skip analysing view template
  Fix bug in AMP_DOM_Utils referencing inexistent property
  ...
@westonruter westonruter merged commit c6b1328 into develop Mar 16, 2020
@westonruter westonruter deleted the fix/date-picker-tree-shaking branch March 16, 2020 17:03
@kienstra
Copy link
Contributor Author

Nice, thanks!

@westonruter westonruter added this to the v1.5 milestone Mar 22, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes Signed the Google CLA
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Tree shaker removes CSS style rules for amp-date-picker classes
4 participants