Menu
Menu Sheet Overlay
Search

Managing Performance

The Performance Manager provides control of a number of features that affect PWA performance. In this context, performance means time taken for the PWA to start and become responsive to user input.

The Performance Manager is designed to help minimize the Time To Interactive metrics measured by Google Lighthouse. This is a key performance metric used to drive the Lighthouse PWA Performance measure.

The Performance Manager allows you to control and configure:

  • Task splitting: breaking long-running Javascript operations into smaller tasks, which allows the main browser thread to be available more often to respond to user input.
  • Download management: delaying downloads of page assets (scripts, fetch results, images, etc) so that only a limited number of downloads are in progress at any one time.
  • Quiet state: delaying work until the PWA has fully loaded and is no longer busy. The Performance Manager provides an event that's sent when the PWA has been busy and becomes "quiet".

By default, task splitting and download management will be enabled. You can add further configuration as needed to help reach PWA performance goals.

How does Task-splitting work?

Browser interactivity (responsiveness to user input) depends on the browser's main execution thread being available to respond to user events. Long-running Javascript tasks block the main thread while they execute, reducing interactivity.

The task-splitting code provides a function called defer, which takes a function as its argument and will execute that function when the main thread is available. It maintains a queue of functions waiting to be executed, and executes them in order, releasing the browser's main thread between functions. This makes the browser more responsive to user input.

When task-splitting is enabled, Promise handlers (functions passed to then and catch) are executed using defer, so that they don't block the main thread.

You can also use defer for your own code, if needed. It operates like setTimeout or requestIdleCallback. You can use defer without enabling task-splitting.

Enabling Task-splitting

Task-splitting uses the Bluebird Promise library.

Here's an example of how you would enable task-splitting in the main.jsx file of your PWA:

import {PerformanceManager} from 'progressive-web-sdk/dist/utils/performance-manager'

const performanceManager = PerformanceManager.getManager()

// setTaskSplitting returns a Promise that uses task-splitting, if configured,
// so you can chain tasks from it.
performanceManager.setTaskSplitting(true)
    .then(myFirstTask)
    .then(mySecondTask)

Using defer for your own functions

import {PerformanceManager} from 'progressive-web-sdk/dist/utils/performance-manager'

Then, to execute a function when the browser thread is available:

const myFunction = () => {
    // do work...
}

PerformanceManager.defer(myFunction)

Development options

The Bluebird Promise implementation supports a number of configuration options. You can pass an options object as the second parameter to setTaskSplitting when enabling task-splitting. For example:

performanceManager.setTaskSplitting(
    true,
    {
        // Assumes that DEVELOPMENT is true for a dev build, false in production
        longStackTraces: DEVELOPMENT
    }
)

What is Download Management?

The Google Lighthouse Time To Interactive metric is affected by the number of downloads that are in progress at any given time. The SDK contains a Download Manager that can limit the number of simultaneous downloads, and prioritize downloads so that more important assets are fetched sooner. This is especially useful on slower mobile networks, where attempting to download a large number of assets at the same time can result in all of them competing for bandwidth, and downloading slowly.

How does the Download Manager work?

The Download Manager uses the Service Worker installed as part of the PWA. The Mobify service worker code provided in the SDK includes a component that intercepts all requests generated by the PWA, including scripts, images, fonts, as well as any fetch requests started by the PWA code.

The Download Manager only operates on browsers that support service workers. If the PWA is run on a browser that doesn't support service workers, then the Download Manager will do nothing (configuration will succeed, but have no effect).

The service worker keeps track of the number of current downloads (downloads that are in progress). It can be configured to set a maximum number of downloads. If a new request is made when the number of current downloads is at the maximum, that new request will be delayed until a current download completes.

The worker will also prioritize delayed downloads. There are four priorities that can be assigned to a request: high, normal, low and unthrottled. Requests with the unthrottled priority will never be delayed, even if the maximum number of downloads has been reached. Other requests that have been delayed are started in priority order, from high through normal and low.

You can configure the priorities assigned to requests by providing a list of Javascript regular expression strings to match the URLs of requests, along with the priority to be assigned to requests that match the regular expressions.

Enabling and configuring the Download Manager

Your code controls and configures the Download Manager via the Performance Manager. Here's an example of how you would enable download management in the main.jsx file of your PWA:

import {PerformanceManager} from 'progressive-web-sdk/dist/utils/performance-manager'

const performanceManager = PerformanceManager.getManager()
performanceManager.configureDownloads(
    {
        // Set the maximum number of downloads to 2
        maxDownloads: 2,
        priorityFilters: [
            {
                // Set all our images to low priority
                regexp: '.+\\.[jpg|jpeg|gif|png|webp].*',
                priority: PerformanceManager.LOW
            },
            {
                // Never throttle this important asset
                regexp: '.+importantConfigData.json.*',
                priority: PerformanceManager.UNTHROTTLED 
            }
        ]
    }
).then(
    () => {
        // Download management is set up: start rendering...
        render(<Router store={store} />, rootEl)
    }
)

The configureDownloads method returns a Promise that resolves when configuration is complete (when the service worker is installed and active). You may choose to wait until this Promise resolves before starting PWA rendering, as shown in the example above.

If you are also enabling task-splitting, you should do that before configuring the Download Manager. For example:

import {PerformanceManager} from 'progressive-web-sdk/dist/utils/performance-manager'

const performanceManager = PerformanceManager.getManager()

performanceManager.setTaskSplitting(true)
    .then(() => performanceManager.configureDownloads({maxDownloads: 2}))
    .then(
        () => {
            // Download management is set up: start rendering...
            render(<Router store={store} />, rootEl)
        }
    )

What is the PWA "quiet" state?

The PWA becomes "quiet" when it's loaded, fetched the assets needed to render and is responsive to the user. Often it's better to delay some work (such as prefetching assets that might be used in the future) until the PWA has finished loading, and the Performance Manager provides support for this via the PWAQuietEvent and the pwaIsQuiet property.

The Performance Manager monitors internal conditions to detect the PWA becoming quiet. The exact conditions monitored can vary, depending on whether task-splitting and download management are enabled. The monitoring may also change in future versions of the SDK. However, PWA code can rely on the following:

  • The PerformanceManager will always send a PWAQuietEvent when the PWA has become quiet
  • The event will always be sent after document.readyState changes to 'complete'
  • The event will be sent at an appropriate time to fetch assets that are not critical to initial rendering of the PWA

The PWAQuietEvent and callWhenQuiet

The PWAQuietEvent is sent when the PWA has been busy and becomes quiet. It may be sent multiple times during the life of a PWA, but often PWA code will only want to detect the first event. To simplify your code, use the callWhenQuiet method of the PerformanceManager, as in this example, where we call prefetchAssets when the PWA becomes quiet:

import {PerformanceManager} from './performance-manager'

const performanceManager = PerformanceManager.getManager()

// If the PWA is quiet, prefetchAssets is called immediately. If the PWA is
// busy, prefetchAssets is called when the PWA becomes quiet.
performanceManager.callWhenQuiet(prefetchAssets)

IN THIS ARTICLE:

Feedback

Was this page helpful?