AMP Optimizer PHP library

Overview
AMP logo

AMP Toolbox for PHP

Build Status Coverage

A collection of AMP tools making it easier to publish and host AMP pages with PHP.

The following tools are part of this library:

AMP Optimizer for PHP

AMP Optimizer is a library for doing server-side optimization to AMP markup by automatically applying AMP performance best practices and enabling AMP server-side-rendering.

Table of Contents (click to expand)

Conceptual Overview

The AMP Optimizer is a AmpProject\Optimizer\TransformationEngine object that sets up a pipeline of consecutive AmpProject\Optimizer\Transformer objects. The engine takes unoptimized input in the form of either a HTML markup string or an AmpProject\Dom\Document object and turns it into an optimized HTML markup string.

During the process, errors might occur that make parts of the optimization impossible. These are collected within an AmpProject\Optimizer\ErrorCollection object that you can then iterate over to find out more and provide feedback as needed.

Diagram showing the conceptual parts of the AMP Optimizer

Installation

The AMP Optimizer is part of the AMP Toolbox for PHP library that you can pull into your project via the Composer PHP package manager:

composer require ampproject/amp-toolbox

Basic Usage

The following code snippet shows the most basic way of using the AMP Optimizer:

use AmpProject\Optimizer\ErrorCollection;
use AmpProject\Optimizer\TransformationEngine;

$transformationEngine = new TransformationEngine();    // 1.
$errorCollection      = new ErrorCollection;           // 2.

$optimizedHtml = $transformationEngine->optimizeHtml(  // 3.
    $unoptimizedHtml,                                  // 4.
    $errorCollection                                   // 5.
);
  1. First we instantiate the transformation engine itself.
  2. Then we instantiate an AmpProject\Optimizer\ErrorCollection object as we need a "bag" to collect the errors in and pass them around.
  3. As a final step, we store the result of calling the transformation engine's optimizeHtml() method, which requires...
  4. ... the unoptimized input HTML markup as a string and ...
  5. ... the empty error collection we've already instantiated. After the transformation engine ran, this collection will contain all errors that were encountered during the transformation.

Usage with a DOM Representation

If you already have a DOM representation, there's no need to save it as HTML first to use it with the transformation engine. The transformation engine accepts an AmpProject\Dom\Document object[^1] directly via its optimizeDom() method.

If you have a regular PHP built-in DOMDocument instead, you can turn it into an AmpProject\Dom\Document using AmpProject\Dom\Document::fromNode().

use AmpProject\Dom\Document;
use AmpProject\Optimizer\ErrorCollection;
use AmpProject\Optimizer\TransformationEngine;

if (! $dom instanceof Document) {
    $dom = Document::fromNode($dom);
}

$transformationEngine = new TransformationEngine();
$errorCollection      = new ErrorCollection;
$transformationEngine->optimizeDom($dom, $errorCollection);

Do note that the optimizeDom() doesn't have a return value, as it changes the provided AmpProject\Dom\Document in-place.

Working with Errors

The AmpProject\Optimizer\ErrorCollection that you pass into the transformation engine's optimizeHtml() or optimizeDom() method should ideally stay empty after the optimization pass.

To check whether errors were found, you can iterate over the collection, which will provide you with 0 or more AmpProject\Optimizer\Error objects.

$errorCollection = new ErrorCollection;

// Do the transformation here, while passing in the $errorCollection object.

foreach ($errorCollection as $error) {
	printf(
	    "Error code: %s\nError Message: %s\n",
	    $error->getCode(),
	    $error->getMessage()
	);
}

A quick count of the errors can be done for early returns as needed:

if ($errorCollection->count() > 0) {
	$this->log('The AMP serverside optimization process produced one or more errors.');
}

You can check whether the collection of errors contains an error with a specific code as well. The current convention is that all errors have their class shortname (the class name without the namespace) as the error code.

if ($errorCollection->has('CannotRemoveBoilerplate')) {
	$this->log('The boilerplate was not removed by the Optimizer.');
}

Note that this only lets you check whether an error "category" popped up. It can be one or more errors with that same code. If you need a more detailed check, you should iterate over the collection instead.

Included Transformers

