Skip to content

Instantly share code, notes, and snippets.

@yapus
Created October 21, 2024 05:04
Show Gist options
  • Save yapus/8912047d624cbcd5774be03c4cc65d3b to your computer and use it in GitHub Desktop.
Save yapus/8912047d624cbcd5774be03c4cc65d3b to your computer and use it in GitHub Desktop.
rails7-vite - vite-plugin-pwa
// file: app/frontend/entrypoints/app.jsx
import { render } from 'solid-js/web'
import App from '@/components/App'
import '@/utils/pwa'
const app = document.getElementById('app')
if (app) {
render(
() => <App />,
app
)
}
{
"name": "<%= t('app.name') %>",
"icons": [
{
"src": "/icon.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "/icon.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "maskable"
}
],
"start_url": "/",
"display": "standalone",
"scope": "/",
"description": "<%= t('app.description') %>",
"theme_color": "#f5f5f5",
"background_color": "#17212b"
}
// file: app/frontend/utils/pwa.js
import { useRegisterSW } from 'virtual:pwa-register/solid'
// import { pwaInfo } from 'virtual:pwa-info'
// console.log(pwaInfo)
const reloadSW = true
const { needRefresh, offlineReady, updateServiceWorker } = useRegisterSW({
immediate: true,
onRegisteredSW(swUrl, r) {
console.log(`Service Worker at: ${swUrl}`)
if (reloadSW === 'true') {
r &&
setInterval(() => {
console.log('Checking for sw update')
r.update()
}, 60 * 60 * 1000 /* 1h */)
} else {
console.log(`SW Registered: ${r}`)
}
},
onRegisterError(error) {
console.error('SW registration error', error)
}
})
# file: config/routes.rb
Rails.application.routes.draw do
# ...
get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
# ...
end
// file: app/frontend/pwa/sw.ts
import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching'
import { clientsClaim } from 'workbox-core'
import { NavigationRoute, registerRoute } from 'workbox-routing'
import { NetworkFirst } from 'workbox-strategies'
declare let self: ServiceWorkerGlobalScope
// self.__WB_MANIFEST is default injection point
const manifest = self.__WB_MANIFEST || [];
precacheAndRoute(manifest);
// clean old assets
cleanupOutdatedCaches()
let allowlist: undefined | RegExp[]
if (import.meta.env.DEV)
allowlist = [/^\/$/]
// Handle the offline page with a Network First strategy
const offlineHandler = new NetworkFirst({
cacheName: 'offline-page',
});
// to allow work offline
registerRoute(
new NavigationRoute(async (context) => {
try {
// Try to get the response from the network
return await offlineHandler.handle(context);
} catch (error) {
// If offline, return the cached offline page
return await caches.match('/offline.html');
}
}, { allowlist }),
)
self.skipWaiting()
clientsClaim()
// Log the precache manifest for debugging
console.log('Precache manifest:', manifest);
import { resolve } from 'path'
import { defineConfig } from 'vite'
import RubyPlugin from 'vite-plugin-ruby'
import Environment from 'vite-plugin-environment'
import FullReload from 'vite-plugin-full-reload'
import solidPlugin from 'vite-plugin-solid'
import ViteRestart from 'vite-plugin-restart'
import { VitePWA, VitePWAOptions, ManifestOptions } from 'vite-plugin-pwa';
const pwaOptions: Partial<VitePWAOptions> = {
injectRegister: null,
manifest: false,
mode: process.env.SW_DEV === 'true' ? 'development' : 'production',
base: process.env.SW_DEV === 'true' ? '/vite-dev/' : '/vite/',
includeAssets: ['icon.png', 'icon.svg', 'favicon.ico'],
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
navigateFallbackDenylist: [/^\/admin/],
runtimeCaching: [
{
urlPattern: /^https:\/\/lh3\.googleusercontent\.com\/d/i,
handler: 'CacheFirst',
options: {
cacheName: 'google-drive-images-cache',
expiration: {
maxEntries: 10,
maxAgeSeconds: 60 * 60 * 24 * 365 // <== 365 days
},
cacheableResponse: {
statuses: [0, 200]
}
}
},
{
urlPattern: '/offline.html',
handler: 'NetworkFirst',
options: {
cacheName: 'offline-page',
expiration: {
maxEntries: 1,
maxAgeSeconds: 60 * 60 * 24 * 7 // 1 week
}
}
}
]
},
devOptions: {
enabled: process.env.SW_DEV === 'true',
type: 'module',
navigateFallback: '/offline.html',
},
}
const selfDestroying = process.env.SW_DESTROY === 'true'
if (process.env.SW === 'true') {
pwaOptions.srcDir = 'pwa'
pwaOptions.filename = 'sw.ts'
pwaOptions.strategies = 'injectManifest'
pwaOptions.injectManifest = {
swSrc: `app/frontend/pwa/${pwaOptions.filename}`,
swDest: 'sw.js',
injectionPoint: undefined,
rollupFormat: 'iife',
minify: true
}
}
pwaOptions.registerType = 'autoUpdate'
if (selfDestroying)
pwaOptions.selfDestroying = selfDestroying
export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, "app/frontend")
},
},
plugins: [
RubyPlugin(),
Environment(['NODE_ENV']),
FullReload([
'config/routes.rb',
'app/views/**/*',
'app/assets/**/*',
// => add paths you want to watch
], {delay: 200}),
solidPlugin(),
VitePWA(pwaOptions),
ViteRestart({
restart: [
'tmp/restart.vite.txt',
]
})
],
build: {
manifest: true,
rollupOptions: {}
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment