Blazor-Toolbelt.Blazor.PWA.Updater

[删除(380066935@qq.com或微信通知)]

jsakamoto/Toolbelt.Blazor.PWA.Updater: Provide "Update Now" UI and feature to your Blazor PWA that appears when the next version of one is available. (github.com)


Blazor PWA Updater

Summary

Provide "Update Now" UI and feature to your Blazor PWA that appears when the next version of one is available.

Supported platforms

.NET 6 or later. Both Blazor Server and Blazor Assembly are supported.

🤔 Backgrounds

Typically, a service worker of PWA is never updated even when updated contents have been deployed to a server, even if you reload the page of that PWA. After the user has navigated away from the PWA in all tabs, updates will complete. This is not specific to Blazor, but rather is a standard web platform behavior.

For more detail, please see also the following link on the Microsoft Docs site.

"ASP.NET Core Blazor Progressive Web App (PWA)" | Miceooft Docs

However, sometimes, a site owner or a developer may want updates completed as soon as possible. In that case, all we can do is notify the user that the new version of the service worker is ready on the browser screen and trigger the update process via the user's manual action.

This NuGet package allows us to implement that behavior like the following GIF animation on your Blazor PWA more easily.

🚀 Quick Start

1. Install this NuGet package

dotnet add package Toolbelt.Blazor.PWA.Updater

2. Register a "PWA updater" service to a DI container

// 📜 This is the "Program.cs" file of your Blazor PWA.
...
// 👇 Add this line to open the name space...
using Toolbelt.Blazor.Extensions.DependencyInjection;
...
// 👇 and add this line to register a "PWA updater" service to a DI container.
builder.Services.AddPWAUpdater();
...
await builder.Build().RunAsync();

3. Place a <PWAUpdater> component somewhere in your Blazor PWA

<PWAUpdater> component is a user interface element showing users the "UPDATE NOW" button and its notification bar. One of the good places to place a <PWAUpdater> component is somewhere shared layout components, such as "MainLayout.razor".

@* 📜 This is the "MainLayout.razor" file of your Blazor PWA *@
@inherits LayoutComponentBase

@* 👇 Add this line to place the "UPDATE NOW" button UI. *@
<PWAUpdater />
...

4. Modify the "service-worker.published.js" file

// 📜 This is the "service-worker.published.js" file of your Blazor PWA.

// 👇 Add these line to accept the message from this library.
self.addEventListener('message', event => { 
  if (event.data?.type === 'SKIP_WAITING') self.skipWaiting();
});
...

5. Modify the "index.html" file

<!-- 📜 This is the "index.html" file of your Blazor PWA. -->
  ...
  <script src="_framework/blazor.webassembly.js"></script>

  <!-- 👇 Remove this script, and...
  <script>navigator.serviceWorker.register('service-worker.js');</script> -->

  <!-- 👇 add this script element instead. -->
  <script src="_content/Toolbelt.Blazor.PWA.Updater.Service/script.min.js"></script>
</body>
</html>

That's all.

⚙️ Configuration

Parameters of the PWAUpdater component

ParameterTypeDescription
TextstringThe text that is shown on the notification bar UI. The default value is "The new version is ready.".
ButtonCaptionstringThe text that is shown as the caption of the button to trigger updates. The default value is "UPDATE NOW".
AlignPWAUpdater.AlignsThe value to specify the position of the notification bar, whether Top or Bottom. The default value is Top.
EnvironmentsForWorkstringThe comma-separated string that specifies environment names that the notification UI should work. If this parameter is an empty string, notification always works regardless of the current environment name, including during development. Usually, notification UI should be a bother during development, so the default value of this parameter is "Production", which doesn't include "Development".

CSS custom properties (variables) for the PWAUpdater component

The following CSS custom properties (variables) are defined in the .pwa-updater[b-pwa-updater] scope to configure the appearance of the notification UI.

Property nameDescription
--pwa-updater-font-sizeThe font size of the notification UI. The default value is 13px.
--pwa-updater-font-familyThe font family of the notification UI. The default value is sans-serif.
--pwa-updater-bar-heightThe height of the notification UI. The default value is 32px.
--pwa-updater-bar-colorThe foreground color of notification UI. The default value is white.
--pwa-updater-bar-backcolorThe background color of notification UI. The default value is darkorange.
--pwa-updater-bar-z-indexThe Z-index value of the notification UI. The default value is 10.

If you define CSS style as below in your Blazor PWA,

body .pwa-updater[b-pwa-updater] {
    --pwa-updater-bar-backcolor: forestgreen;
}

you will get the green appearance of the notification UI like below.

Customize a service worker's script file name

By default, this package will load the "service-worker.js" JavaScript file as a service worker. If the service worker's script file path on your Blazor PWA is not "service-worker.js", then you have to specify that path as the property of the script element loading the JavaScript file of the "PWA Updater" like the following example.

<!-- 📜 This is the "index.html" file of your Blazor PWA. -->
  ...
  <!-- 👇 Set the "register" to specify the service worker script file. -->
  <script src="_content/Toolbelt.Blazor.PWA.Updater.Service/script.min.js"
          register="path/to/your-service-worker.js">
  </script> 
</body>
</html>

Customize the process of registering a service worker

Sometimes, you may have to do something in a service worker registering process. In this case, you can add the no-register attribute to the script element loading the JavaScript file of the "PWA Updater" to prevent loading the service worker's script file by that automatically.

If you do that, please manually invoke the Toolbelt.Blazor.PWA.Updater.handleRegistration() method, that is part of the "PWA Updater" JavaScript code, at the call back of the service worker registered.

<!-- 📜 This is the "index.html" file of your Blazor PWA. -->
  ...
  <!-- 👇 Set "no-register" attribute to prevent service worker registration. -->
  <script src="_content/Toolbelt.Blazor.PWA.Updater.Service/script.min.js"
          no-register>
  </script>

  <script>
    navigator.serviceWorker.register('service-worker.js').then(registration => {
      ...
      // 👇 Invoke this manually.
      Toolbelt.Blazor.PWA.Updater.handleRegistration(registration);
      ...
    });
  </script>
</body>
</html>

⛏️ Implement UI from scratch

You can implement your UI component for "PWA Updater" from scratch.

To do that, at first, reference only the Toolbelt.Blazor.PWA.Updater.Service NuGet package instead of the Toolbelt.Blazor.PWA.Updater NuGet package.

dotnet add package Toolbelt.Blazor.PWA.Updater.Service

Next, inject the IPWAUpdaterService object into your Razor component.

@* 📜 Your Razor component file (.razor) *@
@using Toolbelt.Blazor.PWA.Updater.Service
@inject IPWAUpdaterService PWAUpdaterService
...

Then, subscribe to the NextVersionIsWaiting event on your component. When the NextVersionIsWaiting event is fired, the Blazor PWA is ready to update to the next version. Ordinary, the component should show a notification to users when this event was fired.

@* 📜 Your Razor component file (.razor) *@
...
@code {
  protected override void OnAfterRender(bool firstRender)
  {
    if (firstRender)
    {
      this.PWAUpdaterService.NextVersionIsWaiting += PWAUpdaterService_NextVersionIsWaiting;
    }
  }
  ...

Warning
I strongly recommend subscribing to that event in the OnAfterRender life cycle event method. If you subscribe to the event in other life cycle methods such as OnInitialized, you will run into an error at runtime when server-side pre-rendering if you implemented server-side pre-rendering on the Blazor PWA.

Warning
Please remember to unsubscribe the subscription to the NextVersionIsWaiting event when your component will be disposing, like an example code below.

@* 📜 Your Razor component file (.razor) *@
...
@implements IDisposable
...
@code {
  ...
  void IDisposable.Dispose()
  {
    this.PWAUpdaterService.NextVersionIsWaiting -= PWAUpdaterService_NextVersionIsWaiting;
  }
  ...

At last, invoke the SkipWaitingAsync async method of the IPWAUpdaterService object for updating the Blazor PWA to the next version. Ordinary that method should be invoked according to the user's actions. The SkipWaitingAsync method will cause updating the Blazor PWA to the next version, and the Blazor PWA will be reloaded immediately.

@* 📜 Your Razor component file (.razor) *@
...
@code {
  ...
  private async Task OnClickUpdateNowAsync()
  {
    await this.PWAUpdaterService.SkipWaitingAsync();
  }
  ...

Additionally, please consider implementing your UI will work only on a released environment. If the "PWA Updater" UI always works, including the development phase, it must deteriorate the development speed. The UI provided by the Toolbelt.Blazor.PWA.Updater NuGet package is doing that by referencing the Environment property of the IWebAssemblyHostEnvironment object.