Service Workers with Vue and Bundling - vue.js

I use Vue 2 with Common.js to generate an AMD Bundle. I need to be able to automatically register my service worker on runtime. Something that works:
https://www.npmjs.com/package/worker-plugin
https://www.npmjs.com/package/worker-loader
The reason I need a service worker is for sending notifications. However, I am having trouble with this, as it seems that the only workers supported are in DedicatedWorkerGlobalScope or SharedWorkers. In order to dispatch "showNotification" however, I need the Service Worker type.
So basically what I do:
import Worker from "worker-loader!./Worker.js"
const worker = new Worker()
Works like charm, as does this (Worker Plugin):
const worker = new Worker('./worker.js', { type: 'module' });
However, always normal workers. Those arent service workers and I have been trying to figure this out since hours. Is there a configuration im missing to change the type? Some insight would be great.
To illustrate what im trying to achieve:
Registration of the Service Worker needs to happen automatically on Runtime, without me having to reference absolute or relative urls.
Any insight on how I can achieve what im trying to accomplish?

I did not use your plugins but I used workbox plugin for Vue. The configuration is quite simple and well documented.
Installing in an Already Created Project
vue add workbox-pwa
Config example
// Inside vue.config.js
module.exports = {
// ...other vue-cli plugin options...
pwa: {
name: 'My App',
themeColor: '#4DBA87',
msTileColor: '#000000',
appleMobileWebAppCapable: 'yes',
appleMobileWebAppStatusBarStyle: 'black',
manifestOptions: {
start_url: '/'
},
// configure the workbox plugin
workboxPluginMode: 'GenerateSW', // 'GenerateSW' will lead to a new service worker file being created each time you rebuild your web app.
workboxOptions: {
swDest: 'service-worker.js'
}
}
}

You could use service-worker-loader instead (which is based on worker-loader).
Install service-worker-loader:
npm i -D service-worker-loader
Create a service worker script (e.g., at ./src/sw.js) with the following example contents:
import { format } from 'date-fns'
self.addEventListener('install', () => {
console.log('Service worker installed', format(new Date(), `'Today is a' eeee`))
})
Import the service worker script in your entry file:
// main.js
import registerServiceWorker from 'service-worker-loader!./sw'
registerServiceWorker({ scope: '/' }).then(registration => {
//...
registration.showNotification('Notification Title', {
body: 'Hello world!',
})
})
demo

Related

How to use vue component across multiple node projects?

I'm trying to build a website builder within the drag-and-drop abilities via using Vue3. So, the user will be playing with the canvas and generate a config structure that going to post the backend. Furthermore, the server-side will generate static HTML according to this config.
Eventually, the config will be like the below and it works perfectly. The config only can have HTML tags and attributes currently. Backend uses h() function to generate dom tree.
My question is: can I use .vue component that will generate on the server side as well? For example, the client-side has a Container.vue file that includes some interactions, styles, etc. How can the backend recognize/resolve this vue file?
UPDATE:
Basically, I want to use the Vue component that exists on the Client side on the backend side to generate HTML strings same as exactly client side. (including styles, interactions etc).
Currently, I'm able to generate HTML string via the below config but want to extend/support Vue component itself.
Note: client and server are completely different projects. Currently, server takes config and runs createSSRApp, renderToString methods.
Here is the gist of how server would handle the API:
https://gist.github.com/yulafezmesi/162eafcf7f0dcb3cb83fb822568a6126
{
id: "1",
tagName: "main",
root: true,
type: "container",
properties: {
class: "h-full",
style: {
width: "800px",
transform: "translateZ(0)",
},
},
children: [
{
id: "9",
type: "image",
tagName: "figure",
interactive: true,
properties: {
class: "absolute w-28",
style: {
translate: "63px 132px",
},
},
},
],
}
This might get you started: https://vuejs.org/guide/scaling-up/ssr.html#rendering-an-app
From the docs:
// this runs in Node.js on the server.
import { createSSRApp } from 'vue'
// Vue's server-rendering API is exposed under `vue/server-renderer`.
import { renderToString } from 'vue/server-renderer'
const app = createSSRApp({
data: () => ({ count: 1 }),
template: `<button #click="count++">{{ count }}</button>`
})
renderToString(app).then((html) => {
console.log(html)
})
I guess extract the template from request or by reading the submitted Vue file and use that as the template parameter value

