WordPress.org

Make WordPress Core

Opened 3 years ago

Last modified 2 months ago

#36995 accepted feature request

Support for Service Workers

Reported by: bhubbard Owned by: westonruter
Milestone: Future Release Priority: normal
Severity: normal Version: 5.1
Component: General Keywords: needs-patch
Focuses: javascript, administration, performance Cc:
PR Number:

Description

It might make sense into looking to offer a basic service worker for WordPress. It can start with something simple like caching files for wp-admin. Beyond that there are many ways it could be expanded:

  • Cache all scripts and styles for themes/plugins, possibly offering offline mode for sites
  • Possibly Notifications
  • etc

Useful links:

Change History (26)

#1 @bhubbard
3 years ago

  • Type changed from defect (bug) to feature request

#2 @bhubbard
3 years ago

  • Focuses javascript administration performance added

#3 @bhubbard
3 years ago

This project might be a good starting point: https://github.com/mozilla/wp-sw-manager

#4 @westonruter
20 months ago

In the case of the admin, I think a service worker cache could eliminate the need for load-scripts.php

This ticket was mentioned in Slack in #core-js by westonruter. View the logs.


20 months ago

#6 @westonruter
20 months ago

  • Milestone changed from Awaiting Review to Future Release

Incidentally, the WordPress admin used to use Google Gears to cache assets. Google Gears was killed in favor of service workers. So it makes a lot of sense to bring service workers back to fill the gap that Google Gears left.

#7 @westonruter
20 months ago

  • Keywords needs-patch added
  • Version changed from 4.6 to trunk

After a good conversation in #core-js I want to put forward a plan for adding service worker (SW) support in core. Before we can actually start leveraging service workers we need to first start supporting them. By support I mean core needs a framework for core, themes, and plugins to coordinate their respective SW needs (e.g. caching and push notifications). The key that highlights the need for this is the fact that two service workers cannot be installed and running at the same time (so). While eventually multiple service workers may be able to run concurrently in the same scope, the only other alternative I'm aware of is to require layering on a middleware library on top of the service worker. I don't think that core should require using a middleware.

So I'm proposing a lightweight framework for core to facilitate the registration of multiple service workers. Core can create the endpoint and when that endpoint is requested then core can respond with the concatenated service worker scripts. I think we can create an API that follows the same design of WP_Scripts family of scripts, with the key differences being:

  1. Scripts are registered with filesystem paths as opposed to URLs.
  2. Each script needs to indicate the service worker scope, the default being /.

So a function like this:

wp_register_service_worker( $handle, $path, $deps, $ver, $scope )

(The $ver is probably irrelevant here and could be removed since there is no need to cache-bust URLs.)

Here's a couple examples of calls:

wp_register_service_worker( 'foo-front', plugin_dir_path( __FILE__ ) . 'js/front-sw.js', array(), 'v1', '/' );
wp_register_service_worker( 'foo-admin', plugin_dir_path( __FILE__ ) . 'js/admin-sw.js', array(), 'v1', '/wp-admin/' );

At the wp_print_footer_scripts action a new function like wp_print_service_workers() can then run which loops over the registered service workers to get a set of all the scopes. Then given the registered service workers' scopes, the service workers can be installed via:

<script>
if ( navigator.serviceWorker ) {
        navigator.serviceWorker.register(
                <?php echo wp_json_encode( wp_get_service_worker_url( $scope ) ); ?>, 
                { scope: <?php echo wp_json_encode( $scope ); ?> }
        );
}
</script>

Here wp_get_service_worker_url( 'wp-admin' ) could return a URL like /wp-service-worker.js?scope=/wp-admin/, with that path being registered via WP_Rewrite. When the client then fetches this SW URL, this request would be served by WordPress PHP. The response would be handled by taking the requested scope and obtain all registered service workers with that scope and output their concatenated scripts in the order that their declared dependencies require.

