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

Add ability to serve the default offline page when the user or site is offline. #48

Merged
merged 25 commits into from
Aug 15, 2018

Conversation

miina
Copy link
Contributor

@miina miina commented Aug 6, 2018

Fixes #45 .

  • Precache and route the offline page URL, along with its featured image, in the frontend service worker.
  • Serve offline page when unable to navigate to a URL due to do a server error or connection error.
  • Precache and route the theme's header image, background image, and custom logo (with URLs in srcset).
  • Precache and route all enqueued scripts and styles.
  • Add runtime caching with stale-while-revalidate strategy for any images and fonts inside of themes (such as referenced in the stylesheets).
  • Override wp.serviceWorker.routing with instance of WPRouting and frozen public API on the same way Workbox does.
  • Add is_offline() template conditional.

handler: matchedRoute.handler
};

// @todo Confirm approach for conflicting routes: for example perhaps we shouldn't consider multiple matches but same strategy a conflict. Maybe we should still use the first matching route and warn about others.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Agreed. I think it's a good idea to let the first match handle the request, and then to warn about the conflicting handlers.

*
* @return string Script.
*/
protected function configure_offline_page() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should ideally tie-in to a precaching API (e.g. register_precaching_for_routes) instead of having its own separate logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the logic within dec04fb to use register_precached_routes for the precaching part.

Wondering if we should rename register_precaching_for_routes to get_precaching_for_routes_script or something similar, otherwise maybe it's too similar with register_precached_routes. Also, one is registering the route for caching and the other is generating and returning the script. Thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, good idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated within a71a0cd.

return;
}

if ( self::STRATEGY_PRECACHE === $strategy ) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of including precache in this method, I think it should probably be a separate method like register_precached_route. This then can be a straight wrapper for workbox.precaching.precache(): https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching#.precache

*/
protected function register_precaching_for_routes( $routes ) {

$routes_list = '[';
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be better to construct an array here, like $routes_list = array(). And then for each $route do:

$routes_list[] = array(
    'url' => $base_url . $route,
    'revision' => $revision,
);

And then at the end just do:

return sprintf( "wp.serviceWorker.precaching.precacheAndRoute( %s );\n", wp_json_encode( $routes_list ) );

if ( isset( $args['max_entries'] ) || isset( $args['max_age'] ) ) {
$script .= 'args.plugins = [
new wp.serviceWorker.expiration.Plugin({';
if ( isset( $args['max_age'] ) ) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The the max_age shouldn't be hard-coded here in $args I don't think. Instead, $args should have a plugins array like:

array(
    'plugins' => array(
        'expiration' => array(
            'maxAgeSeconds' => 123,
            'maxEntries' => 45,
        ),
    )
)

This would then allow any plugin to be referenced and passing all of the args that the plugins accept. The $args would be whatever the caching strategy options are accepted, for example: https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies.CacheFirst#constructor_1

So $args should be renamed $strategy_args for clarity.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That being the case, I think the $regex should not be passed in the $args now. Perhaps there should be a separate methods like register_cached_route_pattern() and register_cached_route_url() to differentiate between the two types of args allowed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Made the general Workbox related changes within 6c2592f in #43.

Makes sense that the plugins shouldn't be hardcoded and it should be possible to add other plugins, too. Made these changes within 6c2592f.

On regex: what do you think of just adding an additional param to the method so that if a developer would switch from between regex/url then only a param would require changing instead of the method? For example: register_cached_route( $route, $strategy, $strategy_args = array(), $is_regex = false ).
Thinking that since the only difference it makes when registering routes to self::$registered_caching_routes would be a change if is_regex is true/false for the relevant route, then maybe an additional param would be enough.

Or are you thinking that maybe it's more straightforward and better structured to have two separate methods? Thoughts?

(Added $is_regex param as a temporary change within 6c2592f to move this out of the $strategy_args, will it change it accordingly.)

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm wary of having too many positional parameters, and if $is_regex is the 4th argument, then it is far away from the $route that it is managing. We could have such a method still but let it be protected and then the other public methods could pass this 4th arg. (Note that workbox.routing.registerRoute() has a 3rd argument, after the strategy, and it is the HTTP method to cache, which it is GET by default.) It's unfortunate that there is no native RegExp type in PHP.

If going with a 4th parameter, then it should be an $options array instead. This could contain the method and is_regex args. But then again, I suspect that few plugins would be handling non-GET requests and if they wanted to they could register custom JS directly into the SW.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense. Split the method into two within 0fd9320.

@westonruter westonruter mentioned this pull request Aug 7, 2018
@miina miina changed the title [WIP] Add ability to serve the default offline page when the user or site is offline. Add ability to serve the default offline page when the user or site is offline. Aug 13, 2018
Copy link
Collaborator

@westonruter westonruter left a comment

Choose a reason for hiding this comment

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

I'm starting work on this.

if ( $offline_page_id ) {

// Cache relevant theme assets.
$this->register_cached_route( '/wp-content/.*\.(?:css|gif|png|jpg|jpeg)', self::STRATEGY_STALE_WHILE_REVALIDATE );
Copy link
Collaborator

Choose a reason for hiding this comment

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

This will be problematic because it will store all uploaded files in the cache. We'll need to limit this to the actual theme directory URL.

* @return string Script.
*/
protected function configure_offline_page() {
$offline_page_id = (int) get_option( WP_Offline_Page::OPTION_NAME, 0 );
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should short-circuit if this is not a valid page.

*/
protected function get_offline_post_image_routes( $post ) {
$routes = array();
preg_match_all( '/< *img[^>]*src *= *["\']?([^"\']*)/i', $post->post_content, $matches );
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we need to actually extract the attachment image IDs, as we'll need to also obtain the images defined in the srcset and sizes attributes (via wp_get_attachment_image_srcset() and wp_calculate_image_sizes() respectively).

$routes[] = array(
'url' => $this->remove_url_scheme( $featured_img_url ),
'revision' => $post->post_modified,
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Per above, we'll also need to calculate the image URLs for the srcset and sizes, as the post-thumbnail image may not actually be the size used on the offline page.

@@ -0,0 +1,26 @@
/* global OFFLINE_PAGE_URL */
( () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

My old JS habits are carrying through here. We only need to wrap in a { block since the const variables are scoped lexically. No need for IIFE.

( () => {

// Add custom offline / error response serving.
const networkFirstHandler = wp.serviceWorker.strategies.networkFirst( {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we need to re-use precache as the cacheName for this so that it will use what was already cached.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nevermind. It isn't required to use cacheName: wp.serviceWorker.core.cacheNames.precache because all of the caches are being looked at for finding the offline page.

@westonruter westonruter self-assigned this Aug 13, 2018
$this->register_precached_routes( array_merge(
array(
array(
'url' => $this->remove_url_scheme( get_the_permalink( $offline_page_id ) ),
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think actually we won't need remove_url_scheme because all of the URLs should be getting served as HTTPS anyway.

* Skip looking for assets in content for now.
* Remove unnecessary remove_url_scheme.
* Consolidate precache URLs into single get_offline_page_precache_entries method.
* Add helper get_offline_page method for getting published offline page.
* The offline page will be served from the precache so no caching will be done during navigation.
* Blacklist /wp-admin/ from serving the offline page; a separate admin offline page will be needed.
Copy link
Collaborator

@westonruter westonruter left a comment

Choose a reason for hiding this comment

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

This is working well for serving the offline page. There is more work to do on the offline page and the API for precaching, but we'll continue iterating in subsequent PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add ability to serve the default offline page when the user or site is offline
2 participants