mischnic / parcel-plugin-sw-cache

📦👷 Parcel plugin for caching using a service worker
https://npm.im/parcel-plugin-sw-cache
MIT License
47 stars 6 forks source link

Is workbox BroadcastCacheUpdate supported? #4

Closed goofblob closed 5 years ago

goofblob commented 6 years ago

If so, how is it implemented in package.json, and how would a page listen to the notification?

mischnic commented 6 years ago

You could inject you own code with importScripts: ['push-notifications.js'] However, as the documtation says:

When NOT to use generateSW

You want to use other Service Worker features (i.e. Web Push). You want to import additional scripts or add additional logic.

So you would be better off using injectManifest (see example).

Here is the documentation from workbox (last is a demo, look at the code in devtools)

goofblob commented 6 years ago

The inject approach would indeed work. But the API that comes with this plugin actually goes quite a long way.

So for example: "runtimeCaching": [ { "urlPattern": [ "https://path.to.api" ], "handler": "staleWhileRevalidate", "options": { "cacheName": "app-data", "plugins": [ { "broadcastUpdate": { "channelName": "api-updates" } } ] } }]

This does in fact build successfully. And the output to sw.js is non-breaking. But unfortunately it passes the broadcastUpdate object as is.

So this then would be a feature request, if this is doable. Take this object:

{ "broadcastUpdate": { "channelName": "api-updates" } }

... and produce:

new workbox.broadcastUpdate.Plugin({ channelName: 'api-updates', })

...in the output sw.js as per the documentation you have mentioned.

I think this would be very much in the spirit of this plugin, in that it is producing a fully functional SW out of just the json config in package.json

But you know... only if it's not too much bother :-)

mischnic commented 6 years ago

This plugin is only an interface to workbox-build, so it basically only calls generateSW or injectManifest. I don't really want to change the serviceworker "manually" after the generation, it seems like this could cause some bugs. If workbox-build doesn't offer enough options for you, please open an issue at https://github.com/GoogleChrome/workbox/issues.