How to check if server build has been updated in SSR Nuxt application

I dont know how to solve the problem if SSR Nuxt app in browser is not compatible with server side build cause build has been updated. It means that user have old version of application in the browser and needs to refresh the page. I found something like this: https://dev-clone.nuxtjs.app/alejandroakbal/632139
So I have created pwa-update.js file in the plugins dir and register it in the nuxt.config.js.
But I dont see any console.log() in the console. Dont understand how to use it and if it is the right way to do it.
Implementaion looks like pwa-update.js
export default async (context) => {
const workbox = await window.$workbox;
if (!workbox) {
console.debug("Workbox couldn't be loaded.");
return;
} else {
console.log('Workbox has been loaded.'); // Dont see any message.
}
workbox.addEventListener('installed', (event) => {
if (!event.isUpdate) {
console.log('The PWA is on the latest version.');
return;
}
console.log('There is an update for the PWA, reloading...');
// window.location.reload();
});
};
nuxt.config.js
plugins: [
{ src: '~/plugins/pwa-update.js', mode: 'client' },
],
If you're regenerating your service worker using workbox during your development build process, a new service worker will be installed after every build. You can check this in your browser's dev tools. I believe the workbox generated service worker callls skipWaiting() in order to install new service workers immediately.
The client should get the new resources automagically because of webpack JS chunk name changes (assuming you're using webpack, the JS chunks it generates get new names after every new build, for cache busting purposes) and service worker version changes (workbox auto-increments service worker version, busting that cache as well). In other words, SSR or not, you won't need to worry about any version mismatches so long as you're using workbox to generate your service worker for you.

How to cache .mp4 files in Safari with workbox-webpack-plugin?

I'm having exactly the same issue reported at https://github.com/GoogleChrome/workbox/issues/1663 which describes an issue that occurs exclusively in Safari where mp4 videos are not rendered after being cached by the service worker.
I'm using workbox-webpack-plugin, so the instructions provided in the comment https://github.com/GoogleChrome/workbox/issues/1663#issuecomment-448755945 will not work in my case. I'm not being able to require workbox-range-requests plugin in my webpack config file and pass it to the runtime caching options because I believe this package is intended for browser usage only. My workbox config is precaching .mp4 assets and uses a network first strategy for runtime caching.
How can I setup workbox-range-requests with workbox-webpack-plugin?
EDIT: Following Jeff's answer below, I've adjusted my webpack config to the following:
new WorkboxPlugin.InjectManifest({
swSrc: serviceWorkerSrcPath,
swDest: serviceWorkerBuildPath,
importsDirectory: 'sw',
})
The build produces the following service worker:
importScripts("/_build/sw/precache-manifest.8a0be820b796b153c97ba206d9753bdb.js", "https://storage.googleapis.com/workbox-cdn/releases/3.6.2/workbox-sw.js");
workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
workbox.routing.registerRoute(
/.*\.mp4/,
new workbox.strategies.CacheFirst({
cacheName: 'videos',
plugins: [
new workbox.cacheableResponse.Plugin({ statuses: [200] }),
new workbox.rangeRequests.Plugin(),
],
}),
);
If forgot to mention previously, but I've also added crossOrigin="anonymous" attribute to the video elements.
EDIT:
Repro that demonstrates it does not work as expected on Safari: https://github.com/acostalima/workbox-range-requests-mp4-demo
There's specific guidance for this use case in the "Serve cached audio and video" recipe in the Workbox documentation.
You can continue using the workbox-webpack-plugin, but I'd suggest using it in InjectManifest mode, which will give you control over the top-level service worker file. That will in turn make it possible to follow the recipe.
This documentation has guidance on configuring workbox-webpack-plugin in InjectManifest mode.
I had the same issue with Safari and managed to resolve it by removing my video from the precahe list self.__precacheManifest and instead by adding it in the service worker's install handler:
self.addEventListener('install', (event) => {
const urls = [/* videoUrl */];
const cacheName = 'videos';
event.waitUntil(caches.open(cacheName).then((cache) => cache.addAll(urls)));
});
Looking at the logs, it seemed that otherwise only the precache was used to respond to the request for the video resource and not the router.
Although the docs say that adding mp4s to the precache cache and then configuring the range plugin to handle precache mp4s is supposed to work, in practice, it wasn't. Removing mp4s from the precache and configuring your own video cache with the range plugin did the trick for me. Don't forget to add the crossorigin="anonymous" tag to your videos!
Here's how I did it (webpack 5, workbox 6):
// src/service-worker.js
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
import { cacheNames } from 'workbox-core';
import { precacheAndRoute } from 'workbox-precaching';
import { RangeRequestsPlugin } from 'workbox-range-requests';
import { registerRoute } from 'workbox-routing';
import { CacheFirst } from 'workbox-strategies';
const allEntries = self.__WB_MANIFEST; // Injected by WorkboxWebpackPlugin at compile time
const videoEntries = allEntries.filter((entry) => entry.url.endsWith('.mp4'));
const restEntries = allEntries.filter((entry) => !entry.url.endsWith('.mp4'));
precacheAndRoute(restEntries);
const videoCacheName = `${cacheNames.prefix}-videos-${cacheNames.suffix}`;
self.addEventListener('install', (event) => {
const allVideosAddedToCache = caches.open(videoCacheName).then((videoCache) => {
const videoUrls = videoEntries.map((entry) => entry.url);
return videoCache.addAll(videoUrls);
});
event.waitUntil(allVideosAddedToCache);
});
registerRoute(
(route) => route.url.pathname.endsWith('.mp4'),
new CacheFirst({
cacheName: videoCacheName,
plugins: [new CacheableResponsePlugin({ statuses: [200] }), new RangeRequestsPlugin()],
})
);
// webpack.config.js
plugins: [
new WorkboxWebpackPlugin.InjectManifest({
swSrc: 'src/service-worker.js',
}),
]
// index.tsx
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js');
});
}

