Skip to content

Commit

Permalink
AMP options + fetchpriority
Browse files Browse the repository at this point in the history
- added support for new banner option `fetchpriority`
- added support for banner options defined in the AMP administration
- the option `loading` is now processed as an expression. The option `loading-offset` is ignored
- updated docs
  • Loading branch information
tg666 committed Sep 19, 2024
1 parent dfb2c60 commit a1ef524
Show file tree
Hide file tree
Showing 43 changed files with 679 additions and 112 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Added support for new banner option `fetchpriority`.
- Added support for banner options defined in the AMP administration.

### Changed
- The option `loading` is now processed as an expression. The option `loading-offset` is ignored.
- Updated docs.

## [1.2.0] - 2024-04-04
### Added
- Added integration of the new AMP API fields `mode` and `dimensions`.
Expand Down
39 changes: 34 additions & 5 deletions docs/integration-without-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,24 +261,53 @@ echo $renderer->renderClientSide(new Position('homepage.top'), [], [
```

A special case is a position of type `multiple`, where it may be desirable to lazily load all banners except the first.
This can be achieved by adding the option `loading-offset`, whose value specifies from which banner the attribute `loading` should be rendered.
his can be achieved with the following expression:

```php
# server-side rendering:
echo $renderer->render($response->getPosition('homepage.top'), [], [
'loading' => 'lazy',
'loading-offset' => 1,
'loading' => '>=1:lazy',
]);

# client-side rendering:
echo $renderer->renderClientSide(new Position('homepage.top'), [], [
'loading' => 'lazy',
'loading-offset' => 1,
'loading' => '>=1:lazy',
]);
```

If you prefer a different implementation of lazy loading, it is possible to use own templates instead of the default ones and integrate a different solution in these templates.

#### Fetch priority of image banners

The [fetchpriority](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/fetchPriority) attribute can be set for image and embed banners using the `fetchpriority` option.

```php
# server-side rendering:
echo $renderer->render($response->getPosition('homepage.top'), [], [
'fetchpriority' => 'high',
]);

# client-side rendering:
echo $renderer->renderClientSide(new Position('homepage.top'), [], [
'fetchpriority' => 'high',
]);
```

In the case of a `multiple` position, it may be required that the first banner have a fetch priority of `high` and the others `low`.
This can be achieved with the following expression:

```php
# server-side rendering:
echo $renderer->render($response->getPosition('homepage.top'), [], [
'fetchpriority' => '0:high,low',
]);

# client-side rendering:
echo $renderer->renderClientSide(new Position('homepage.top'), [], [
'fetchpriority' => '0:high,low',
]);
```

### Templates overwriting

The default templates are written as `.phtml` templates and can be found [here](../src/Renderer/Phtml/Templates). Templates can be also overwritten:
Expand Down
6 changes: 6 additions & 0 deletions src/Bridge/Nette/DI/AmpClientExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
use SixtyEightPublishers\AmpClient\Bridge\Nette\DI\Config\RendererConfig;
use SixtyEightPublishers\AmpClient\Bridge\Nette\NetteCacheStorage;
use SixtyEightPublishers\AmpClient\ClientConfig;
use SixtyEightPublishers\AmpClient\Expression\ExpressionParser;
use SixtyEightPublishers\AmpClient\Expression\ExpressionParserInterface;
use SixtyEightPublishers\AmpClient\Http\Cache\CacheStorageInterface;
use SixtyEightPublishers\AmpClient\Http\Cache\NoCacheStorage;
use SixtyEightPublishers\AmpClient\Http\HttpClientFactory;
Expand Down Expand Up @@ -153,6 +155,10 @@ public function loadConfiguration(): void
'cacheStorage' => new Reference($this->prefix('cacheStorage')),
]);

$builder->addDefinition($this->prefix('expressionParser'))
->setType(ExpressionParserInterface::class)
->setFactory(ExpressionParser::class);

$builder->addDefinition($this->prefix('renderer.rendererBridge'))
->setAutowired(false)
->setType(RendererBridgeInterface::class)
Expand Down
98 changes: 98 additions & 0 deletions src/Expression/ExpressionParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\AmpClient\Expression;

use function array_map;
use function explode;
use function preg_match;
use function trim;

final class ExpressionParser implements ExpressionParserInterface
{
private const ExpressionRegex = '/^(?:(?<INTERVAL_FROM>\d+)-(?<INTERVAL_TO>\d+):|(?<EQ>\d+):|<(?<LT>\d+):|<=(?<LTE>\d+):|>(?<GT>\d+):|>=(?<GTE>\d+):)?(?<VALUE>[^:\s]+)$/';

/** @var array<string, ParsedExpression> */
private array $cache = [];

public function parseExpression(string $expression): ParsedExpression
{
if (isset($this->cache[$expression])) {
return $this->cache[$expression];
}

$parts = array_map(
static fn (string $part) => trim($part),
explode(',', $expression),
);
$rules = [];

foreach ($parts as $part) {
if (!preg_match(self::ExpressionRegex, $part, $matches)) {
continue;
}

$value = $matches['VALUE'] ?? null;

if (null === $value) {
continue;
}

switch (true) {
case '' !== ($matches['INTERVAL_FROM'] ?? '') && '' !== ($matches['INTERVAL_TO'] ?? ''):
$rules[] = ExpressionRule::range(
(int) $matches['INTERVAL_FROM'],
(int) $matches['INTERVAL_TO'],
$value,
);

break;
case '' !== ($matches['EQ'] ?? ''):
$rules[] = ExpressionRule::eq(
(int) $matches['EQ'],
$value,
);

break;
case '' !== ($matches['LT'] ?? ''):
$rules[] = ExpressionRule::lt(
(int) $matches['LT'],
$value,
);

break;
case '' !== ($matches['LTE'] ?? ''):
$rules[] = ExpressionRule::lte(
(int) $matches['LTE'],
$value,
);

break;
case '' !== ($matches['GT'] ?? ''):
$rules[] = ExpressionRule::gt(
(int) $matches['GT'],
$value,
);

break;
case '' !== ($matches['GTE'] ?? ''):
$rules[] = ExpressionRule::gte(
(int) $matches['GTE'],
$value,
);

break;
default:
$rules[] = ExpressionRule::positive($value);
}
}

return $this->cache[$expression] = new ParsedExpression($rules);
}

public function evaluateExpression(string $expression, int $index): ?string
{
return $this->parseExpression($expression)->evaluate($index);
}
}
12 changes: 12 additions & 0 deletions src/Expression/ExpressionParserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\AmpClient\Expression;

interface ExpressionParserInterface
{
public function parseExpression(string $expression): ParsedExpression;

public function evaluateExpression(string $expression, int $index): ?string;
}
101 changes: 101 additions & 0 deletions src/Expression/ExpressionRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\AmpClient\Expression;

use Closure;

final class ExpressionRule
{
public string $value;

/** @var Closure(int $index): bool */
private Closure $matcher;

/**
* @param Closure(int $index): bool $matcher
*/
private function __construct(
string $value,
Closure $matcher
) {
$this->value = $value;
$this->matcher = $matcher;
}

public static function eq(int $eq, string $value): self
{
return new self(
$value,
function (int $index) use ($eq): bool {
return $index === $eq;
},
);
}

public static function range(int $from, int $to, string $value): self
{
return new self(
$value,
function (int $index) use ($from, $to): bool {
return $index >= $from && $index <= $to;
},
);
}

public static function lt(int $lt, string $value): self
{
return new self(
$value,
function (int $index) use ($lt): bool {
return $index < $lt;
},
);
}

public static function lte(int $lte, string $value): self
{
return new self(
$value,
function (int $index) use ($lte): bool {
return $index <= $lte;
},
);
}

public static function gt(int $gt, string $value): self
{
return new self(
$value,
function (int $index) use ($gt): bool {
return $index > $gt;
},
);
}

public static function gte(int $gte, string $value): self
{
return new self(
$value,
function (int $index) use ($gte): bool {
return $index >= $gte;
},
);
}

public static function positive(string $value): self
{
return new self(
$value,
function (): bool {
return true;
},
);
}

public function matches(int $index): bool
{
return ($this->matcher)($index);
}
}
42 changes: 42 additions & 0 deletions src/Expression/ParsedExpression.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace SixtyEightPublishers\AmpClient\Expression;

use function array_key_exists;

final class ParsedExpression
{
/** @var list<ExpressionRule> */
private array $rules;

/** @var array<string, string|null> */
private array $cache = [];

/**
* @param list<ExpressionRule> $rules
*/
public function __construct(
array $rules
) {
$this->rules = $rules;
}

public function evaluate(int $index): ?string
{
$cacheKey = 'i_' . $index;

if (array_key_exists($cacheKey, $this->cache)) {
return $this->cache[$cacheKey];
}

foreach ($this->rules as $rule) {
if ($rule->matches($index)) {
return $this->cache[$cacheKey] = $rule->value;
}
}

return $this->cache[$cacheKey] = null;
}
}
7 changes: 3 additions & 4 deletions src/Renderer/Latte/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace SixtyEightPublishers\AmpClient\Renderer\Latte;

use SixtyEightPublishers\AmpClient\Renderer\Options;
use SixtyEightPublishers\AmpClient\Request\ValueObject\Position as RequestPosition;

final class Helpers
Expand All @@ -25,15 +26,13 @@ public static function createResourceAttributes(RequestPosition $position): arra
}

/**
* @param array<string, scalar> $options
*
* @return array<string, scalar>
*/
public static function createOptionAttributes(array $options): array
public static function createOptionAttributes(Options $options): array
{
$attributes = [];

foreach ($options as $name => $value) {
foreach ($options->toArray() as $name => $value) {
$attributes['data-amp-option-' . $name] = $value;
}

Expand Down
Loading

0 comments on commit a1ef524

Please sign in to comment.