The wp_register_service_worker() function would be a wrapper around WP_Service_Workers::add(), where WP_Service_Workers is a subclass of WP_Scripts.

Thoughts?

This ticket was mentioned in Slack in #core-js by westonruter. View the logs.


20 months ago

This ticket was mentioned in Slack in #core-js by aduth. View the logs.


20 months ago

#10 follow-up: @markjaquith
20 months ago

I like the idea of re-using WP_Scripts.

My main question is whether we think this will be rolled out to enough browsers (e.g. Mobile Safari doesn't have it yet) to be worth using before some support for multiple service workers is build into browsers. If we think multiple service worker support is years away (or not even going to happen), then it's smart to get ahead of this.

could return a URL like /wp-service-worker.js?scope=/wp-admin/, with that path being registered via WP_Rewrite

What about non-pretty permalinks?

#11 in reply to: ↑ 10 @westonruter
20 months ago

Replying to markjaquith:

My main question is whether we think this will be rolled out to enough browsers (e.g. Mobile Safari doesn't have it yet) to be worth using before some support for multiple service workers is build into browsers.

There's currently ~75% global browser support for service workers across Chrome and Firefox: https://caniuse.com/#feat=serviceworkers

I can see that support is currently being worked on in Safari and Edge: https://jakearchibald.github.io/isserviceworkerready/

If we think multiple service worker support is years away (or not even going to happen), then it's smart to get ahead of this.

Even if/when browsers support multiple service workers for a given scope it will still make sense to have a wp_register_service_worker() in the same way that it makes sense to have wp_register_script(): it provides a standard way to register a service worker script. This will then allow for the the service worker to be installed appropriately according to the target page, such as using amp-install-serviceworker in AMP HTML.

When/if multiple concurrent-scoped service workers are commonplace, then we can just remove the use of the script-concatenating endpoint and write out the script installation separately. That being said, this could be a good reason to use local filesystem URL as opposed to a filesystem path as that would allow us to eventually install the scripts directly. In other words, we could require assets be registered relative to the document root, just as \WP_Scripts::$default_dirs is used for concatenating for load-scripts.php. If a non-relative path is supplied, the call to wp_register_service_worker() could trigger a _doing_it_wrong().

could return a URL like /wp-service-worker.js?scope=/wp-admin/, with that path being registered via WP_Rewrite

What about non-pretty permalinks?

Yes, if non-pretty permalinks are active then this could instead just be /?wp_service_worker=1&scope=/wp-admin/, similar to what happens when interacting with the REST API with pretty permalinks disabled. Using a pretty permalink wouldn't be necessary actually.

This ticket was mentioned in Slack in #core-js by aduth. View the logs.


20 months ago

This ticket was mentioned in Slack in #core-js by westonruter. View the logs.


20 months ago

#14 @westonruter
20 months ago

  • Owner set to westonruter
  • Status changed from new to accepted

#15 @westonruter
18 months ago

Service worker support has landed in Edge: https://blogs.windows.com/msedgedev/2018/04/30/edgehtml-17-april-2018-update/

It is now supported by the latest version of each major browser: https://jakearchibald.github.io/isserviceworkerready/

#16 @westonruter
17 months ago

I've created a feature plugin repo for collaborating on this: https://github.com/xwp/pwa-wp

Last edited 17 months ago by westonruter (previous) (diff)

#17 @westonruter
16 months ago

Initial pull request in feature plugin to add core support for service workers: https://github.com/xwp/pwa-wp/pull/14

#18 @westonruter
15 months ago

I realized that the ability to register arbitrary path scopes for a given service worker script may needlessly complicating the implementation. On further reflection, it seems a script should only ever be registered for the frontend (scope /) and the admin (scope /wp-admin/). The frontend scope would by default include the admin, unless the home URL and the site URL differ.

Having arbitrary scopes makes it harder to unregister service workers that go away, such as via plugin deactivation (as pointed out by @nico_martin). See https://github.com/xwp/pwa-wp/issues/26

Do we even really need a separate scope for admin vs frontend or should there just be one scope for the root of a site?

Last edited 15 months ago by westonruter (previous) (diff)

#19 @westonruter
15 months ago

I've opened a PR to reduce the service worker scopes to just two: front and admin, with a script being able to be registered to all so that it gets included in both front and admin. See full reasoning in description: https://github.com/xwp/pwa-wp/pull/27

This ticket was mentioned in Slack in #core-js by adamsilverstein. View the logs.


15 months ago

#21 @westonruter
15 months ago

That PR has been merged. The three different scopes are indicated by constants (integers).

Example usage:

<?php
// Register script to only run on the frontend, with dependency on some other app-shell SW script.
wp_register_service_worker(
    'foo', // Handle.
    plugin_dir_url( __FILE__ ) . 'foo.js', // Source.
    array( 'app-shell' ), // Dependency.
    WP_Service_Workers::SCOPE_FRONT // Scope.
);

// Register script (here via render callback instead of URL) to only run only in the admin.
wp_register_service_worker( 
    'bar', 
    function() { return 'console.info("Hello admin!")'; }, 
    array(), // No deps.
    WP_Service_Workers::SCOPE_ADMIN
);

// Register script with to run in both the frontend and wp-admin.
wp_register_service_worker( 'baz', plugin_dir_url( __FILE__ ) . 'baz.js', array(), WP_Service_Workers::SCOPE_ALL );

// The default values for $deps and $scope are array() and WP_Service_Workers::SCOPE_ALL  respectively, so this is same as previous.
wp_register_service_worker( 'baz', plugin_dir_url( __FILE__ ) . 'baz.js' );

I think the next thing to do is to look at higher-level abstractions than this low-level service worker API. While I think we'll always need to have this ability to write scripts directly into the service worker, in practice I think we should target a PHP API that abstracts away the most common code that gets included in a service worker, namely the various caching strategies for fetch events. For that we're looking into using Workbox. By having such an abstraction, we should be able to provide better detection for when two plugins/themes attempt to handle fetch events for the same routes.

#22 @westonruter
15 months ago

PWA feature plugin is now live on WordPress.org: https://wordpress.org/plugins/pwa/

#23 @collimarco
7 months ago

Any plans to merge this on master branch? Until it remains a plugin it is useless.

The problem is that there are too many plugins that attempt to serve as a general solution for service workers and everyone uses a different strategy to register service workers. IMHO the only solution is to add an official hook to Wordpress core that allows plugins to add their code to the service worker.

For example some days ago we had a customer who were using these two plugins:
https://wordpress.org/plugins/pushpad-web-push-notifications/
https://wordpress.org/plugins/super-progressive-web-apps/

They are incompatible because each one tries to register a different service worker for the root scope.

An official hook would definitely solve this kind of conflicts.

I agree that one service worker for frontend and one for the admin is enough.

#24 @westonruter
7 months ago

@collimarco have you been using the PWA plugin on the develop branch? Agreed that a 0.2 needs to be cut soon. Any testing you provide would be helpful.

One recent change was isolating the integrations from the other code which would be proposed for core merge. You can still opt-in to use them, but we'll need to determine which make sense to include by default in core and which should remain in plugin/theme territory.

#25 @westonruter
3 months ago

New 0.3-beta1 pre-release of PWA plugin: https://github.com/xwp/pwa-wp/releases/tag/0.3-beta1

This fixes an annoying bug where navigation preload had to be disabled to get navigation request caching strategies to work properly.

#26 @westonruter
2 months ago

PWA plugin v0.3.0 now available on WordPress.org: https://wordpress.org/plugins/pwa/

Release notes: https://github.com/xwp/pwa-wp/releases/tag/0.3.0

For what's next, see all open issues related to service workers: https://github.com/xwp/pwa-wp/labels/service-workers

Note: See TracTickets for help on using tickets.