Skip to content

Commit

Permalink
Merge pull request #1059 from WordPress/feature/lazy-embeds
Browse files Browse the repository at this point in the history
Merge Embed Optimizer into trunk
  • Loading branch information
westonruter committed Mar 20, 2024
2 parents 9e3e4dc + 013acb9 commit 7385b1e
Show file tree
Hide file tree
Showing 18 changed files with 457 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Expand Up @@ -59,3 +59,7 @@
# Plugin: Dominant Color Images
/plugins/dominant-color-images @pbearne @spacedmonkey
/tests/plugins/dominant-color-images @pbearne @spacedmonkey

# Plugin: Embed Optimizer
/plugins/embed-optimizer @adamsilverstein @westonruter
/tests/plugins/embed-optimizer @adamsilverstein @westonruter
1 change: 1 addition & 0 deletions .wp-env.json
Expand Up @@ -4,6 +4,7 @@
".",
"./plugins/auto-sizes",
"./plugins/dominant-color-images",
"./plugins/embed-optimizer",
"./plugins/speculation-rules",
"./plugins/webp-uploads"
],
Expand Down
6 changes: 6 additions & 0 deletions composer.json
Expand Up @@ -30,11 +30,13 @@
"@lint",
"@lint:auto-sizes",
"@lint:dominant-color-images",
"@lint:embed-optimizer",
"@lint:speculation-rules",
"@lint:webp-uploads"
],
"lint:auto-sizes": "@lint -- ./plugins/auto-sizes --standard=./plugins/auto-sizes/phpcs.xml.dist",
"lint:dominant-color-images": "@lint -- ./plugins/dominant-color-images --standard=./plugins/dominant-color-images/phpcs.xml.dist",
"lint:embed-optimizer": "@lint -- ./plugins/embed-optimizer --standard=./plugins/embed-optimizer/phpcs.xml.dist",
"lint:speculation-rules": "@lint -- ./plugins/speculation-rules --standard=./plugins/speculation-rules/phpcs.xml.dist",
"lint:webp-uploads": "@lint -- ./plugins/webp-uploads --standard=./plugins/webp-uploads/phpcs.xml.dist",
"test": "phpunit --verbose --testsuite performance-lab",
Expand All @@ -50,19 +52,23 @@
"test:plugins": [
"@test:auto-sizes",
"@test:dominant-color-images",
"@test:embed-optimizer",
"@test:speculation-rules",
"@test:webp-uploads"
],
"test-multisite:plugins": [
"@test-multisite:auto-sizes",
"@test-multisite:dominant-color-images",
"@test-multisite:embed-optimizer",
"@test-multisite:speculation-rules",
"@test-multisite:webp-uploads"
],
"test:auto-sizes": "phpunit --verbose --testsuite auto-sizes",
"test-multisite:auto-sizes": "phpunit -c tests/multisite.xml --verbose --testsuite auto-sizes",
"test:dominant-color-images": "phpunit --verbose --testsuite dominant-color-images",
"test-multisite:dominant-color-images": "phpunit -c tests/multisite.xml --verbose --testsuite dominant-color-images",
"test:embed-optimizer": "phpunit --verbose --testsuite embed-optimizer",
"test-multisite:embed-optimizer": "phpunit -c tests/multisite.xml --verbose --testsuite embed-optimizer",
"test:speculation-rules": "phpunit --verbose --testsuite speculation-rules",
"test-multisite:speculation-rules": "phpunit -c tests/multisite.xml --verbose --testsuite speculation-rules",
"test:webp-uploads": "phpunit --verbose --testsuite webp-uploads",
Expand Down
1 change: 1 addition & 0 deletions load.php
Expand Up @@ -322,6 +322,7 @@ function perflab_get_standalone_plugin_version_constants( $source = 'plugins' )
return array(
'webp-uploads' => 'WEBP_UPLOADS_VERSION',
'dominant-color-images' => 'DOMINANT_COLOR_IMAGES_VERSION',
'embed-optimizer' => 'EMBED_OPTIMIZER_VERSION',
'performant-translations' => 'PERFORMANT_TRANSLATIONS_VERSION',
'auto-sizes' => 'IMAGE_AUTO_SIZES_VERSION',
'speculation-rules' => 'SPECULATION_RULES_VERSION',
Expand Down
2 changes: 2 additions & 0 deletions module-i18n.php
Expand Up @@ -9,6 +9,8 @@
_x( 'Creates WebP versions for new JPEG image uploads if supported by the server.', 'module description', 'performance-lab' ),
_x( 'Enqueued Assets Health Check', 'module name', 'performance-lab' ),
_x( 'Adds a CSS and JS resource check in Site Health status.', 'module description', 'performance-lab' ),
_x( 'Embed Optimizer', 'module name', 'performance-lab' ),
_x( 'Optimize the performance of embeds.', 'module description', 'performance-lab' ),
_x( 'Autoloaded Options Health Check', 'module name', 'performance-lab' ),
_x( 'Adds a check for autoloaded options in Site Health status.', 'module description', 'performance-lab' ),
);
Expand Down
5 changes: 5 additions & 0 deletions phpcs.ruleset.xml
Expand Up @@ -89,5 +89,10 @@
</properties>
</rule>