Class (short name) Description
AmpBoilerplate Transformer that removes AMP boilerplate <style> and <noscript> tags in <head>, keeping only the amp-custom style tag. It then (re-)inserts the amp-boilerplate unless the document is marked with the i-amphtml-no-boilerplate attribute.
AmpRuntimeCss Transformer adding https://cdn.ampproject.org/v0.css if server-side-rendering is applied (known by the presence of the <style amp-runtime> tag). AMP runtime css (v0.css) will always be inlined as it'll get automatically updated to the latest version once the AMP runtime has loaded.
PreloadHeroImage Transformer that optimizes image rendering times for hero images by adding preload and serverside-rendered <img> tags when possible. Viable hero images are <amp-img> tags, <amp-video> tags with a poster attribute as well as <amp-iframe> and <amnp-video-iframe> tags with a placeholder attribute. The first viable image that is encountered is used by default, but this behavior can be overridden by adding the data-hero attribute to a maximum of two images. The preloads only work work images that don't use srcset, as that is not supported as a preload in most browsers. The serverside-rendered image will not be created for <amp-video> tags.
ReorderHead Transformer applying the head reordering transformations to the HTML input. ReorderHead reorders the children of <head>. Specifically, it orders the <head> like so:
(0) <meta charset> tag
(1) <style amp-runtime> (inserted by AmpRuntimeCss)
(2) remaining <meta> tags (those other than <meta charset>)
(3) AMP runtime .js <script> tag
(4) AMP viewer runtime .js <script>
(5) <script> tags that are render delaying
(6) <script> tags for remaining extensions
(7) <link> tag for favicons
(8) <link> tag for resource hints
(9) <link rel=stylesheet> tags before <style amp-custom>
(10) <style amp-custom>
(11) any other tags allowed in <head>
(12) AMP boilerplate (first <style> boilerplate, then <noscript>)
RewriteAmpUrls Transformer that rewrites AMP runtime URLs to decide what version of the runtime to use. This allows you to do such things as switching to the LTS version or disabling ES modules.
ServerSideRendering Transformer applying the server-side rendering transformations to the HTML input. This does immediately on the server what would normally be done on the client after the runtime was downloaded and executed to process the DOM. As such, it allows for the removal of the boilerplate CSS that hides the page while it has not yet been processed on the client, drastically improving time it takes for the First Contentful Paint (FCP).
TransformedIdentifier Transformer applying the transformed identifier transformations to the HTML input. This is what marks an AMP document as "already optimized", so that the AMP runtime does not need to process it anymore.

Configuring the Transformers

You can inject a configuration object into the AmpProject\Optimizer\TransformationEngine to override the default configuration.

The AmpProject\Optimizer\Configuration interface and its default implementation AmpProject\Optimizer\DefaultConfiguration will provide the list of transformers to use, as well as give access to child objects they store that are Transformer-specific configuration objects.

To override the list of transformers to use, you can provide an array containing the AmpProject\Optimizer\Configuration::KEY_TRANSFORMERS key.

use AmpProject\Optimizer\Configuration;
use AmpProject\Optimizer\DefaultConfiguration;
use AmpProject\Optimizer\TransformationEngine;
use AmpProject\Optimizer\Transformer;

$configurationData = [
	Configuration::KEY_TRANSFORMERS => [
		Transformer\ServerSideRendering::class,
		Transformer\AmpRuntimeCss::class,
		Transformer\TransformedIdentifier::class,
	],
];

$transformationEngine = new TransformationEngine(
	new DefaultConfiguration($configurationData)
);

Configuration values for the transformers can be stored under the fully qualified class name of these transformers. This can be easily done by using their ::class constant.

They will also usually provide publicly accessible constants for the known configuration keys as well.

In the following example, we configure the AmpProject\Optimizer\Transformer\AmpRuntimeCss transformer by setting its 'canary' option to true (which would default to false).

use AmpProject\Optimizer\Configuration;
use AmpProject\Optimizer\DefaultConfiguration;
use AmpProject\Optimizer\TransformationEngine;
use AmpProject\Optimizer\Transformer;

$configurationData = [
	Transformer\AmpRuntimeCss::class => [
		Configuration\AmpRuntimeCssConfiguration::CANARY => true,
	],
];

$transformationEngine = new TransformationEngine(
	new DefaultConfiguration($configurationData)
);

Creating a Custom Transformer

A custom transformer is at the most basic level an object that implements the AmpProject\Optimizer\Transformer interface.

This means it needs to have at the very least the following method:

public function transform(Document $document, ErrorCollection $errors)
{
	// Apply transformations to the provided $document and ...

	// ... add any encountered errors to the $errors collection.
}

To make this transformer then known to the transformation engine, you add it to the AmpProject\Optimizer\Configuration::KEY_TRANSFORMERS key of the AmpProject\Optimizer\Configuration object you pass into it.

use AmpProject\Optimizer\Configuration;
use AmpProject\Optimizer\DefaultConfiguration;
use AmpProject\Optimizer\TransformationEngine;
use MyProject\MyCustomTransformer;

$configurationData = [
	Configuration::KEY_TRANSFORMERS => array_merge(
		Configuration::DEFAULT_TRANSFORMERS,
		[
			MyCustomTransformer::class
		],
	),
];

$transformationEngine = new TransformationEngine(
	new DefaultConfiguration($configurationData)
);

Making a Custom Transformer Configurable

Configuration objects for the individual transformers need to be registered with the main AmpProject\Optimizer\Configuration object using its registerConfigurationClass() method, which takes a fully qualified class name of the transformer as well as a fully qualified class name of the corresponding configuration object as its two arguments.

The configuration objects for the transformers that ship with this library are already registered by default. But if you add third-party or custom transformers, you'll need to register whatever configuration objects they might need with the main AmpProject\Optimizer\Configuration object first.

In the following example, we add a new MyProject\MyCustomTransformer transformer in addition to the default set and configure it with a default value, and then we register its corresponding configuration object to make sure the configuration can be properly validated and passed around.