Vue Cli 3 how to use the official PWA plugin ( Service Worker )

on my first vue project attempting to wrestle with the official PWA plugin ( https://github.com/yyx990803/register-service-worker ).
My specific problem: capturing the registered service worker and using it for anything. The github readme shows the exact file that is produced, and there seems to be zero documentation about how to work with this service worker once it is instantiated ( do I capture the registration instance? if so, how? )
I found this issue: https://github.com/vuejs/vue-cli/issues/1481
and am providing a better place to talk about this, as I haven't been able to find any example code or clear documentation about how to work with this.
If anyone has some sample code, please share. Vue and the new cli are incredible tools, documenting things like this is a necessary step forward to increasing the adoption of the platform
As already pointed out, it's more of a "service workers" issue than a "vue cli" one.
First of all, to make sure we're on the same page, here's what the boilerplate content of registerServiceWorker.js should look like (vue cli 3, official pwa plugin):
import { register } from 'register-service-worker'
if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready () {
console.log(
'App is being served from cache by a service worker.\n'
)
},
cached () {
console.log('Content has been cached for offline use.')
},
updated () {
console.log('New content is available; please refresh.')
},
offline () {
console.log('No internet connection found. App is running in offline mode.')
},
error (error) {
console.error('Error during service worker registration:', error)
}
})
}
If you haven't changed the BASE_URL variable in your .env file, then it should correspond to the root of your vue app. You have to create a file named service-worker.js in the public folder (so that it's copied into your output directory on build).
Now, it is important to understand that all the code in the registerServiceWorker.js file does is register a service worker and provide a few hooks into its lifecycle. Those are typically used for debugging purposes and not to actually program the service worker. You can understand it by noticing that the registerServiceWorker.js file will be bundled into the app.js file and run in the main thread.
The vue-cli 3 official PWA plugin is based on Google's workbox, so to use the service worker, you'll have to first create a file named vue.config.js at the root of your project and copy the following code in it:
// vue.config.js
module.exports = {
// ...other vue-cli plugin options...
pwa: {
// configure the workbox plugin
workboxPluginMode: 'InjectManifest',
workboxOptions: {
// swSrc is required in InjectManifest mode.
swSrc: 'public/service-worker.js',
// ...other Workbox options...
}
}
}
If you already have created a vue.config.js file, then you just have to add the pwa attribute to the config object. Those settings will allow you to create your custom service worker located at public/service-worker.js and have workbox inject some code in it: the precache manifest. It's a .js file where a list of references to your compiled static assets is stored in a variable typically named self.__precacheManifest. You have to build your app in production mode in order to make sure that this is the case.
As it is generated automatically by workbox when you build in production mode, the precache manifest is very important for caching your Vue app shell because static assets are usually broken down into chunks at compile time and it would be very tedious for you to reference those chunks in the service worker each time you (re)build the app.
To precache the static assets, you can put this code at the beginning of your service-worker.js file (you can also use a try/catch statement):
if (workbox) {
console.log(`Workbox is loaded`);
workbox.precaching.precacheAndRoute(self.__precacheManifest);
}
else {
console.log(`Workbox didn't load`);
}
You can then continue programming your service worker normally in the same file, either by using the basic service worker API or by using workbox's API. Of course, don't hesitate to combine the two methods.
I hope it helps !
as an addition to the answer above: I wrote a small guide on how to go further and add some functionality to the custom service-worker, using the setup above. You can find it here.
Four main things to keep in mind:
configure Workbox in vue.config.js to InjectManifest mode, pointing the swSrc key to a custom service-worker file in /src
In this custom service-worker, some lines will be added automatically in the Build process for importing the precache-manifest and workbox CDN. Following lines need to be added in the custom service-worker.js file to actually precache the manifest files:
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
Listen to registration events in the registerServiceWorker.js file. You can use the registration object that is passed as first argument to the event handlers to post messages to the service-worker.js file:
...
updated(registration) {
console.log("New content is available; please refresh.");
let worker = registration.waiting
worker.postMessage({action: 'skipWaiting'})
},
...
Subscribe to messages in the service-worker.js file and act accordingly:
self.addEventListener("message", (e)=>{
if (e.data.action=='skipWaiting') self.skipWaiting()
})
Hope this helps someone.

Environmental-based switching of endpoints in Aurelia-API

I have two endpoints for aurelia-api that are registered in main.js. One points to my staging server, the other points to my local development server (Kestrel).
What is the recommended way to register endpoints or set the default endpoint so that I can switch between them easily based on environments?
.plugin('aurelia-api', config => {
config
//.registerEndpoint('api', 'http://localhost:5000/api/')
.registerEndpoint('api', 'http://server:port/api/')
.setDefaultEndpoint('api');
})
The best way to configure anything based on your environment is by utilising the environments folder, that Aurelia creates when you start your app, containing a dev and a prod environment.
dev.ts :
export default {
debug: true,
testing: true,
endpoint: "http://localhost:5000/api"
}
prod.ts
export default {
debug: false,
testing: false,
endpoint: "http://server:port/api/"
}
These compile to the file environment.ts, based on whether you're running it locally or on the server.
If you inject the environment into your file, you are able to use any variable specified in it, like so:
import environment from "./environment";
export function configure(aurelia) {
aurelia.use
.plugin('aurelia-api', config => {
config
.registerEndpoint('api', environment.endpoint)
.setDefaultEndpoint('api');
})