<!-- Do not apply script rules for unit tests -->
<rule ref="WordPress.WP.EnqueuedResources.NonEnqueuedScript">
<exclude-pattern>tests/*</exclude-pattern>
</rule>

<rule ref="SlevomatCodingStandard.Functions.StaticClosure" />
</ruleset>
3 changes: 3 additions & 0 deletions phpunit.xml.dist
Expand Up @@ -18,6 +18,9 @@
<testsuite name="dominant-color-images">
<directory suffix=".php">./tests/plugins/dominant-color-images</directory>
</testsuite>
<testsuite name="embed-optimizer">
<directory suffix=".php">./tests/plugins/embed-optimizer</directory>
</testsuite>
<testsuite name="speculation-rules">
<directory suffix=".php">./tests/plugins/speculation-rules</directory>
</testsuite>
Expand Down
1 change: 1 addition & 0 deletions plugins.json
Expand Up @@ -3,6 +3,7 @@
"plugins": [
"auto-sizes",
"dominant-color-images",
"embed-optimizer",
"speculation-rules",
"webp-uploads"
]
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions plugins/embed-optimizer/.wordpress-org/icon.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
171 changes: 171 additions & 0 deletions plugins/embed-optimizer/hooks.php
@@ -0,0 +1,171 @@
<?php
/**
* Hook callbacks used for Embed Optimizer.
*
* @since 0.1.0
* @package embed-optimizer
*/

if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}

/**
* Filter the oEmbed HTML.
*
* Add loading="lazy" to any iframe tags.
* Lazy load any script tags.
*
* @since 0.1.0
*
* @param string $html The oEmbed HTML.
* @return string
*/
function embed_optimizer_filter_oembed_html( string $html ): string {
$p = new WP_HTML_Tag_Processor( $html );

/**
* Determine how to lazy load the embed.
*
* - If there is only one iframe, set loading="lazy".
* - Prevent making scripts lazy if there is an inline script.
* - Only make script lazy if there is a single external script (since if there are
* multiple they may not get loaded in the right order).
* - Ensure that both the iframe and the script are made lazy if both occur in the same embed.
*/
$iframe_count = 0;
$script_count = 0;
$has_inline_script = false;
// Locate the iframes and scripts.
while ( $p->next_tag() ) {
if ( 'IFRAME' === $p->get_tag() ) {
$loading_value = $p->get_attribute( 'loading' );
if ( empty( $loading_value ) ) {
++$iframe_count;
if ( ! $p->set_bookmark( 'iframe' ) ) {
embed_optimizer_trigger_error( __FUNCTION__, esc_html__( 'Embed Optimizer unable to set iframe bookmark.', 'embed-optimizer' ) );
return $html;
}
}
} elseif ( 'SCRIPT' === $p->get_tag() ) {
if ( ! $p->get_attribute( 'src' ) ) {
$has_inline_script = true;
} else {
++$script_count;
if ( ! $p->set_bookmark( 'script' ) ) {
embed_optimizer_trigger_error( __FUNCTION__, esc_html__( 'Embed Optimizer unable to set script bookmark.', 'embed-optimizer' ) );
return $html;
}
}
}
}
// If there was only one non-inline script, make it lazy.
if ( 1 === $script_count && ! $has_inline_script && $p->has_bookmark( 'script' ) ) {
add_action( 'wp_footer', 'embed_optimizer_lazy_load_scripts' );
if ( $p->seek( 'script' ) ) {
if ( $p->get_attribute( 'type' ) ) {
$p->set_attribute( 'data-original-type', $p->get_attribute( 'type' ) );
}
$p->set_attribute( 'type', 'application/vnd.embed-optimizer.javascript' );
} else {
embed_optimizer_trigger_error( __FUNCTION__, esc_html__( 'Embed Optimizer unable to seek to script bookmark.', 'embed-optimizer' ) );
}
}
// If there was only one iframe, make it lazy.
if ( 1 === $iframe_count && $p->has_bookmark( 'iframe' ) ) {
if ( $p->seek( 'iframe' ) ) {
$p->set_attribute( 'loading', 'lazy' );
} else {
embed_optimizer_trigger_error( __FUNCTION__, esc_html__( 'Embed Optimizer unable to seek to iframe bookmark.', 'embed-optimizer' ) );
}
}
return $p->get_updated_html();
}
add_filter( 'embed_oembed_html', 'embed_optimizer_filter_oembed_html' );

