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

Send preload links via HTTP Link headers in addition to LINK tags #1323

Merged
merged 10 commits into from
Jul 8, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ public function add_link( array $attributes, ?int $minimum_viewport_width, ?int
}

/**
* Get adjacent-deduplicated links.
* Prepare links by deduplicating adjacent links and adding media attributes.
AhmarZaidi marked this conversation as resolved.
Show resolved Hide resolved
*
* When two links are identical except for their minimum/maximum widths which are also consecutive, then merge them
* together.
* together. Also, add media attributes to the links.
*
* @return array<int, Link> Links with adjacent-duplicates merged together.
* @return array<int, Link> Prepared links with adjacent-duplicates merged together and media attributes added.
*/
private function get_adjacent_deduplicated_links(): array {
private function prepare_links(): array {
AhmarZaidi marked this conversation as resolved.
Show resolved Hide resolved
$links = $this->links;

usort(
Expand All @@ -100,7 +100,8 @@ static function ( array $a, array $b ): int {
}
);

return array_reduce(
// Deduplicating adjacent links.
AhmarZaidi marked this conversation as resolved.
Show resolved Hide resolved
$prepared_links = array_reduce(
$links,
/**
* Reducer.
Expand All @@ -125,7 +126,7 @@ static function ( array $carry, array $link ): array {
) {
$last_link['maximum_viewport_width'] = max( $last_link['maximum_viewport_width'], $link['maximum_viewport_width'] );

// Update the last link with the new maximum viewport with.
// Update the last link with the new maximum viewport width.
$carry[ count( $carry ) - 1 ] = $last_link;
} else {
$carry[] = $link;
Expand All @@ -134,6 +135,20 @@ static function ( array $carry, array $link ): array {
},
array()
);

// Add media attributes to the deduplicated links.
foreach ( $prepared_links as &$link ) {
$media_attributes = array( 'screen' );
if ( null !== $link['minimum_viewport_width'] && $link['minimum_viewport_width'] > 0 ) {
$media_attributes[] = sprintf( '(min-width: %dpx)', $link['minimum_viewport_width'] );
}
if ( null !== $link['maximum_viewport_width'] && PHP_INT_MAX !== $link['maximum_viewport_width'] ) {
$media_attributes[] = sprintf( '(max-width: %dpx)', $link['maximum_viewport_width'] );
}
$link['attributes']['media'] = implode( ' and ', $media_attributes );
}

return $prepared_links;
}

/**
Expand All @@ -144,16 +159,7 @@ static function ( array $carry, array $link ): array {
public function get_html(): string {
$link_tags = array();

foreach ( $this->get_adjacent_deduplicated_links() as $link ) {
$media_features = array( 'screen' );
if ( null !== $link['minimum_viewport_width'] && $link['minimum_viewport_width'] > 0 ) {
$media_features[] = sprintf( '(min-width: %dpx)', $link['minimum_viewport_width'] );
}
if ( null !== $link['maximum_viewport_width'] && PHP_INT_MAX !== $link['maximum_viewport_width'] ) {
$media_features[] = sprintf( '(max-width: %dpx)', $link['maximum_viewport_width'] );
}
$link['attributes']['media'] = implode( ' and ', $media_features );

foreach ( $this->prepare_links() as $link ) {
$link_tag = '<link data-od-added-tag rel="preload"';
foreach ( $link['attributes'] as $name => $value ) {
$link_tag .= sprintf( ' %s="%s"', $name, esc_attr( $value ) );
Expand All @@ -166,6 +172,31 @@ public function get_html(): string {
return implode( '', $link_tags );
}

/**
* Constructs the Link HTTP response header.
*
* @return string|null Link HTTP response header, or null if there are none.
*/
public function get_response_header(): ?string {
$link_headers = array();

foreach ( $this->prepare_links() as $link ) {
$link_header = '<' . esc_url_raw( $link['attributes']['href'] ?? '' ) . '>; rel="preload"';
AhmarZaidi marked this conversation as resolved.
Show resolved Hide resolved
foreach ( $link['attributes'] as $name => $value ) {
if ( 'href' !== $name ) {
$link_header .= sprintf( '; %s="%s"', $name, rawurlencode( $value ) );
}
AhmarZaidi marked this conversation as resolved.
Show resolved Hide resolved
}

$link_headers[] = $link_header;
}
if ( count( $link_headers ) === 0 ) {
return null;
}

AhmarZaidi marked this conversation as resolved.
Show resolved Hide resolved
return 'Link: ' . implode( ', ', $link_headers );
}

/**
* Counts the links.
*
Expand Down
6 changes: 5 additions & 1 deletion plugins/optimization-detective/optimization.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,12 @@ function od_optimize_template_output_buffer( string $buffer ): string {
$generator->next();
}

// Inject any preload links at the end of the HEAD.
// Send any preload links in a Link response header and in a LINK tag injected at the end of the HEAD.
if ( count( $preload_links ) > 0 ) {
$response_header_links = $preload_links->get_response_header();
if ( ! is_null( $response_header_links ) && ! headers_sent() ) {
header( $response_header_links, false );
}
$walker->append_head_html( $preload_links->get_html() );
}

Expand Down
Loading