use AmpProject\Optimizer\Configuration;
use AmpProject\Optimizer\DefaultConfiguration;
use AmpProject\Optimizer\TransformationEngine;
use MyProject\MyCustomTransformer;
use MyProject\MyCustomTransformerConfiguration;

$configurationData = [
	Configuration::KEY_TRANSFORMERS => array_merge(
		Configuration::DEFAULT_TRANSFORMERS,
		[
			MyCustomTransformer::class
		],
	),
	MyCustomTransformer::class => [
		MyCustomTransformerConfiguration::SOME_CONFIG_KEY => 'some value',
	],
];

$configuration = new DefaultConfiguration($configurationData);

$configuration->registerConfigurationClass(
	MyCustomTransformer::class,
	MyCustomTransformerConfiguration::class
);

$transformationEngine = new TransformationEngine($configuration);

For the wiring to work correctly, the MyProject\MyCustomTransformer class needs to accept within its constructor an object implementing the AmpProject\Optimizer\TransformerConfiguration interface. The transformation engine will then inject the appropriate implementation at runtime when the transformer is being instantiated.

The MyProject\MyCustomTransformerConfiguration class should then implement that same AmpProject\Optimizer\TransformerConfiguration interface. For convenience, it can do so easily by extending the abstract AmpProject\Optimizer\Configuration\BaseTransformerConfiguration base class.

The configuration object will then be automatically injected into the transformer's constructor as needed.

Here's an example configuration class for our custom MyProject\MyCustomTransformer transformer:

namespace MyProject;

use AmpProject\Optimizer\Configuration\BaseTransformerConfiguration;

final class MyCustomTransformerConfiguration extends BaseTransformerConfiguration
{
	const SOME_CONFIG_KEY = 'some_config_key';

	protected function getAllowedKeys()
	{
		return [
			self::SOME_CONFIG_KEY => 'default value',
		];
	}

	protected function validate($key, $value)
	{
		switch ($key) {
			case self::SOME_CONFIG_KEY:
				// Validate configuration value here.
		}

		return $value;
	}
}

Here's how the transformer itself can accept and make use of the configuration object:

namespace MyProject;

use AmpProject\Dom\Document;
use AmpProject\Optimizer\Configurable;
use AmpProject\Optimizer\ErrorCollection;
use AmpProject\Optimizer\TransformerConfiguration;
use AmpProject\Optimizer\Transformer;

final class MyCustomTransformer implements Transformer
{
	private $configuration;

	public function __construct(TransformerConfiguration $configuration)
	{
		$this->configuration = $configuration;
	}

	public function transform(Document $document, ErrorCollection $errors)
	{
		// Bogus transformer logic that adds the configuration value as a body attribute.
		$document->body->setAttribute(
			'data-my-custom-transformer-body-attribute,
			$this->configuration->get(
				MyCustomTransformerConfiguration::SOME_CONFIG_KEY
			)
		);
	}
}

Transformers Requesting External Data

In case your transformer needs to make remote requests to fetch external data (like the AmpProject\Optimizer\Transformer\AmpRuntimeCss does for fetching the latest version of the CSS to inline), you need to accept an AmpProject\RemoteGetRequest object as an argument in your constructor. The transformation engine will then inject the appropriate implementation at runtime when the transformer is being instantiated.

This layer of abstraction allows code outside of the transformation engine to control the specific conditions and limits that govern these remote request, like for example throttling them or integrating them with the caching subsystem of the framework in use.

namespace MyProject;

use AmpProject\Dom\Document;
use AmpProject\RemoteGetRequest;
use AmpProject\Optimizer\ErrorCollection;
use AmpProject\Optimizer\Transformer;
use Throwable;

final class MyCustomTransformer implements Transformer
{
	const END_POINT = 'https://example.com/some_endpoint/';

	private $remoteRequest;

	public function __construct(RemoteGetRequest $remoteRequest)
	{
		$this->remoteRequest = $remoteRequest;
	}

	public function transform(Document $document, ErrorCollection $errors)
	{
		try {
			$response = $this->remoteRequest->get(self::END_POINT);
		} catch (Throwable $exception) {
			// Add error handling here.
		}

		$statusCode = $response->getStatusCode();

		if (200 < $statusCode || $statusCode >= 300) {
			// Add error handling here.
		}

		$content = $response->getBody();
		
		// Make use of the $content you've just retrieved from an external source.
	}
}

Adapting the Handling of Remote Requests

The implementation to use for fulfilling requests made via the AmpProject\RemoteGetRequest interface can be injected into the AmpProject\Optimizer\TransformationEngine via its second, optional argument:

use AmpProject\Optimizer\DefaultConfiguration;
use AmpProject\Optimizer\TransformationEngine;

$transformationEngine = new TransformationEngine(
	new DefaultConfiguration(),

	// A custom implementation that lets you control how remote requests are handled.
	new MyCustomRemoteGetRequestImplementation()
);

If this optional second argument is not provided when instancing the transformation engine, the default AmpProject\RemoteRequest\CurlRemoteGetRequest implementation is used.

There are other implementations already provided that can be useful:

Class (short name) Description
CurlRemoteGetRequest Remote request transport using cURL. This is the default implementation that will be used if you don't provide one explicitly.