/**
* Add a script to the footer if there are lazy loaded embeds.
* Load the embed's scripts when they approach the viewport using an IntersectionObserver.
*
* @since 0.1.0
*/
function embed_optimizer_lazy_load_scripts() {
$js = <<<JS
const lazyEmbedsScripts = document.querySelectorAll( 'script[type="application/vnd.embed-optimizer.javascript"]' );
const lazyEmbedScriptsByParents = new Map();
const lazyEmbedObserver = new IntersectionObserver(
( entries ) => {
for ( const entry of entries ) {
if ( entry.isIntersecting ) {
const lazyEmbedParent = entry.target;
const lazyEmbedScript = /** @type {HTMLScriptElement} */ lazyEmbedScriptsByParents.get( lazyEmbedParent );
const embedScript = document.createElement( 'script' );
for ( const attr of lazyEmbedScript.attributes ) {
if ( attr.nodeName === 'type' ) {
// Omit type=application/vnd.embed-optimizer.javascript type.
continue;
}
embedScript.setAttribute(
attr.nodeName === 'data-original-type' ? 'type' : attr.nodeName,
attr.nodeValue
);
}
lazyEmbedScript.replaceWith( embedScript );
lazyEmbedObserver.unobserve( lazyEmbedParent );
}
}
},
{
rootMargin: '100% 0% 100% 0%',
threshold: 0
}
);
for ( const lazyEmbedScript of lazyEmbedsScripts ) {
const lazyEmbedParent = /** @type {HTMLElement} */ lazyEmbedScript.parentNode;
lazyEmbedScriptsByParents.set( lazyEmbedParent, lazyEmbedScript );
lazyEmbedObserver.observe( lazyEmbedParent );
}
JS;
wp_print_inline_script_tag( $js, array( 'type' => 'module' ) );
}

/**
* Generates a user-level error/warning/notice/deprecation message.
*
* Generates the message when `WP_DEBUG` is true.
*
* @since 0.1.0
*
* @param string $function_name The function that triggered the error.
* @param string $message The message explaining the error.
* The message can contain allowed HTML 'a' (with href), 'code',
* 'br', 'em', and 'strong' tags and http or https protocols.
* If it contains other HTML tags or protocols, the message should be escaped
* before passing to this function to avoid being stripped {@see wp_kses()}.
* @param int $error_level Optional. The designated error type for this error.
* Only works with E_USER family of constants. Default E_USER_NOTICE.
*/
function embed_optimizer_trigger_error( string $function_name, string $message, int $error_level = E_USER_NOTICE ) {
if ( ! function_exists( 'wp_trigger_error' ) ) {
return;
}
wp_trigger_error( $function_name, $message, $error_level );
}