However, I think that this is already in the workbox-build configuration: (from https://developers.google.com/web/tools/workbox/modules/workbox-build#full_generatesw_config)

runtimeCaching: [{
    urlPattern: /api/,
    handler: 'networkFirst',
    options: {
      networkTimeoutSeconds: 10,
      cacheName: 'my-api-cache',
      // Configure the broadcast cache update plugin.
      broadcastUpdate: {
        channelName: 'my-update-channel',
      },
      // Add in any additional plugin logic you need.
      plugins: [
        {cacheDidUpdate: () => /* custom plugin code */}
      ],
    },
  }]

If this code doesn't work, open an issue with workbox and tell them that either the docs are wrong or that something isn't working properly.

goofblob commented 6 years ago

What I am saying is that some options passed to generateSW work properly, while others do not.

So this works:

    "runtimeCaching": [
      {
        "urlPattern": [
          "https://api"
        ],
        "handler": "staleWhileRevalidate",
        "options": {
          "cacheName": "api"
        }
      }

But this does not:

    "runtimeCaching": [
      {
        "urlPattern": [
          "https://api"
        ],
        "handler": "staleWhileRevalidate",
        "options": {
          "cacheName": "api",
          "broadcastUpdate": {
            "channelName": "my-update-channel",
           }
        }
      }

So the plug-in does not interface with that aspect of generateSW. If given the latter config, the sw.js is not produced in ./dist/

mischnic commented 6 years ago

I will look into this.

mischnic commented 6 years ago

Seems to be a problem with the documentation and workbox itself: GoogleChrome/workbox/issues/1289

mischnic commented 6 years ago

This will get fixed in the next workbox release. The docs are correct, so your code should work then (couldn't test it yet):

    "runtimeCaching": [
      {
        "urlPattern": [
          "https://api"
        ],
        "handler": "staleWhileRevalidate",
        "options": {
          "cacheName": "api",
          "broadcastUpdate": {
            "channelName": "my-update-channel",
           }
        }
      }
goofblob commented 6 years ago

Ah... I see.

I will leave this issue open until we hear from upstream and we can test it.

Thanks!

mischnic commented 6 years ago

There is another issue with workbox 😞 (https://github.com/GoogleChrome/workbox/issues/1334).


@goofblob The fix is in workbox beta.1. Could you please test it? I get (in the browser): Uncaught channel-name-required: You must provide a channelName to construct a BroadcastCacheUpdate instance.

"cache": {
        "inDev": true,
        "runtimeCaching": [
            {
                "urlPattern": "https://raw.githubusercontent.com/parcel-bundler/website/01a1f7dd/src/assets/parcel@3x.png",
                "handler": "cacheFirst",
                "options": {
                    "broadcastUpdate": {
                        "channelName": "my-update-channel"
                    }
                }
            }
        ]
    }
goofblob commented 6 years ago

Hmm... I do not get that specific log error, but:

  1. I get Unhandled promise rejection: TypeError: Cannot read property 'error' of undefined at workbox.generateSW.then.catch (~path/node_modules/parcel-plugin-sw-cache/index.js:114:12)

  2. The event does not seem to trigger - I've tested both locally and in a test deploy environment. I can't quite figure out why this happens, because I am not getting any useful error messages, but it might relate to the error message you are getting.

The reason I believe this might be the case is the following. The Workbox spec says that the broadast channel should be registered with the following syntax:

workbox.strategies.staleWhileRevalidate({
    plugins: [
      new workbox.broadcastUpdate.Plugin({
        channelName: 'api-updates',
      })
    ]
  })

But the plugin generates:

workbox.strategies.staleWhileRevalidate({
    plugins: [
      new workbox.broadcastUpdate.Plugin({
        "channelName": "api-updates",
      })
    ]
  })

The difference is "channelName". Ideally that difference in syntax should not make a difference, but given how goofy and finnecky these things can be...

For the sake of completeness, I listed to the broadcast within an onLoad listener on the index page as follows:

            const updatesChannel = new BroadcastChannel('api-updates');
            updatesChannel.addEventListener('message', (event) => {
                const { cacheName, updatedUrl } = event.data.payload;
                console.log("Master List Updated \n");
                console.log(event.data.payload);
            });

This is more or less taken as is from the Workbox documentation.

mischnic commented 6 years ago

That error of undefined is caused by a change in parcel, so the actual workbox error doesn't get logged.

mischnic commented 6 years ago

The service worker generation works with

{
    "urlPattern": "...",
    "handler": "cacheFirst",
    "options": {
        "broadcastUpdate": {
            "channelName": "api-updates"
        },
        "cacheableResponse": {
            "statuses": [
                0
            ]
        }
    }
}

and workbox-3-beta1.

I however can't test the actual functionality.


The logger is fixed in version 0.2.0

goofblob commented 6 years ago

Hmm...

In order to test it, we need to listen to the broadcastUpdate event:

const updatesChannel = new BroadcastChannel('api-updates');
updatesChannel.addEventListener('message', (event) => {
        console.log(event.data.payload);
});

Problem is that if I put this in index.html the Parcel bundler complains that it doesn't know what BroadcastChannel is. If I put it lower down in one of my components, it bundles it successfully. But then it does not seem to trigger.

mischnic commented 6 years ago

It worked for me:

config:

"cache": {
  "runtimeCaching": [
    {
      "urlPattern": "http://localhost:1234/cache.jpg",
      "handler": "staleWhileRevalidate",
      "options": {
        "broadcastUpdate": {
          "channelName": "api-updates"
        }
      }
    }
  ]
}

index.js

const updatesChannel = new BroadcastChannel('api-updates');
updatesChannel.addEventListener('message', event => {
        console.log(event);
});

index.html contained <img src="http://localhost:1234/cache.jpg">. Changing out cache.jpg fires the eventlistener.

Did you use staleWhileRevalidate?

goofblob commented 6 years ago

Hmm... Still not working for me.

Yes, I did use staleWhileRevalidate.

In package.json I have:

"runtimeCaching": [
      {
        "urlPattern": [
          "https://link.to/api.json"
        ],
        "handler": "staleWhileRevalidate",
        "options": {
          "cacheName": "api",
          "broadcastUpdate": {
            "channelName": "api-update"
          }
        }
      }
]

And in index.js I have:

// LISTEN FOR API UPDATES
    const updatesChannel = new BroadcastChannel('api-update');
    updatesChannel.addEventListener('message', (event) => {
        console.log("API Updated.");
        console.log(event.data.payload);
    });

But when I make changes to the API, and go through the reloads of the site nothing happens. So I get served the stale .json from the Service Worker cache just fine on the first go, and then the refreshed one on the second go. But I do not get the "API Updated." console message on the first go when the .json has been successfully refreshed in the background. Or the payload.

If it works in your tests, then that must mean that the functionality does work. There is something else about my setup that interferes. So if you can see nothing wrong with my code here, I think you can close the issue. I'll just have to bash my head against my code a bit more until it works.

Thanks again for your work on the plugin!

mischnic commented 6 years ago

Workbox checks the headers to determine whether the file updated, by default ['content-length', 'etag', 'last-modified']; if these headers don't change, the event won't fire.

https://developers.google.com/web/tools/workbox/reference-docs/prerelease/workbox.broadcastUpdate.Plugin

With my example: The event fires on the first reload (after getting the old response) and the new response gets returned on the second reload.

goofblob commented 6 years ago

Right. That's how it is intended to work.

What happens in my use case is that the update does happen in the background, and that is reflected in the second reload. But the updatesChannel event listener does not get triggered to give me the console output it should on the first load.

My intended use for this is that I want to serve stale data from the service worker cache on the first load, and then automatically update that data without reload when/if the background service worker process finds fresh API data. But if the event does not get triggered, I cannot do this live API data update.

It's not mission critical, but that is pretty much the standard use case for staleWhileRevalidate, and it's bugging me that I have not yet managed to get it working.

But as I say, if the event triggers in your example, there is something I'm doing wrong on my end, so this would not be an issue with the plugin.