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

Install service worker to cache AMP CDN assets, cache Google Fonts, and cache images (following AMP by Example) #1261

Merged
merged 36 commits into from
Apr 3, 2019
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
bc57707
Install service worker for AMP pages to cache AMP CDN assets
westonruter Jul 8, 2018
d0ed452
Update service worker integration to use proposed 'front' scope
westonruter Jul 10, 2018
43ba855
Improve stale-while-revalidate from Google offline-cookbook; add Goog…
westonruter Jul 10, 2018
71cb091
Update scopes to use constants
westonruter Jul 12, 2018
0536a1e
Merge branch 'develop' of https://github.com/Automattic/amp-wp into a…
westonruter Aug 24, 2018
f708adf
Use PWA plugin API for runtime caching
westonruter Aug 24, 2018
0d68201
Add runtime caching of images
westonruter Aug 24, 2018
02fd0a7
Ensure font stylesheets are requested in CORS mode in both AMP and no…
westonruter Aug 25, 2018
01408ca
Fix call to register_cached_route for AMP CDN assets
westonruter Aug 25, 2018
4c5eff9
Use register_script method instead of deprecated register script for SW
westonruter Aug 26, 2018
3041d88
Use register_precached_route in PHP instead of custom JS
westonruter Aug 27, 2018
cc0e49a
Merge branch 'develop' of https://github.com/Automattic/amp-wp into a…
westonruter Sep 2, 2018
e7c5dd9
Update PWA integration due to https://github.com/xwp/pwa-wp/pull/57
westonruter Sep 5, 2018
fadfb21
Improve passing of registry object to register_runtime_precaches method
westonruter Sep 5, 2018
831b158
Allow query params on URLs for cached images
westonruter Sep 5, 2018
ea6a9cd
Limit runtime images cache to URLs in wp-content
westonruter Sep 5, 2018
f2ad472
Apply changes for https://github.com/xwp/pwa-wp/pull/59
westonruter Sep 6, 2018
15e3c5f
Merge branch 'develop' of https://github.com/Automattic/amp-wp into a…
westonruter Sep 7, 2018
dbc0c86
Update runtime pre-caching based on https://github.com/xwp/pwa-wp/pul…
westonruter Sep 8, 2018
40a1e2e
Add comments with links to amp-by-example code
westonruter Sep 8, 2018
da6a6f2
Merge branch 'develop' of https://github.com/Automattic/amp-wp into a…
westonruter Oct 3, 2018
9c35c3d
Merge branch 'develop' of https://github.com/Automattic/amp-wp into a…
westonruter Oct 4, 2018
55525e1
Merge branch 'develop' of github.com:ampproject/amp-wp into add/servi…
westonruter Feb 7, 2019
fbed16d
Unpluralize AMP_Service_Workers; sync changes from #1519
westonruter Feb 7, 2019
5cbc6b3
Add updated Google Fonts runtime caching particularly if PWA integrat…
westonruter Feb 7, 2019
80dc2d1
Gate AMP service worker functionality behind theme support flag
westonruter Feb 7, 2019
33bb93b
Use proper URL for content in image caching
westonruter Feb 7, 2019
a1cb0cd
Merge branch 'develop' of github.com:ampproject/amp-wp into add/servi…
westonruter Mar 11, 2019
18915f2
Make AMP service worker opt-out instead of opt-in
westonruter Apr 2, 2019
b6862e2
Merge branch 'develop' of github.com:ampproject/amp-wp into add/servi…
westonruter Apr 2, 2019
82bc980
Remove todo resolved by https://github.com/xwp/pwa-wp/pull/147
westonruter Apr 2, 2019
05172f8
Align default-enabled service worker features with ABE SW
westonruter Apr 2, 2019
1038397
Add check for argument type in add_google_fonts_caching; advise to up…
westonruter Apr 2, 2019
0b51923
Merge branch 'develop' of github.com:ampproject/amp-wp into add/servi…
westonruter Apr 2, 2019
7fec746
Ensure is_amp_endpoint() returns false for service worker requests
westonruter Apr 2, 2019
1ef6fbb
Add tests for AMP integration with PWA plugin
westonruter Apr 3, 2019
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: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"globals": {
"wp": true,
"window": true,
"document": true
"document": true,
"Set": true
},
"settings": {
"react": {
Expand Down
1 change: 1 addition & 0 deletions amp.php
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ function amp_init() {
AMP_HTTP::handle_xhr_request();
AMP_Theme_Support::init();
AMP_Validation_Manager::init();
AMP_Service_Worker::init();
add_action( 'init', array( 'AMP_Post_Type_Support', 'add_post_type_support' ), 1000 ); // After post types have been defined.

if ( defined( 'WP_CLI' ) && WP_CLI ) {
Expand Down
3 changes: 1 addition & 2 deletions assets/js/amp-block-validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,7 @@ var ampBlockValidation = ( function() { // eslint-disable-line no-unused-vars
}

return (
module.lastStates.validationErrors.length !== validationErrors.length
||
module.lastStates.validationErrors.length !== validationErrors.length ||
( validationErrors && ! _.isEqual( module.lastStates.validationErrors, validationErrors ) )
);
},
Expand Down
9 changes: 9 additions & 0 deletions assets/js/amp-service-worker-runtime-precaching.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* global URLS */
// See AMP_Service_Workers::add_amp_runtime_caching() and <https://github.com/ampproject/amp-by-example/blob/a4d798cac6a534e0c46e78944a2718a8dab3c057/boilerplate-generator/templates/files/serviceworkerJs.js#L9-L22>.
{
self.addEventListener( 'install', event => {
event.waitUntil(
caches.open( wp.serviceWorker.core.cacheNames.runtime ).then( cache => cache.addAll( URLS ) )
);
} );
}
1 change: 1 addition & 0 deletions includes/class-amp-autoloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class AMP_Autoloader {
private static $classmap = array(
'AMP_Editor_Blocks' => 'includes/admin/class-amp-editor-blocks',
'AMP_Theme_Support' => 'includes/class-amp-theme-support',
'AMP_Service_Worker' => 'includes/class-amp-service-worker',
'AMP_HTTP' => 'includes/class-amp-http',
'AMP_Comment_Walker' => 'includes/class-amp-comment-walker',
'AMP_Template_Customizer' => 'includes/admin/class-amp-customizer',
Expand Down
337 changes: 337 additions & 0 deletions includes/class-amp-service-worker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,337 @@
<?php
/**
* AMP Service Workers.
*
* @package AMP
* @since 1.1
*/

/**
* Class AMP_Service_Worker.
*/
class AMP_Service_Worker {

/**
* Query var that is used to signal a request to install the service worker in an iframe.
*
* @link https://www.ampproject.org/docs/reference/components/amp-install-serviceworker#data-iframe-src-(optional)
*/
const INSTALL_SERVICE_WORKER_IFRAME_QUERY_VAR = 'amp_install_service_worker_iframe';

/**
* Init.
*/
public static function init() {
if ( ! class_exists( 'WP_Service_Workers' ) ) {
return;
}

// Shim support for service worker installation from PWA feature plugin.
add_filter( 'query_vars', array( __CLASS__, 'add_query_var' ) );
add_action( 'parse_request', array( __CLASS__, 'handle_service_worker_iframe_install' ) );
add_action( 'wp', array( __CLASS__, 'add_install_hooks' ) );

$theme_support = AMP_Theme_Support::get_theme_support_args();
if ( isset( $theme_support['service_worker'] ) && false === $theme_support['service_worker'] ) {
return;
}

/*
* The default-enabled options reflect which features are not commented-out in the AMP-by-Example service worker.
* See <https://github.com/ampproject/amp-by-example/blob/e093edb401b1617859b5365e80b639d81b06f058/boilerplate-generator/templates/files/serviceworkerJs.js>.
*/
$enabled_options = array(
'cdn_script_caching' => true,
'image_caching' => false,
'google_fonts_caching' => false,
);
if ( is_array( $theme_support['service_worker'] ) ) {
$enabled_options = array_merge(
$enabled_options,
$theme_support['service_worker']
);
}

if ( $enabled_options['cdn_script_caching'] ) {
add_action( 'wp_front_service_worker', array( __CLASS__, 'add_cdn_script_caching' ) );
}
if ( $enabled_options['image_caching'] ) {
add_action( 'wp_front_service_worker', array( __CLASS__, 'add_image_caching' ) );
}
if ( $enabled_options['google_fonts_caching'] ) {
add_action( 'wp_front_service_worker', array( __CLASS__, 'add_google_fonts_caching' ) );
}
}

/**
* Add query var for iframe service worker request.
*
* @param array $vars Query vars.
* @return array Amended query vars.
*/
public static function add_query_var( $vars ) {
$vars[] = self::INSTALL_SERVICE_WORKER_IFRAME_QUERY_VAR;
return $vars;
}

/**
* Add runtime caching for scripts loaded from the AMP CDN with a stale-while-revalidate strategy.
*
* @link https://github.com/ampproject/amp-by-example/blob/4593af61609898043302a101826ddafe7206bfd9/boilerplate-generator/templates/files/serviceworkerJs.js
*
* @param WP_Service_Worker_Scripts $service_workers Service worker registry.
*/
public static function add_cdn_script_caching( $service_workers ) {
if ( ! ( $service_workers instanceof WP_Service_Worker_Scripts ) ) {
_doing_it_wrong( __METHOD__, esc_html__( 'Please update to PWA v0.2. Expected argument to be WP_Service_Worker_Cache_Registry.', 'amp' ), '1.1' );
return;
}

// Add AMP scripts to runtime cache which will then get stale-while-revalidate strategy.
$service_workers->register(
'amp-cdn-runtime-caching',
function() {
$urls = AMP_Service_Worker::get_precached_script_cdn_urls();
if ( empty( $urls ) ) {
return '';
}

$js = file_get_contents( AMP__DIR__ . '/assets/js/amp-service-worker-runtime-precaching.js' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, WordPress.WP.AlternativeFunctions.file_system_read_file_get_contents
$js = preg_replace( '#/\*\s*global.+?\*/#', '', $js );
$js = str_replace(
'URLS',
wp_json_encode( $urls ),
$js
);
return $js;
}
);

// Serve the AMP Runtime from cache and check for an updated version in the background. See <https://github.com/ampproject/amp-by-example/blob/4593af61609898043302a101826ddafe7206bfd9/boilerplate-generator/templates/files/serviceworkerJs.js#L54-L58>.
$service_workers->caching_routes()->register(
'^https:\/\/cdn\.ampproject\.org\/.*',
array(
'strategy' => WP_Service_Worker_Caching_Routes::STRATEGY_STALE_WHILE_REVALIDATE,
)
);
}

/**
* Add runtime image caching from the origin with a cache-first strategy.
*
* @link https://github.com/ampproject/amp-by-example/blob/4593af61609898043302a101826ddafe7206bfd9/boilerplate-generator/templates/files/serviceworkerJs.js#L60-L74
*
* @param WP_Service_Worker_Scripts $service_workers Service workers.
*/
public static function add_image_caching( $service_workers ) {
if ( ! ( $service_workers instanceof WP_Service_Worker_Scripts ) ) {
_doing_it_wrong( __METHOD__, esc_html__( 'Please update to PWA v0.2. Expected argument to be WP_Service_Worker_Scripts.', 'amp' ), '1.1' );
return;
}

$service_workers->caching_routes()->register(
'^' . preg_quote( set_url_scheme( content_url( '/' ), 'https' ), '/' ) . '[^\?]+?\.(?:png|gif|jpg|jpeg|svg|webp)(\?.*)?$',
array(
'strategy' => WP_Service_Worker_Caching_Routes::STRATEGY_CACHE_FIRST,
'cacheName' => 'images',
'plugins' => array(
'cacheableResponse' => array(
'statuses' => array( 0, 200 ),
),
'expiration' => array(
'maxEntries' => 60,
'maxAgeSeconds' => MONTH_IN_SECONDS,
),
),
)
);
}

/**
* Add runtime caching of Google Fonts with stale-while-revalidate strategy for stylesheets and cache-first strategy for webfont files.
*
* @link https://developers.google.com/web/tools/workbox/guides/common-recipes#google_fonts
* @link https://github.com/ampproject/amp-by-example/blob/4593af61609898043302a101826ddafe7206bfd9/boilerplate-generator/templates/files/serviceworkerJs.js#L76-L103
* @link https://github.com/xwp/pwa-wp/blob/master/integrations/class-wp-service-worker-fonts-integration.php
*
* @param WP_Service_Worker_Scripts $service_workers Service workers.
*/
public static function add_google_fonts_caching( $service_workers ) {
felixarntz marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to have the check for the correct class as well, like the above (without _doing_it_wrong()).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not like this?

diff --git a/includes/class-amp-service-worker.php b/includes/class-amp-service-worker.php
index 37dbc0e1..4de47a37 100644
--- a/includes/class-amp-service-worker.php
+++ b/includes/class-amp-service-worker.php
@@ -157,6 +157,10 @@ class AMP_Service_Worker {
 	 * @param WP_Service_Worker_Scripts $service_workers Service workers.
 	 */
 	public static function add_google_fonts_caching( $service_workers ) {
+		if ( ! ( $service_workers instanceof WP_Service_Worker_Scripts ) ) {
+			_doing_it_wrong( __METHOD__, esc_html__( 'Expected argument to be WP_Service_Worker_Scripts.', 'amp' ), '1.1' );
+			return;
+		}
 
 		// The PWA plugin also automatically adds runtime caching for Google Fonts when WP_SERVICE_WORKER_INTEGRATIONS_ENABLED is set.
 		if ( class_exists( 'WP_Service_Worker_Fonts_Integration' ) ) {

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the other comments above.

if ( ! ( $service_workers instanceof WP_Service_Worker_Scripts ) ) {
_doing_it_wrong( __METHOD__, esc_html__( 'Please update to PWA v0.2. Expected argument to be WP_Service_Worker_Scripts.', 'amp' ), '1.1' );
return;
}

// The PWA plugin also automatically adds runtime caching for Google Fonts when WP_SERVICE_WORKER_INTEGRATIONS_ENABLED is set.
if ( class_exists( 'WP_Service_Worker_Fonts_Integration' ) ) {
return;
}

// Cache the Google Fonts stylesheets with a stale while revalidate strategy.
$service_workers->caching_routes()->register(
'^https:\/\/fonts\.googleapis\.com',
array(
'strategy' => WP_Service_Worker_Caching_Routes::STRATEGY_STALE_WHILE_REVALIDATE,
'cacheName' => 'google-fonts-stylesheets',
)
);

// Cache the Google Fonts webfont files with a cache first strategy for 1 year.
$service_workers->caching_routes()->register(
'^https:\/\/fonts\.gstatic\.com',
array(
'strategy' => WP_Service_Worker_Caching_Routes::STRATEGY_CACHE_FIRST,
'cacheName' => 'google-fonts-webfonts',
'plugins' => array(
'cacheableResponse' => array(
'statuses' => array( 0, 200 ),
),
'expiration' => array(
'maxAgeSeconds' => YEAR_IN_SECONDS,
'maxEntries' => 30,
),
),
)
);
}

/**
* Register URLs that will be precached in the runtime cache. (Yes, this sounds somewhat strange.)
*
* Note that the PWA plugin handles the precaching of custom logo, custom header,
* and custom background. The PWA plugin also handles precaching & serving of the
* offline/500 error pages and enabling navigation preload.
*
* @link https://github.com/ampproject/amp-by-example/blob/4593af61609898043302a101826ddafe7206bfd9/boilerplate-generator/templates/files/serviceworkerJs.js#L9-L22
* @see AMP_Service_Worker::add_cdn_script_caching()
*
* @return array Runtime pre-cached URLs.
*/
public static function get_precached_script_cdn_urls() {

// List of AMP scripts that we know will be used in WordPress always.
$precached_handles = array(
'amp-runtime',
'amp-bind', // Used by comments.
'amp-form', // Used by comments.
'amp-install-serviceworker',
);

$theme_support = AMP_Theme_Support::get_theme_support_args();
if ( ! empty( $theme_support['comments_live_list'] ) ) {
$precached_handles[] = 'amp-live-list';
}

if ( amp_get_analytics() ) {
$precached_handles[] = 'amp-analytics';
}
felixarntz marked this conversation as resolved.
Show resolved Hide resolved

$urls = array();
foreach ( $precached_handles as $handle ) {
if ( wp_script_is( $handle, 'registered' ) ) {
$urls[] = wp_scripts()->registered[ $handle ]->src;
}
}

return $urls;
}

/**
* Add hooks to install the service worker from AMP page.
*/
public static function add_install_hooks() {
if ( current_theme_supports( 'amp' ) && is_amp_endpoint() ) {
add_action( 'wp_footer', array( __CLASS__, 'install_service_worker' ) );

// Prevent validation error due to the script that installs the service worker on non-AMP pages.
$priority = has_action( 'wp_print_scripts', 'wp_print_service_workers' );
if ( false !== $priority ) {
remove_action( 'wp_print_scripts', 'wp_print_service_workers', $priority );
}
}
add_action( 'amp_post_template_footer', array( __CLASS__, 'install_service_worker' ) );
}

/**
* Install service worker(s).
*
* @since 1.1
* @see wp_print_service_workers()
* @link https://github.com/xwp/pwa-wp
*/
public static function install_service_worker() {
if ( ! function_exists( 'wp_service_workers' ) || ! function_exists( 'wp_get_service_worker_url' ) ) {
return;
}

$src = wp_get_service_worker_url( WP_Service_Workers::SCOPE_FRONT );
$iframe_src = add_query_arg(
self::INSTALL_SERVICE_WORKER_IFRAME_QUERY_VAR,
WP_Service_Workers::SCOPE_FRONT,
home_url( '/', 'https' )
);
?>
<amp-install-serviceworker
src="<?php echo esc_url( $src ); ?>"
data-iframe-src="<?php echo esc_url( $iframe_src ); ?>"
layout="nodisplay"
>
</amp-install-serviceworker>
<?php
}

/**
* Handle request to install service worker via iframe.
*
* @see wp_print_service_workers()
* @link https://www.ampproject.org/docs/reference/components/amp-install-serviceworker#data-iframe-src-(optional)
*/
public static function handle_service_worker_iframe_install() {
if ( ! isset( $GLOBALS['wp']->query_vars[ self::INSTALL_SERVICE_WORKER_IFRAME_QUERY_VAR ] ) ) {
return;
}

$scope = intval( $GLOBALS['wp']->query_vars[ self::INSTALL_SERVICE_WORKER_IFRAME_QUERY_VAR ] );
if ( WP_Service_Workers::SCOPE_ADMIN !== $scope && WP_Service_Workers::SCOPE_FRONT !== $scope ) {
wp_die(
esc_html__( 'No service workers registered for the requested scope.', 'amp' ),
esc_html__( 'Service Worker Installation', 'amp' ),
array( 'response' => 404 )
);
}

$front_scope = home_url( '/', 'relative' );

?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><?php esc_html_e( 'Service Worker Installation', 'amp' ); ?></title>
</head>
<body>
<?php esc_html_e( 'Installing service worker...', 'amp' ); ?>
<?php
printf(
'<script>navigator.serviceWorker.register( %s, %s );</script>',
wp_json_encode( wp_get_service_worker_url( $scope ) ),
wp_json_encode( array( 'scope' => $front_scope ) )
);
?>
</body>
</html>
<?php

// Die in a way that can be unit tested.
add_filter(
'wp_die_handler',
function() {
return function() {
die();
};
},
1
);
wp_die();
}
}
2 changes: 1 addition & 1 deletion includes/class-amp-theme-support.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ public static function read_theme_support() {
$args = self::get_theme_support_args();

// Validate theme support usage.
$keys = array( 'template_dir', 'comments_live_list', 'paired', 'templates_supported', 'available_callback', 'nav_menu_toggle', 'nav_menu_dropdown' );
$keys = array( 'template_dir', 'comments_live_list', 'paired', 'templates_supported', 'available_callback', 'service_worker', 'nav_menu_toggle', 'nav_menu_dropdown' );

if ( count( array_diff( array_keys( $args ), $keys ) ) !== 0 ) {
_doing_it_wrong(
Expand Down