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

Fix deduplication of AMP scripts and improve ReorderHead transformer #130

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Amp.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,11 @@ public static function isRuntimeScript(DOMNode $node)
}

if (
// @TODO Compare performance against single regex.
substr($src, -6) !== '/v0.js'
&& substr($src, -7) !== '/v0.mjs'
&& substr($src, -14) !== '/amp4ads-v0.js'
&& substr($src, -15) !== '/amp4ads-v0.mjs'
schlessera marked this conversation as resolved.
Show resolved Hide resolved
) {
return false;
}
Expand Down
73 changes: 30 additions & 43 deletions src/Optimizer/Transformer/ReorderHead.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ final class ReorderHead implements Transformer
private $noscript = null;
private $others = [];
private $resourceHintLinks = [];
private $scriptAmpRuntime = null;
private $scriptAmpViewer = null;
private $scriptAmpRuntime = [];
private $scriptAmpViewer = [];
private $scriptNonRenderDelayingExtensions = [];
private $scriptRenderDelayingExtensions = [];
private $styleAmpBoilerplate = null;
Expand All @@ -91,7 +91,6 @@ public function transform(Document $document, ErrorCollection $errors)
$this->registerNode($node);
}

$this->deduplicateAndSortCustomNodes();
$this->appendToHead($document);
}