/**
* Displays the HTML generator tag for the Embed Optimizer plugin.
*
* See {@see 'wp_head'}.
*
* @since 0.1.0
*/
function embed_optimizer_render_generator() {
if (
defined( 'EMBED_OPTIMIZER_VERSION' )
) {
echo '<meta name="generator" content="Embed Optimizer ' . esc_attr( EMBED_OPTIMIZER_VERSION ) . '">' . "\n";
}
}
add_action( 'wp_head', 'embed_optimizer_render_generator' );
26 changes: 26 additions & 0 deletions plugins/embed-optimizer/load.php
@@ -0,0 +1,26 @@
<?php
/**
* Plugin Name: Embed Optimizer
* Plugin URI: https://github.com/WordPress/performance/tree/trunk/plugins/embed-optimizer
* Description: Optimizes the performance of embeds by lazy-loading iframes and scripts.
* Requires at least: 6.3
* Requires PHP: 7.0
* Version: 0.1.0
* Author: WordPress Performance Team
* Author URI: https://make.wordpress.org/performance/
* License: GPLv2 or later
* License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* Text Domain: embed-optimizer
*
* @package embed-optimizer
*/

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}

define( 'EMBED_OPTIMIZER_VERSION', '0.1.0' );

// Load in the Embed Optimizer module hooks.
require_once __DIR__ . '/hooks.php';
10 changes: 10 additions & 0 deletions plugins/embed-optimizer/phpcs.xml.dist
@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<ruleset name="WPP-EmbedOptimizer">
<description>WordPress Coding Standards for Embed Optimizer Plugin</description>

<rule ref="../../phpcs.ruleset.xml"/>

<config name="text_domain" value="embed-optimizer"/>

<file>.</file>
</ruleset>
54 changes: 54 additions & 0 deletions plugins/embed-optimizer/readme.txt
@@ -0,0 +1,54 @@
=== Embed Optimizer ===

Contributors: wordpressdotorg
Requires at least: 6.3
Tested up to: 6.5
Requires PHP: 7.0
Stable tag: 0.1.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Tags: performance, embeds

Optimizes the performance of embeds by lazy-loading iframes and scripts.

== Description ==

This plugin's purpose is to optimize the performance of [embeds in WordPress](https://wordpress.org/documentation/article/embeds/), such as YouTube videos, TikToks, and so on. Initially this is achieved by lazy-loading them only when they come into view. This improves performance because embeds are generally very resource-intensive and so lazy-loading them ensures that they don't compete with resources when the page is loading. [Other optimizations](https://github.com/WordPress/performance/issues?q=is%3Aissue+is%3Aopen+label%3A%22%5BPlugin%5D+Embed+Optimizer%22) are planned for the future.

== Installation ==

= Installation from within WordPress =

1. Visit **Plugins > Add New**.
2. Search for **Embed Optimizer**.
3. Install and activate the **Embed Optimizer** plugin.

= Manual installation =

1. Upload the entire `embed-optimizer` folder to the `/wp-content/plugins/` directory.
2. Visit **Plugins**.
3. Activate the **Embed Optimizer** plugin.

== Frequently Asked Questions ==

= Where can I submit my plugin feedback? =

Feedback is encouraged and much appreciated, especially since this plugin may contain future WordPress core features. If you have suggestions or requests for new features, you can [submit them as an issue in the WordPress Performance Team's GitHub repository](https://github.com/WordPress/performance/issues/new/choose). If you need help with troubleshooting or have a question about the plugin, please [create a new topic on our support forum](https://wordpress.org/support/plugin/embed-optimizer/#new-topic-0).

= Where can I report security bugs? =

The Performance team and WordPress community take security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.

To report a security issue, please visit the [WordPress HackerOne](https://hackerone.com/wordpress) program.

= How can I contribute to the plugin? =

Contributions are always welcome! Learn more about how to get involved in the [Core Performance Team Handbook](https://make.wordpress.org/performance/handbook/get-involved/).

The [plugin source code](https://github.com/WordPress/performance/tree/trunk/plugins/embed-optimizer) is located in the [WordPress/performance](https://github.com/WordPress/performance) repo on GitHub.

== Changelog ==

= 0.1.0 =

* Initial release.

0 comments on commit 7385b1e

Please sign in to comment.