Adefowowe
Adefowowe

Reputation: 260

What is the correct way to use vite-plugin-pwa in a laravel project?

I have a laravel (inertia/vue) app (that used laravel-mix until recently) with a pwa feature that I built up using pwa builder and workbox.

Following the change from laravel-mix to vite in the laravel framework, I migrated to vite and its features work as expected, the js and css are injected into the head element of the entry page of the app, and vue pages work as expected, nothing is compiled and injected into the public folder in development, as was the case with laravel-mix/webpack.

I'm now having trouble getting the pwa feature to work as it did before. Since I'm no longer using webpack/laravel-mix, I replaced the webpack-workbox plugin I was using with this plugin: vite-plugin-pwa.

When I was using laravel-mix and webpack-workbox plugin, I simply point to my source service worker file in the webpack config file and use my desired workbox strategy to build up my final service worker file which then gets compiled and placed in my public folder along with other compiled css and js files.

With this vite plugin, my final service worker does not get compiled and placed in the public directory as such no service worker is discoverable for the app. I get a 404 error with the message: 'Failed to register a service worker for scope...', because no service worker file is available.

I suspect that I'm not configuring or using this plugin the correct way and would appreciate help with how to do this or otherwise resolve this.

This is my vite.config.js file:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
    plugins: [
        laravel({
            input: 'resources/js/app.js',
        }),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
                compilerOptions: {
                    isCustomElement: tag => tag.startsWith('pwa-')
                    || tag.startsWith('font-')
                },
            },
        }),
        VitePWA({
            strategies: 'injectManifest',
            swSrc: './public/sw.js',
            swDest: './public/pwabuilder-sw.js',
            devOptions: {
                enabled: true,
                type: 'module'
            }
        }),
    ],
});

This is my source service worker file in my public folder:

// This is the "Offline copy of assets" service worker
import {BackgroundSyncPlugin} from 'workbox-background-sync'
import {registerRoute} from 'workbox-routing'
import {StaleWhileRevalidate} from 'workbox-strategies'
import {ExpirationPlugin} from 'workbox-expiration'

const CACHE = "pwabuilder-offline"
const QUEUE_NAME = "bgSyncQueue"

self.__WB_DISABLE_DEV_LOGS = true

self.addEventListener("message", (event) => {
  if (event.data && event.data.type === "SKIP_WAITING") {
    self.skipWaiting()
  }
})

const bgSyncPlugin = new BackgroundSyncPlugin(QUEUE_NAME, {
  maxRetentionTime: 24 * 60 // Retry for max of 24 Hours (specified in minutes)
})

const expPlugin = new ExpirationPlugin({
  maxEntries: 5,
  maxAgeSeconds: 1 * 24 * 60 * 60,
  purgeOnQuotaError: true,
  matchOptions: {
    ignoreVary: true,
  }
})

registerRoute(
  new RegExp('/*'),
  new StaleWhileRevalidate({
    cacheName: CACHE,
    plugins: [
      bgSyncPlugin,
      expPlugin
    ]
  })
)

self.addEventListener('push', function (e) {
  if (!(self.Notification && self.Notification.permission === 'granted')) {
      return;
  }

  if (e.data) {
    const msg = e.data.json()
    clients.matchAll().then(function(c) {
        e.waitUntil(self.registration.showNotification('SmartWealth Push Notification', {
          body: msg.notification.body,
          icon: msg.notification.icon,
          actions: msg.notification.actions,
          deep_link: msg.notification.deep_link
        }))

    })
  }
})

UPDATE This is my current SW using workbox CDN:

// This is the "Offline copy of assets" service worker
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js')

const {BackgroundSyncPlugin} =  workbox.backgroundSync
const {registerRoute} =  workbox.routing
const {StaleWhileRevalidate} =  workbox.strategies
const {ExpirationPlugin} =  workbox.expiration

const CACHE = "offlineAssets"
const QUEUE_NAME = "bgSyncQueue"

self.__WB_DISABLE_DEV_LOGS = true

self.addEventListener('install', () => self.skipWaiting())

self.addEventListener('activate', () => self.clients.claim())

const bgSyncPlugin = new BackgroundSyncPlugin(QUEUE_NAME, {
  maxRetentionTime: 24 * 60 // Retry for max of 24 Hours (specified in minutes)
})

const expPlugin = new ExpirationPlugin({
  maxEntries: 5,
  maxAgeSeconds: 1 * 24 * 60 * 60,
  purgeOnQuotaError: true,
  matchOptions: {
    ignoreVary: true,
  }
})

registerRoute(
  new RegExp('/*'),
  new StaleWhileRevalidate({
    cacheName: CACHE,
    plugins: [
      bgSyncPlugin,
      expPlugin
    ]
  })
)

self.addEventListener('push', (e) => {
  if (!(self.Notification && self.Notification.permission === 'granted')) return
  if (!e.data) return
   
  const msg = e.data.json()

  e.waitUntil(clients.matchAll()
    .then((clients) => {
    // console.log(msg, clients)
    clients.forEach((client) => {
      const url = client.url
      const id = url.slice(url.lastIndexOf('/') + 1)
      if (!client.url.includes(`chat/messages/${id}`)) { //send only if not on chat url for particular sender.
        self.registration.showNotification('SmartWealth Push Notification', {
          body: msg.notification.body,
          icon: msg.notification.icon,
          actions: msg.notification.actions,
          deep_link: msg.notification.deep_link
        })
      }
    })
  }))
})

self.addEventListener('notificationclick', () => {}) //to do

I no longer require vite to inject my SW.

Upvotes: 7

Views: 5089

Answers (1)

sifriday
sifriday

Reputation: 4462

The correct way to use vite-plugin-pwa in a Laravel project is made complex by a number of issues, such as:

  • Laravel has its own public dir
  • So vite then builds to public/build/assets which is different to the usual frontend layout
  • there is not an immediately apparent static HTML entrypoint for the PWA
  • Laravel will put other things (like Telescope) in the public dir that you do not want offline

To make it work, you need to configure vite-plugin-pwa to work around these issues:

  • configure buildBase and outDir in vite.config.ts for your preferred directory structure
  • create a Blade file to act as an HTML entrypoint and add config for this to vite.config.ts
  • Add a Service-Worker-Allowed header to your web server to work around the restrictions imposed by the build directory
  • Configuring caching in vite.config.ts to work around other assets in the public dir

There's a detailed GitHub issue exploring this problem here:

https://github.com/vite-pwa/vite-plugin-pwa/issues/431

I worked though this thread to get my own Laravel, Vite, Vue3 and TypeScript app working as a PWA with offline support and app install prompts. The original issue was asking from a demonstration repository, so I set that up based on my experience in case it can help others. You can find it here:

https://github.com/sfreytag/laravel-vite-pwa

Upvotes: 3

Related Questions