It has the following configuration settings as constructor arguments:
$sslVerify - Whether to verify SSL certificates. Defaults to true.
$timeout - Timeout value to use in seconds. Defaults to 10.
$retries - Number of retry attempts to do if an error code was thrown that is worth retrying. Defaults to 2.
FallbackRemoteGetRequest Fallback pipeline implementation to go through a series of fallback requests until a request succeeds. The request will be tried with the first instance provided, and follow the instance series from one to the next until a successful response was returned.

It has the following configuration settings as constructor arguments:
...$pipeline - Variadic array of RemoteGetRequest instances to use as consecutive fallbacks.
FilesystemRemoteGetRequest Fetch the response for a remote request from the local filesystem instead. This can be used to provide offline fallbacks.

It has the following configuration settings as constructor arguments:
$argumentMap - Associative array of data for mapping between provided URLs and the filepaths they should map to.
StubbedRemoteGetRequest Stub for simulating remote requests. This is mainly used for writing tests.

It has the following configuration settings as constructor arguments:
$argumentMap - Associative array of data for mapping between provided URLs and the body content they should return.

The following code shows an example of how to use a remote request via cURL while falling back to files stored on the disk when an external request fails (probably due to network issues).

use AmpProject\Optimizer\DefaultConfiguration;
use AmpProject\Optimizer\TransformationEngine;
use AmpProject\RemoteRequest\CurlRemoteGetRequest;
use AmpProject\RemoteRequest\FallbackRemoteGetRequest;
use AmpProject\RemoteRequest\FilesystemRemoteGetRequest;

const FALLBACK_MAPPING = [
	'https://example.com/some_endpoint/' => __DIR__ . '/../fallback_files/some_endpoint.json',
];

$remoteRequest = new FallbackRemoteGetRequest(
	new CurlRemoteGetRequest(true, 5, 0),                  // 5 second timeout with no retries, and ...
	new FilesystemRemoteGetRequest(self::FALLBACK_MAPPING) // ... fall back to shipped files.
);

$transformationEngine = new TransformationEngine(new DefaultConfiguration(), $remoteRequest);

To build your own transport, you'll need to implement the AmpProject\RemoteGetRequest interface. For a more involved example of a custom transport or for integrating with your stack of choice, see the two implementations provided by the Amp for WordPress WordPress plugin:

Issues
  • Remove preload for AMP runtime again when boilerplate was removed

    Remove preload for AMP runtime again when boilerplate was removed

    Feature description

    When the AMP boilerplate is removed by the SSR logic, preloading of the AMP runtime is less critical and it seems to slow down other assets that are of higher priority in that scenario.

    The Optimizer should remove an existing preload for the AMP runtime again when the boilerplate was indeed removed.

    This was originally reported by @mehigh for an XWP project.

    /cc @sebastianbenz


    Do not alter or remove anything below. The following sections will be managed by moderators only.

    Acceptance criteria

    Implementation brief

    • Add small transformer that runs after the ServerSideRendering transformer and removes an existing AMP runtime preload iff the AMP boilerplate was removed.

    QA testing instructions

    Demo

    Changelog entry

    P1 SSR Performance Optimizer 
    opened by schlessera 20
  • SSR for `sizes` is not producing the same results as unoptimized version or version from amp-toolbox

    SSR for `sizes` is not producing the same results as unoptimized version or version from amp-toolbox

    Bug Description

    See test case in https://github.com/ampproject/amp-toolbox-php/issues/333#issuecomment-907393025.


    I'm having a problem with AMP-WP running in my installation. In both standard and transitional mode (with ?amp=1), it seems that AMP-WP remove the sizes attribute in the <amp-img> tag of the template. The WordPress theme that I created completely uses the AMPHTML format.

    I've also read the issue #https://github.com/ampproject/amp-wp/issues/4606, but what I'm experiencing right now is in the <amp-img> markup I wrote directly in the template, not from the time of conversion from <img> to <amp-img>.

    One example can be found in the markup (directly written in template, not from the_content() function) I use as follows:

    <amp-img
        data-hero 
        alt="Some Alt"
        srcset="http://xxxx/some-img-240x135.webp 240w, http://xxxx/some-img-480x270.webp 480w, http://xxxx/some-img-720x405.webp 720w, http://xxxx/some-img-960x540.webp 960w, http://xxxx/some-img-1200x675.webp 1200w"
        sizes="(max-width: 768px) 100vw, (max-width: 1023px) 64vw, (max-width: 1215px) 454px, 550px"
        src="http://xxxx/some-img-1200x675.webp"
        width="16"
        height="9"
        layout="responsive">
    </amp-img>
    

    For information, the value of the srcset attribute above is the output of the wp_get_attachment_image_srcset(), while the values ​​of the sizes attribute are entered directly into the template. Check this out: https://developer.wordpress.org/apis/handbook/responsive-images/#customizing-responsive-image-markup

    Once AMP-WP is enabled, the markup changes to:

    <amp-img
        data-hero
        alt="Some Alt"
        srcset="http://xxxx/some-img-240x135.webp 240w, http://xxxx/some-img-480x270.webp 480w, http://xxxx/some-img-720x405.webp 720w, http://xxxx/some-img-960x540.webp 960w, http://xxxx/some-img-1200x675.webp 1200w"
        src="http://xxxx/some-img-1200x675.webp"
        width="16"
        height="9"
        layout="responsive"
        i-amphtml-ssr
        id="i-amp-2"
        class="i-amphtml-layout-responsive i-amphtml-layout-size-defined"
        i-amphtml-layout="responsive">
    </amp-img>
    

    Note that the sizes attribute disappears from the markup above and makes the layout of the image not look as expected. On some other amp-imgs, I've also come across cases where the sizes values ​​in the template are changing from what they should be.

    In another case, the link rel="preload" line with the imgsizes attribute as below, which I placed in the <head>, is also missing when the AMP-WP plugin is activated.

    <link rel="preload" as="image" imagesrcset="http://xxxx/img/logo-80.png 80w, http://xxxx/img/logo-107.png 107w, http://xxxx/img/logo-240.png 240w, http://xxxx/img/logo-480.png 480w" imagesizes="(max-width: 768px) 80px, 107px">
    

    Expected Behaviour

    The plugin does not delete or change the values of the sizes attribute that has been set in the template

    As additional information, I have also tried to optimize with AMP Optimizer (node.js) on existing AMPHTML templates and did not encounter this problem.

    Is this an error caused by AMP-WP or an error from my own side?

    Thank you in advance.

    Additional context

    • WordPress version: 5.8
    • Plugin version: 2.1.3
    • AMP plugin template mode: Standard and Transitional (with ?amp=1)
    • PHP version: 8.0.9
    • OS: Manjaro Linux
    • Browser: Latest Chrome and Firefox

    Do not alter or remove anything below. The following sections will be managed by moderators only.

    Acceptance criteria

    Implementation brief

    QA testing instructions

    Demo

    Changelog entry

    SSR Optimizer 
    opened by kswro 16
  • Add MinifyHtml transformer

    Add MinifyHtml transformer

    • [x] collapses whitespace (except in pre, textarea, script tags)
    • [x] trims whitespace (except in pre, textarea tags)
    • [x] removes comments except if they match a given pattern
    • [x] minifies JSON
    • [ ] ~~minifies inline amp-script using terser~~ (see #260)

    closes #14

    opened by ediamin 15
  • Add AutoExtensions transformer

    Add AutoExtensions transformer

    This PR adds a new transformer that automatically imports missing extension scripts and removes unneeded ones.

    Depends on #100 Fixes #32

    Optimizer 
    opened by schlessera 13
  • Add ProtectEsiTags Document filter

    Add ProtectEsiTags Document filter

    The PHP DOMDocument alters the ESI tags that use colon in the tag and remove the esi: part and hence breaks the system. This PR adds a new Document filter to fix this issue.

    Fixes #202

    DOM 
    opened by ediamin 11
  • Document parsing fails when HTML start tag contains ⚡

    Document parsing fails when HTML start tag contains ⚡

    Hi there,

    we just started using the AMP Optimizer library and found a bug. In our admittedly strange setup, we use a class binding on the html element to set some marker classes. These class binding gets removed when using the AMP Optimizer.

    Input:

    <!doctype html>
    <html ⚡ [class]="mystate.class" class="blue">
    <head>
        <meta charset="utf-8">
        <title>My AMP Page</title>
        <link rel="canonical" href="self.html" />
        <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
        <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
        <script async src="https://cdn.ampproject.org/v0.js"></script>
        <script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>
        <style amp-custom>
            h1 {
                margin: 1rem;
            }
            .blue {
                background-color: blue;
            }
            .yellow {
                background-color: yellow;
            }
        </style>
    </head>
    <body>
    <amp-state id="mystate">
        <script type="application/json">
            {
                "class": "blue"
            }
        </script>
    </amp-state>
    <button on="tap:AMP.setState({mystate: {class: 'yellow'}})">
        yellow
    </button>
    <button on="tap:AMP.setState({mystate: {class: 'blue'}})">
        blue
    </button>
    </body>
    </html>
    

    Output (the relevant part):

    <!DOCTYPE html>
    <html ⚡ class="blue" i-amphtml-layout="" i-amphtml-no-boilerplate="" transformed="self;v=1">
    <head><meta charset="utf-8">
    

    So after the optimization, clicking on the buttons has no effect.

    We use the Optimizer with the default configuration (nothing special).

    DOM 
    opened by dritter 10
  • issue#37: change padding calculation from round to number_format

    issue#37: change padding calculation from round to number_format

    round is affected by the server locale. on de_DE.UTF8 for example the padding was used with comma instead of dot which prevented images from being rendered.

    Fixes #37

    Bug SSR 
    opened by SimonKraus 10
  • Skip escaped slashes when normalizing self-closing tags

    Skip escaped slashes when normalizing self-closing tags

    Hi! We encountered a problem on our site, when for example for a live article, we'll have a json+ld script part like this:

    "articleBody":"<p><img class=\"aligncenter size-large wp-image-807214\" src=\"https:\/\/images.frandroid.com\/wp-content\/uploads\/2020\/11\/sonos-beam-1200x799.jpg\" alt=\"\" width=\"1200\" height=\"799\" \/><br \/><\/p>\n"
    

    Let's take this simple <br \/>, the WordPress amp plugin will transform it to <br \> which cause a Google rich result validation errors, same thing for the img tag.

    So, here is a fix that handle this sanitized slash and remove it while removing the normal self-closing slash. Hope that's OK for you! Cheers.

    Bug DOM 
    opened by Alexays 10
  • Transformer: Preload images

    Transformer: Preload images

    Feature description

    Add a new transformer BrowserHints that adds browser hints as meta tags to the <head> node. The initial implementation should add the following hint:

    • Add preload hint for the first X images in the page

    The number X should be configurable by the user and default to 5.

    Related ampproject/amp-wp#4213


    Do not alter or remove anything below. The following sections will be managed by moderators only.

    Acceptance criteria

    • [ ] Generated HTML output from the Optimizer contains one or more resource hints that tell the browser to preload images
    • [ ] Images selected for preloading should be the first X in the main post, as well as anything needed for the site header (X defaults to 5)
    • [ ] Images smaller then Y pixels are ignored (Y defaults to 150)
    • [ ] Images with an aspect ratio larger than Z are ignored (Z defaults to 16)
    • [ ] The number of images X, the minimum size Y and the maximum aspect ratio Z are configurable by the user

    Implementation brief

    • See https://github.com/ampproject/amppackager/blob/releases/transformer/transformers/preloadimage.go

    QA testing instructions

    1. Verify: AMP optimization is enabled
    2. Go to the AMP version of a page that contains multiple images
    3. Use "View Source..." on the page to see the generated HTML markup
    4. Verify: The <head> element contains <link rel=preload ...> elements pointing to the first few images

    Demo

    Changelog entry

    On Hold P2 Performance 
    opened by schlessera 8
  • Adapt PreloadHeroImages to support new OptimizeHeroImage configuration arguments

    Adapt PreloadHeroImages to support new OptimizeHeroImage configuration arguments

    The Node.JS implementation deprecated PreloadHeroImages in favor of a new OptimizeHeroImages with more sophisticated configuration arguments.

    The PHP version needs to be adapted in order to support these via the imported spec tests.

    See https://github.com/ampproject/amp-toolbox/pull/1133

    Testing SSR Optimizer 
    opened by schlessera 8
Releases(0.8.2)
  • 0.8.2(Oct 27, 2021)

  • 0.8.1(Oct 26, 2021)

  • 0.8.0(Oct 23, 2021)

    Changes

    • Add AutoExtensions transformer [#210], [#381]
    • Add OptimizeViewport transformer [#373]
    • Add ProtectEsiTags Document filter [#343], [#369]
    • Turn TagWithExtensionSpec into an abstract class [#358] (⚠breaking change)
    • Add $headers argument to RemoteGetRequest::get() [#356]
    • Add Attribute::DEFER [#366]
    • Refactor CLI option retrieval to improve typing [#357] (⚠breaking change)
    • Update documentation for OptimizeHeroImages [#337]

    Bugfixes

    • Only merge CSS rules for display:none [#335]
    • Fix PHP 8.1 compatibility issues [#374]

    Dependencies

    • Regenerate validator specs and update SpecGenerator and Dumper classes [#384]

    Meta

    • Fix outdated PHPStan version in GHA workflow [#368]
    • Sync spec test suite [#375]
    Source code(tar.gz)
    Source code(zip)
  • 0.7.0(Aug 27, 2021)

    Changes

    • Add MinifyHtml transformer [#251]
    • Add MinifyHtml to list of default transformers [#330]
    • Add amp-script minification [#281]
    • Optimize mustache template detection logic in MinifyHtml transformer [#309]
    • Refactor Dom\Document class using filters [#271] (⚠breaking change)
    • Consume bundles.config.extensions.json to amend TagWithExtensionSpec classes with extension version meta [#297]
    • Apply slot="i-amphtml-svc" to i-amphtml-sizer elements. [#316]
    • Allow CSS max byte count enforcement in TransformedIdentifier transformer to be configured [#319]

    Bugfixes

    • Preserve mustache tag wrapped in comment inside template element [#305]
    • Add LIBXML_SCHEMA_CREATE flag in Dom\Document filter [#322]
    • Fix missing amp binary in the distribution package [#310]

    Dependencies

    • Update validator spec - 2021-07-29 [#296]
    • Regenerate validator spec with new Extension constants [#298]
    • Update sirbrillig/phpcs-variable-analysis requirement from 2.11.1 to 2.11.2 [#273]

    Meta

    • Migrate PHPUnit configuration file over to latest schema [#332]
    • Prepare tests for running in process isolation [#329]
    • Add randomly-ordered unit test run to GHA workflow [#328]
    • Disable the Codecov annotations check [#307]
    • Sync spec test suite [#266, #278, #287]
    Source code(tar.gz)
    Source code(zip)
  • 0.6.0(Oct 27, 2021)

    Changes

    • Add validator spec via code generation [#100, #221]
    • Add option to detect malformed byte sequences [#200]
    • Add comment about mb_detect_encoding() performance [#186]
    • Add SSR support for fluid layout [#181]
    • Add resource hints transformers [#179]
    • Deprecate PreloadHeroImage for OptimizeHeroImages [#262]
    • Disable AMP runtime preloading by default [#261]
    • Add support for amp-onerror on transformed pages (with ESM scripts) [#211, #218]

    Bugfixes

    • Correct sort of transformers to fix preload hero images [#209]
    • Skip broken boilerplate error handler spec test [#229]
    • Use maxBytes from Spec instead of hard-coding it [#231]
    • Point OptimizeAmpBind spec tests to valid instead of experimental [#174]

    Dependencies

    • Update validator spec - 2021-06-24 [#258]
    • Sync local fallback files [#216]
    • Sync spec test suite [#166, #206, #215, #235, #240, #244, #257]
    • Update sirbrillig/phpcs-variable-analysis requirement from 2.11.0 to 2.11.1 [#250]
    • Update yoast/phpunit-polyfills requirement from ^0.2.0 to ^0.2.0 || ^1.0.0 [#249]

    Meta

    • Upgrade to GitHub-native Dependabot [#172]
    • Add codecov token [#230]
    • Add WPCS for inline doc and comments [#254]
    Source code(tar.gz)
    Source code(zip)
  • 0.5.2(May 6, 2021)

  • 0.5.1(May 4, 2021)

  • 0.5.0(Apr 29, 2021)

    Changes

    • Add OptimizeAmpBind transformer [#162]
    • Add toArray() to transformer configuration [#154]
    • Add missing constants for AMP-WP #6112 [#169]

    Meta

    • Replace Codecov bash uploader with GHA counterpart [#158]
    • Fix covers annotation for CLI exceptions and improve overall coverage [#167]
    Source code(tar.gz)
    Source code(zip)
  • 0.4.0(Apr 13, 2021)

    Changes

    • Add RewriteAmpUrls transformer [#119]
    • Add AmpBoilerplateErrorHandler transformer [#148]
    • Make Configuration extensible [#150] (⚠breaking change)
    • Include candidate hero images after paragraphs and persist loading attribute on noscript > img fallback [#149]
    • Add more dynamic logging behavior [#146]
    • Add crossorigin=anonymous to nomodule scripts [#140]
    • Prevent SSR for amp-img occurring after second paragraph [#141]

    Bugfixes

    • Fix SSR for nested AMP components [#122]
    • Fix tiny image detection inside responsive parent [#144]
    • Check hero image media attribute before srcset [#136]
    • Only construct preload if it is needed [#135]
    • Fix deduplication of AMP scripts and improve ReorderHead transformer [#130]
    • Calling curl_errno() before closing the connection throws an error [#128]

    Dependencies

    Meta

    • Add autoloader scenario for package as dependency [#145]
    • Replace PHPCS exclude-pattern with explicit file includes [#133]
    • Add amp binary to Composer "bin" directive [#139]
    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(Mar 29, 2021)

    Changes

    • Add CLI tool [#92]

    Bugfixes

    • Remove hard-coded isTiny() result for intrinsic layout [#115]
    • Prevent loading=lazy attribute from being removed from amp-story-player poster img [#106]

    Dependencies

    Source code(tar.gz)
    Source code(zip)
  • 0.2.0(Mar 18, 2021)

    Changes

    • Add support for a data-hero-candidate attribute [#86]
    • Enforce CSS byte count limit across all inline style additions and inline object-* attributes [#29]
    • Support dimensions with units [#49]
    • Use magic $html property in normalizeHtmlAttributes() [#30]

    Bugfixes

    • Fix wrongly URL-encoded src attributes in Mustache templates [#104]
    • Fix broken encoding when using document fragments [#99]
    • Parse document correctly when AMP emoji is used [#79]
    • Secure HTML entities within template text [#54]
    • Fix @covers annotations [#46]
    • Fix broken spec test for converting intrinsic layout [#39]
    • Change padding calculation from round() to number_format() [#38]
    • Skip escaped slashes when normalizing self-closing tags [#36]

    Dependencies

    • Sync local fallback files [#35, #66, #70, #77, #80, #81, #88, #93, #94, #95, #97]
    • Sync spec test suite [#45, #56, #71, #76#85]
    • Update composer command in docs [#23]
    • Update civicrm/composer-downloads-plugin requirement from ^2.1 to ^2.1 || ^3.0 [#3]
    • Update dealerdirect/phpcodesniffer-composer-installer requirement from 0.7.0 to 0.7.1 [#43]
    • Update phpcompatibility/phpcompatibility-wp requirement from 2.1.0 to 2.1.1 [#72]
    • Update sirbrillig/phpcs-variable-analysis requirement from 2.9.0 to 2.11.0 [#44, #52, #101]

    Meta

    • Add automerge workflow [#96]
    • Add code coverage collection [#50]
    • Add GHA workflow to sync test suite and create PR [#58]
    • Avoid running sync on composer update [#64]
    • Improve PHPUnit integration [#40]
    • Raise PHPStan level to 5 [#42]
    • Run tests on all pushes [#78]
    Source code(tar.gz)
    Source code(zip)
  • 0.1.1(Dec 11, 2020)

  • 0.1.0(Nov 6, 2020)

PHP Text Analysis is a library for performing Information Retrieval (IR) and Natural Language Processing (NLP) tasks using the PHP language

php-text-analysis PHP Text Analysis is a library for performing Information Retrieval (IR) and Natural Language Processing (NLP) tasks using the PHP l

null 434 Nov 19, 2021
php-echarts is a php library for the echarts 5.0.

php-echarts 一款支持Apache EChart5.0+图表的php开发库 优先ThinkPHP5/6的开发及测试。 Apache EChart5.0已经最新发布,在视觉效果、动画效果和大数据展示方面已经远超之前的版本; 故不考虑EChart5.0之前版本的兼容问题;建议直接尝试5.0+

youyiio 2 Oct 12, 2021
Minimalist PHP frame for Core-Library, for Developing PHP application that gives you the full control of your application.

LazyPHP lightweight Pre-Made Frame for Core-library Install Run the below command in your terminal $ composer create-project ryzen/lazyphp my-first-pr

Ry-Zen 3 Oct 19, 2021
:date: The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects

sabre/vobject The VObject library allows you to easily parse and manipulate iCalendar and vCard objects using PHP. The goal of the VObject library is

sabre.io 503 Nov 19, 2021
Small convention based CQRS library for PHP

LiteCQRS for PHP Small naming-convention based CQRS library for PHP (loosely based on LiteCQRS for C#) that relies on the MessageBus, Command, EventSo

Benjamin Eberlei 566 Nov 12, 2021
Experimental library for forking PHP

Spork: PHP on a Fork <?php $manager = new Spork\ProcessManager(); $manager->fork(function() { // do something in another process! return 'Hel

Kris Wallsmith 590 Nov 23, 2021
Collection pipeline library for PHP

Knapsack Collection pipeline library for PHP Knapsack is a collection library for PHP >= 5.6 that implements most of the sequence operations proposed

Dušan Kasan 523 Oct 12, 2021
A PHP library to play with the Raspberry PI's GPIO pins

php-gpio php-gpio is a simple PHP library to play with the Raspberry PI's GPIO pins. It provides simple tools such as reading & writing to pins. [UPDA

Ronan Guilloux 266 Aug 2, 2021
PHP library for dealing with European VAT

ibericode/vat This is a simple PHP library to help you deal with Europe's VAT rules. Fetch VAT rates for any EU member state using ibericode/vat-rates

ibericode 363 Nov 27, 2021
iOS passbook library for PHP 5.4+

PHP PASSBOOK LIBRARY What is Passbook? Passbook is an application in iOS that allows users to store coupons, boarding passes, event tickets, store car

Eymen Gunay 244 Oct 18, 2021
Sslurp is a simple library which aims to make properly dealing with SSL in PHP suck less.

Sslurp v1.0 by Evan Coury Introduction Dealing with SSL properly in PHP is a pain in the ass and completely insecure by default. Sslurp aims to make i

Evan Coury 62 Apr 29, 2021
A framework agnostic PHP library to build chat bots

BotMan If you want to learn how to create reusable PHP packages yourself, take a look at my upcoming PHP Package Development video course. About BotMa

BotMan 5.5k Nov 17, 2021
Lock library to provide serialized execution of PHP code.

Requirements | Installation | Usage | License and authors | Donations php-lock/lock This library helps executing critical code in concurrent situation

null 819 Nov 15, 2021
PHP Machine Learning library

PHP-ML - Machine Learning library for PHP Fresh approach to Machine Learning in PHP. Algorithms, Cross Validation, Neural Network, Preprocessing, Feat

Jorge Casas 119 Nov 15, 2021
A framework agnostic, multi-gateway payment processing library for PHP 5.6+

Omnipay An easy to use, consistent payment processing library for PHP Omnipay is a payment processing library for PHP. It has been designed based on i

The League of Extraordinary Packages 5.4k Nov 22, 2021
PHP library providing retry functionality with multiple backoff strategies and jitter support

PHP Backoff Easily wrap your code with retry functionality. This library provides: 4 backoff strategies (plus the ability to use your own) Optional ji

Signature Tech Studio 107 Nov 25, 2021
Currency is a simple PHP library for current and historical currency exchange rates & crypto exchange rates. based on the free API exchangerate.host

Currency Currency is a simple PHP library for current and historical currency exchange rates & crypto exchange rates. based on the free API exchangera

Amr Shawky 9 Oct 17, 2021
A high-level machine learning and deep learning library for the PHP language.

Rubix ML A high-level machine learning and deep learning library for the PHP language. Developer-friendly API is delightful to use 40+ supervised and

Rubix 1.5k Nov 25, 2021
PHP library to create and validate html forms

FormManager Note: this is the documentation of FormManager 6.x For v5.x version Click here Installation: This package requires PHP>=7.1 and is availab

Oscar Otero 139 Sep 29, 2021