Expand Down Expand Up @@ -127,7 +126,7 @@ private function registerNode(DOMNode $node)
$this->registerLink($node);
break;
case Tag::NOSCRIPT:
$this->noscript = $node;
$this->noscript = $node; // @todo Make this an array.
break;
default:
$this->others[] = $node;
Expand Down Expand Up @@ -156,32 +155,37 @@ private function registerMeta(Element $node)
*/
private function registerScript(Element $node)
{
$nomodule = (int)$node->hasAttribute('nomodule');

if (Amp::isRuntimeScript($node)) {
$this->scriptAmpRuntime = $node;
$this->scriptAmpRuntime[$nomodule] = $node;
return;
}

if (Amp::isViewerScript($node)) {
$this->scriptAmpViewer = $node;
$this->scriptAmpViewer[$nomodule] = $node;
return;
}

if ($node->hasAttribute(Attribute::CUSTOM_ELEMENT)) {
$name = $node->getAttribute(Attribute::CUSTOM_ELEMENT);
if (Amp::isRenderDelayingExtension($node)) {
$this->scriptRenderDelayingExtensions[] = $node;
$this->scriptRenderDelayingExtensions[$name][$nomodule] = $node;
return;
}
$this->scriptNonRenderDelayingExtensions[] = $node;
$this->scriptNonRenderDelayingExtensions[$name][$nomodule] = $node;
return;
}

if ($node->hasAttribute(Attribute::CUSTOM_TEMPLATE)) {
$this->scriptNonRenderDelayingExtensions[] = $node;
$name = $node->getAttribute(Attribute::CUSTOM_TEMPLATE);
$this->scriptNonRenderDelayingExtensions[$name][$nomodule] = $node;
return;
}

if ($node->hasAttribute(Attribute::HOST_SERVICE)) {
$this->scriptNonRenderDelayingExtensions[] = $node;
$name = $node->getAttribute(Attribute::HOST_SERVICE);
$this->scriptNonRenderDelayingExtensions[$name][$nomodule] = $node;
return;
}

Expand Down Expand Up @@ -248,6 +252,7 @@ private function registerLink(Element $node)
|| $this->containsWord($rel, Attribute::REL_PREFETCH)
|| $this->containsWord($rel, Attribute::REL_DNS_PREFETCH)
|| $this->containsWord($rel, Attribute::REL_PRECONNECT)
|| $this->containsWord($rel, Attribute::REL_MODULEPRELOAD)
) {
if ($this->isHintForAmp($node)) {
$this->ampResourceHints[] = $node;
Expand All @@ -260,25 +265,6 @@ private function registerLink(Element $node)
$this->others[] = $node;
}

/**
* Get the name of the custom node or template.
*
* @param Element $node Node to get the name of.
* @return string Name of the custom node or template. Empty string if none found.
*/
private function getName(Element $node)
{
if ($node->hasAttribute(Attribute::CUSTOM_ELEMENT)) {
return $node->getAttribute(Attribute::CUSTOM_ELEMENT);
}

if ($node->hasAttribute(Attribute::CUSTOM_TEMPLATE)) {
return $node->getAttribute(Attribute::CUSTOM_TEMPLATE);
}

return '';
}

/**
* Append all registered nodes to the <head> node.
*
Expand Down Expand Up @@ -314,27 +300,28 @@ private function appendToHead(Document $document)
$node = $document->importNode($this->$category);
$document->head->appendChild($node);
} elseif (is_array($this->$category)) {
// @todo Maybe sort by attribute-name, attribute-value?
foreach ($this->$category as $node) {
$node = $document->importNode($node);
$document->head->appendChild($node);
}
$this->recursiveKeySort($this->$category);
array_walk_recursive(
$this->$category,
static function (DOMNode $node) use ($document) {
$node = $document->importNode($node);
$document->head->appendChild($node);
}
);
}
}
}

/**
* Deduplicate and sort custom extensions.
* Sort array keys recursively.
*
* @param array|mixed $item Item.
*/
private function deduplicateAndSortCustomNodes()
private function recursiveKeySort(&$item)
{
foreach (['scriptRenderDelayingExtensions', 'scriptNonRenderDelayingExtensions'] as $set) {
$sortedNodes = [];
foreach ($this->$set as $node) {
$sortedNodes[$this->getName($node)] = $node;
}
ksort($sortedNodes);
$this->$set = array_values($sortedNodes);
if (is_array($item)) {
ksort($item);
array_walk($item, [$this, 'recursiveKeySort']);
}
}

Expand Down
27 changes: 1 addition & 26 deletions src/Optimizer/Transformer/RewriteAmpUrls.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public function __construct(TransformerConfiguration $configuration)
public function transform(Document $document, ErrorCollection $errors)
{
$host = $this->calculateHost();
$referenceNode = $this->findReferenceNode($document);
$referenceNode = $document->viewport;

$preloadNodes = array_filter($this->collectPreloadNodes($document, $host));
foreach ($preloadNodes as $preloadNode) {
Expand Down Expand Up @@ -347,29 +347,4 @@ private function addMeta(Document $document, $name, $content)
$firstScript = $document->xpath->query('./script', $document->head)->item(0);
$document->head->insertBefore($meta, $firstScript);
}

/**
* Find the reference node to use for adding elements.
*
* It fetches the last <link> after the viewport as the reference node, falling back to the viewport if needed.
*
* @param Document $document Document to find the reference node in.
* @return Element|null Reference node to use, or null if none found.
*/
private function findReferenceNode(Document $document)
{
$referenceNode = $document->viewport;

while (
$referenceNode !== null
&&
$referenceNode->nextSibling instanceof Element
&&
$referenceNode->nextSibling->tagName === Tag::LINK
) {
$referenceNode = $referenceNode->nextSibling;
}

return $referenceNode;
}
}
2 changes: 1 addition & 1 deletion tests/Optimizer/Transformer/ReorderHeadTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ public function dataTransform()
// (5) <script> tags that are render delaying
TestMarkup::SCRIPT_AMPEXPERIMENT .
// (6) <script> tags for remaining extensions
TestMarkup::SCRIPT_AMPMRAID .
TestMarkup::SCRIPT_AMPAUDIO .
TestMarkup::SCRIPT_AMPMRAID .
TestMarkup::SCRIPT_AMPMUSTACHE .
// (7) <link> tag for favicons
TestMarkup::LINK_FAVICON .
Expand Down
2 changes: 1 addition & 1 deletion tests/Optimizer/Transformer/RewriteAmpUrlsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ public function testOrderingOfScripts()
$this->assertCount(0, $errors);
$this->assertSimilarMarkup(
TestMarkup::DOCTYPE . '<html amp><head>' . TestMarkup::META_CHARSET . TestMarkup::META_VIEWPORT
. TestMarkup::LINK_CANONICAL . TestMarkup::LINK_GOOGLE_FONT_PRECONNECT
. '<link as="script" crossorigin="anonymous" href="https://cdn.ampproject.org/v0.mjs" rel="modulepreload">'
. TestMarkup::LINK_CANONICAL . TestMarkup::LINK_GOOGLE_FONT_PRECONNECT
. '<script async nomodule src="https://cdn.ampproject.org/v0.js"></script>'
. '<script async crossorigin="anonymous" src="https://cdn.ampproject.org/v0.mjs" type="module"></script>'
. '</head><body></body></html>',
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.