Last active
May 10, 2021 17:45
-
-
Save westonruter/50abd71d9becb5a34f01136052effef5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* AMP Force Hero Image Preloading plugin bootstrap. | |
* | |
* @package Google\AmpHeroImagePreloading | |
* @author Weston Ruter, Google | |
* @license GPL-2.0-or-later | |
* @copyright 2021 Google Inc. | |
* | |
* @wordpress-plugin | |
* Plugin Name: AMP Force Hero Image Preloading | |
* Plugin URI: https://gist.github.com/westonruter/50abd71d9becb5a34f01136052effef5 | |
* Description: Forcing hero images to be preloaded, even when they are responsive and lack media attributes. This is a workaround until <a href="https://github.com/ampproject/amp-toolbox/issues/1230">amp-toolbox#1230</a> is implemented. | |
* Version: 0.1 | |
* Author: Weston Ruter, Google | |
* Author URI: https://weston.ruter.net/ | |
* License: GNU General Public License v2 (or later) | |
* License URI: http://www.gnu.org/licenses/gpl-2.0.html | |
* Gist Plugin URI: https://gist.github.com/westonruter/50abd71d9becb5a34f01136052effef5 | |
*/ | |
namespace Google\AmpForceHeroImagePreloading; | |
use AmpProject\Optimizer; | |
use AmpProject\Optimizer\Transformer\ReorderHead; | |
/** | |
* Filter configuration array to register the transformer. | |
* | |
* @param array $configuration Associative array of configuration data. | |
* @return array Configuration. | |
*/ | |
function filter_amp_optimizer_config( array $configuration ) { | |
require_once __DIR__ . '/ForcePreloadHeroImage.php'; | |
$transformers = $configuration[ Optimizer\Configuration::KEY_TRANSFORMERS ]; | |
// Add ForcePreloadHeroImage right before the ReorderHead transformer. | |
$reorder_head_position = array_search( ReorderHead::class, $transformers ); | |
if ( false !== $reorder_head_position ) { | |
array_splice( $transformers, $reorder_head_position, 0, [ ForcePreloadHeroImage::class ] ); | |
} else { | |
$transformers[] = ForcePreloadHeroImage::class; | |
} | |
$configuration[ Optimizer\Configuration::KEY_TRANSFORMERS ] = array_values( $transformers ); | |
return $configuration; | |
} | |
if ( empty( $_GET['amp_disable_force_preload_hero_image'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended | |
add_filter( 'amp_optimizer_config', __NAMESPACE__ . '\filter_amp_optimizer_config' ); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Force preloading the hero images. | |
* | |
* @package Google\AmpHeroImagePreloading | |
*/ | |
namespace Google\AmpForceHeroImagePreloading; | |
use AmpProject\Dom\Document; | |
use AmpProject\Optimizer\ErrorCollection; | |
use AmpProject\Optimizer\Transformer; | |
use AmpProject\Attribute; | |
use AmpProject\Tag; | |
use AmpProject\Dom\Element; | |
use AmpProject\RequestDestination; | |
use AmpProject\Extension; | |
use AmpProject\Url; | |
use AmpProject\Optimizer\HeroImage; | |
/** | |
* Transformer which rewrites image URLs to point the AMP Cache as a CDN. | |
*/ | |
final class ForcePreloadHeroImage implements Transformer { | |
/** | |
* Reference node to attach preload links to. | |
* | |
* @var Element|null | |
*/ | |
private $preload_reference_node; | |
/** | |
* Apply transformations to the provided DOM document. | |
* | |
* @param Document $document DOM document to apply the transformations to. | |
* @param ErrorCollection $errors Collection of errors that are collected during transformation. | |
*/ | |
public function transform( Document $document, ErrorCollection $errors ) { | |
$elements = $document->xpath->query( | |
'.//amp-img[ @data-hero and @i-amphtml-ssr ][ not( img/@loading ) or "lazy" != img/@loading ]', | |
$document->body | |
); | |
foreach ( $elements as $element ) { | |
/** @var Element $element */ | |
$src = $element->getAttribute( Attribute::SRC ); | |
if ( Extension::IMG === $element->tagName && ( new Url( $src ) )->isValidNonDataUrl() ) { | |
$hero_image = new HeroImage( | |
$src, | |
$element->getAttribute( Attribute::MEDIA ), | |
$element->getAttribute( Attribute::SRCSET ), | |
$element | |
); | |
$this->generatePreload( $hero_image, $document ); | |
} | |
} | |
} | |
/** | |
* Generate the preload link for a given hero image. | |
* | |
* This is adapted from the same method in the PreloadHeroImage transformer in amp-toolbox-php. | |
* | |
* @see Transformer\PreloadHeroImage::generatePreload() | |
* @link https://github.com/ampproject/amp-toolbox-php/blob/86d53aa73edef1aafd748fb94646af6859414e2a/src/Optimizer/Transformer/PreloadHeroImage.php#L499-L552 | |
* | |
* @param HeroImage $hero_image Hero image to generate the preload link for. | |
* @param Document $document Document to generate the preload link in. | |
*/ | |
private function generatePreload( HeroImage $hero_image, Document $document ) { | |
if ( $this->hasExistingImagePreload( $document, $hero_image->getSrc() ) ) { | |
return; | |
} | |
if ( ! $this->preload_reference_node ) { | |
$this->preload_reference_node = $document->viewport; | |
} | |
$preload = $document->createElement( Tag::LINK ); | |
$preload->setAttribute( Attribute::REL, Attribute::REL_PRELOAD ); | |
$preload->setAttribute( Attribute::HREF, $hero_image->getSrc() ); | |
$preload->setAttribute( Attribute::AS_, RequestDestination::IMAGE ); | |
$preload->appendChild( $document->createAttribute( Attribute::DATA_HERO ) ); | |
if ( $hero_image->getSrcset() ) { | |
$preload->setAttribute( Attribute::IMAGESRCSET, $hero_image->getSrcset() ); | |
$img = $hero_image->getAmpImg(); | |
if ( $img && $img->hasAttribute( Attribute::SIZES ) ) { | |
$preload->setAttribute( Attribute::IMAGESIZES, $img->getAttribute( Attribute::SIZES ) ); | |
} | |
} | |
$media = $hero_image->getMedia(); | |
if ( $media ) { | |
$preload->setAttribute( Attribute::MEDIA, $hero_image->getMedia() ); | |
} | |
if ( $this->preload_reference_node ) { | |
$this->preload_reference_node->parentNode->insertBefore( | |
$preload, | |
$this->preload_reference_node->nextSibling | |
); | |
} else { | |
$document->head->appendChild( $preload ); | |
} | |
$this->preload_reference_node = $preload; | |
} | |
/** | |
* Check whether an existing preload link exists for a given src. | |
* | |
* This is adapted from the same method in the PreloadHeroImage transformer in amp-toolbox-php. | |
* | |
* @see Transformer\PreloadHeroImage::hasExistingImagePreload() | |
* @link https://github.com/ampproject/amp-toolbox-php/blob/86d53aa73edef1aafd748fb94646af6859414e2a/src/Optimizer/Transformer/PreloadHeroImage.php#L611-L639 | |
* | |
* @param Document $document Document in which to check for an existing preload. | |
* @param string $src Preload URL to look for. | |
* | |
* @return bool Whether an existing preload already exists. | |
*/ | |
private function hasExistingImagePreload( Document $document, $src ) { | |
foreach ( $document->head->childNodes as $node ) { | |
if ( ! $node instanceof Element ) { | |
continue; | |
} | |
if ( $node->getAttribute( Attribute::REL ) !== Attribute::REL_PRELOAD ) { | |
continue; | |
} | |
if ( $node->getAttribute( Attribute::AS_ ) !== RequestDestination::IMAGE ) { | |
continue; | |
} | |
if ( $node->getAttribute( Attribute::HREF ) === $src ) { | |
return true; | |
} | |
} | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Installation instructions: https://gist.github.com/westonruter/6110fbc4bef0c4b8c021a112012f7e9c