Add PWA support
This commit is contained in:
parent
b959371784
commit
f3ccfcbe48
|
@ -0,0 +1 @@
|
||||||
|
Fixes service worker (#1634)
|
|
@ -0,0 +1 @@
|
||||||
|
Handle PWA correctly and provide better cache strategy for album covers (#1721)
|
|
@ -24,10 +24,22 @@ module.exports = {
|
||||||
'vue/no-v-html': 'off', // TODO: tackle this properly
|
'vue/no-v-html': 'off', // TODO: tackle this properly
|
||||||
'vue/no-use-v-if-with-v-for': 'off',
|
'vue/no-use-v-if-with-v-for': 'off',
|
||||||
|
|
||||||
'@typescript-eslint/ban-ts-comment': 'off',
|
// NOTE: Handled by typescript
|
||||||
'no-undef': 'off',
|
'no-undef': 'off',
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
|
||||||
|
// TODO (wvffle): Migrate to VUI
|
||||||
|
// We're using `// @ts-ignore` in jQuery extensions
|
||||||
|
// and gettext for vue 2
|
||||||
|
'@typescript-eslint/ban-ts-comment': 'off',
|
||||||
|
|
||||||
// TODO (wvffle): Enable typescript rules later
|
// TODO (wvffle): Enable typescript rules later
|
||||||
'@typescript-eslint/no-this-alias': 'off',
|
'@typescript-eslint/no-this-alias': 'off',
|
||||||
'@typescript-eslint/no-empty-function': 'off'
|
'@typescript-eslint/no-empty-function': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
|
||||||
|
// TODO (wvffle): Migration to pinia
|
||||||
|
// Vuex 3 store does not have types defined, hence we use `any`
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@
|
||||||
"@babel/core": "7.17.12",
|
"@babel/core": "7.17.12",
|
||||||
"@babel/plugin-transform-runtime": "7.17.12",
|
"@babel/plugin-transform-runtime": "7.17.12",
|
||||||
"@babel/preset-env": "7.16.11",
|
"@babel/preset-env": "7.16.11",
|
||||||
|
"@types/jest": "27.4.1",
|
||||||
"@types/jquery": "3.5.14",
|
"@types/jquery": "3.5.14",
|
||||||
"@types/lodash-es": "4.17.6",
|
"@types/lodash-es": "4.17.6",
|
||||||
"@typescript-eslint/eslint-plugin": "5.19.0",
|
"@typescript-eslint/eslint-plugin": "5.19.0",
|
||||||
|
@ -72,12 +73,18 @@
|
||||||
"jest-cli": "27.5.1",
|
"jest-cli": "27.5.1",
|
||||||
"moxios": "0.4.0",
|
"moxios": "0.4.0",
|
||||||
"sinon": "13.0.2",
|
"sinon": "13.0.2",
|
||||||
|
"ts-jest": "27.1.4",
|
||||||
"typescript": "4.6.3",
|
"typescript": "4.6.3",
|
||||||
"unplugin-vue2-script-setup": "0.10.2",
|
"unplugin-vue2-script-setup": "0.10.2",
|
||||||
"vite": "2.8.6",
|
"vite": "2.8.6",
|
||||||
|
"vite-plugin-pwa": "0.12.0",
|
||||||
"vite-plugin-vue2": "1.9.3",
|
"vite-plugin-vue2": "1.9.3",
|
||||||
"vue-jest": "3.0.7",
|
"vue-jest": "3.0.7",
|
||||||
"vue-template-compiler": "2.6.14"
|
"vue-template-compiler": "2.6.14",
|
||||||
|
"workbox-core": "6.5.3",
|
||||||
|
"workbox-precaching": "6.5.3",
|
||||||
|
"workbox-routing": "6.5.3",
|
||||||
|
"workbox-strategies": "6.5.3"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"vue-plyr/plyr": "3.6.12"
|
"vue-plyr/plyr": "3.6.12"
|
||||||
|
@ -132,14 +139,19 @@
|
||||||
],
|
],
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
|
"ts",
|
||||||
"js",
|
"js",
|
||||||
"json",
|
"json",
|
||||||
"vue"
|
"vue"
|
||||||
],
|
],
|
||||||
"transform": {
|
"transform": {
|
||||||
".*\\.(vue)$": "vue-jest",
|
".*\\.(vue)$": "vue-jest",
|
||||||
"^.+\\.js$": "babel-jest"
|
"^.+\\.js$": "babel-jest",
|
||||||
|
"^.+\\.ts$": "ts-jest"
|
||||||
},
|
},
|
||||||
|
"transformIgnorePatterns": [
|
||||||
|
"<rootDir>/node_modules/(?!lodash-es/.*)"
|
||||||
|
],
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
"^~/(.*)$": "<rootDir>/src/$1"
|
"^~/(.*)$": "<rootDir>/src/$1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
/* eslint no-undef: "off" */
|
|
||||||
|
|
||||||
// This is the code piece that GenerateSW mode can't provide for us.
|
|
||||||
// This code listens for the user's confirmation to update the app.
|
|
||||||
workbox.loadModule('workbox-routing')
|
|
||||||
workbox.loadModule('workbox-strategies')
|
|
||||||
workbox.loadModule('workbox-expiration')
|
|
||||||
|
|
||||||
self.addEventListener('message', (e) => {
|
|
||||||
if (!e.data) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.log('[sw] received message', e.data)
|
|
||||||
switch (e.data.command) {
|
|
||||||
case 'skipWaiting':
|
|
||||||
self.skipWaiting()
|
|
||||||
break
|
|
||||||
case 'serverChosen':
|
|
||||||
self.registerServerRoutes(e.data.serverUrl)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
// NOOP
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
workbox.core.clientsClaim()
|
|
||||||
|
|
||||||
const router = new workbox.routing.Router()
|
|
||||||
router.addCacheListener()
|
|
||||||
router.addFetchListener()
|
|
||||||
|
|
||||||
let registeredServerRoutes = []
|
|
||||||
self.registerServerRoutes = (serverUrl) => {
|
|
||||||
console.log('[sw] Setting up API caching for', serverUrl)
|
|
||||||
registeredServerRoutes.forEach((r) => {
|
|
||||||
console.log('[sw] Unregistering previous API route...', r)
|
|
||||||
router.unregisterRoute(r)
|
|
||||||
})
|
|
||||||
if (!serverUrl) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const regexReadyServerUrl = serverUrl.replace('.', '\\.')
|
|
||||||
registeredServerRoutes = []
|
|
||||||
const networkFirstPaths = [
|
|
||||||
'api/v1/',
|
|
||||||
'media/'
|
|
||||||
]
|
|
||||||
const networkFirstExcludedPaths = [
|
|
||||||
'api/v1/listen'
|
|
||||||
]
|
|
||||||
const strategy = new workbox.strategies.NetworkFirst({
|
|
||||||
cacheName: 'api-cache:' + serverUrl,
|
|
||||||
plugins: [
|
|
||||||
new workbox.expiration.Plugin({
|
|
||||||
maxAgeSeconds: 24 * 60 * 60 * 7
|
|
||||||
})
|
|
||||||
]
|
|
||||||
})
|
|
||||||
const networkFirstRoutes = networkFirstPaths.map((path) => {
|
|
||||||
const regex = new RegExp(regexReadyServerUrl + path)
|
|
||||||
return new workbox.routing.RegExpRoute(regex, () => {})
|
|
||||||
})
|
|
||||||
const matcher = ({ url, event }) => {
|
|
||||||
for (let index = 0; index < networkFirstExcludedPaths.length; index++) {
|
|
||||||
const blacklistedPath = networkFirstExcludedPaths[index]
|
|
||||||
if (url.pathname.startsWith('/' + blacklistedPath)) {
|
|
||||||
// the path is blacklisted, we don't cache it at all
|
|
||||||
console.log('[sw] Path is blacklisted, not caching', url.pathname)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// we call other regex matchers
|
|
||||||
for (let index = 0; index < networkFirstRoutes.length; index++) {
|
|
||||||
const route = networkFirstRoutes[index]
|
|
||||||
const result = route.match({ url, event })
|
|
||||||
if (result) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const route = new workbox.routing.Route(matcher, strategy)
|
|
||||||
console.log('[sw] registering new API route...', route)
|
|
||||||
router.registerRoute(route)
|
|
||||||
registeredServerRoutes.push(route)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The precaching code provided by Workbox.
|
|
||||||
self.__precacheManifest = [].concat(self.__precacheManifest || [])
|
|
||||||
|
|
||||||
// workbox.precaching.suppressWarnings(); // Only used with Vue CLI 3 and Workbox v3.
|
|
||||||
workbox.precaching.precacheAndRoute(self.__precacheManifest, {})
|
|
|
@ -111,64 +111,6 @@ const { width } = useWindowSize()
|
||||||
const player = ref()
|
const player = ref()
|
||||||
const showShortcutsModal = ref(false)
|
const showShortcutsModal = ref(false)
|
||||||
const showSetInstanceModal = ref(false)
|
const showSetInstanceModal = ref(false)
|
||||||
// export default {
|
|
||||||
// computed: {
|
|
||||||
// ...mapState({
|
|
||||||
// serviceWorker: state => state.ui.serviceWorker
|
|
||||||
// }),
|
|
||||||
// },
|
|
||||||
// watch: {
|
|
||||||
// 'serviceWorker.updateAvailable': {
|
|
||||||
// handler (v) {
|
|
||||||
// if (!v) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// const self = this
|
|
||||||
// this.$store.commit('ui/addMessage', {
|
|
||||||
// content: this.$pgettext('App/Message/Paragraph', 'A new version of the app is available.'),
|
|
||||||
// date: new Date(),
|
|
||||||
// key: 'refreshApp',
|
|
||||||
// displayTime: 0,
|
|
||||||
// classActions: 'bottom attached opaque',
|
|
||||||
// actions: [
|
|
||||||
// {
|
|
||||||
// text: this.$pgettext('App/Message/Paragraph', 'Update'),
|
|
||||||
// class: 'primary',
|
|
||||||
// click: function () {
|
|
||||||
// self.updateApp()
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// text: this.$pgettext('App/Message/Paragraph', 'Later'),
|
|
||||||
// class: 'basic'
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
// immediate: true
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// async created () {
|
|
||||||
// if (navigator.serviceWorker) {
|
|
||||||
// navigator.serviceWorker.addEventListener(
|
|
||||||
// 'controllerchange', () => {
|
|
||||||
// if (this.serviceWorker.refreshing) return
|
|
||||||
// this.$store.commit('ui/serviceWorker', {
|
|
||||||
// refreshing: true
|
|
||||||
// })
|
|
||||||
// window.location.reload()
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// methods: {
|
|
||||||
// updateApp () {
|
|
||||||
// this.$store.commit('ui/serviceWorker', { updateAvailable: false })
|
|
||||||
// if (!this.serviceWorker.registration || !this.serviceWorker.registration.waiting) { return }
|
|
||||||
// this.serviceWorker.registration.waiting.postMessage({ command: 'skipWaiting' })
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,45 +1,40 @@
|
||||||
import { InitModule } from '~/types'
|
import { AppModule } from '~/types'
|
||||||
import { register } from 'register-service-worker'
|
import { registerSW } from 'virtual:pwa-register'
|
||||||
|
import logger from '~/logging'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
export const install: InitModule = ({ store }) => {
|
const { $pgettext } = Vue.prototype
|
||||||
if (import.meta.env.PROD) {
|
|
||||||
register(`${import.meta.env.BASE_URL}service-worker.js`, {
|
export const install: AppModule = ({ store }) => {
|
||||||
registrationOptions: { scope: '/' },
|
const updateSW = registerSW({
|
||||||
ready () {
|
onRegisterError () {
|
||||||
console.log(
|
logger.default.error('SW install error')
|
||||||
'App is being served from cache by a service worker.'
|
},
|
||||||
)
|
onOfflineReady () {
|
||||||
},
|
logger.default.info('Funkwhale is being served from cache by a service worker.')
|
||||||
registered (registration) {
|
},
|
||||||
console.log('Service worker has been registered.')
|
onRegistered () {
|
||||||
// check for updates every 2 hours
|
logger.default.info('Service worker has been registered.')
|
||||||
const checkInterval = 1000 * 60 * 60 * 2
|
},
|
||||||
// var checkInterval = 1000 * 5
|
onNeedRefresh () {
|
||||||
setInterval(() => {
|
store.commit('ui/addMessage', {
|
||||||
console.log('Checking for service worker update…')
|
content: $pgettext('App/Message/Paragraph', 'A new version of the app is available.'),
|
||||||
registration.update()
|
date: new Date(),
|
||||||
}, checkInterval)
|
key: 'refreshApp',
|
||||||
store.commit('ui/serviceWorker', { registration: registration })
|
displayTime: 0,
|
||||||
if (registration.active) {
|
classActions: 'bottom attached opaque',
|
||||||
registration.active.postMessage({ command: 'serverChosen', serverUrl: store.state.instance.instanceUrl })
|
actions: [
|
||||||
}
|
{
|
||||||
},
|
text: $pgettext('App/Message/Paragraph', 'Update'),
|
||||||
cached () {
|
class: 'primary',
|
||||||
console.log('Content has been cached for offline use.')
|
click: () => updateSW()
|
||||||
},
|
},
|
||||||
updatefound () {
|
{
|
||||||
console.log('New content is downloading.')
|
text: $pgettext('App/Message/Paragraph', 'Later'),
|
||||||
},
|
class: 'basic'
|
||||||
updated (registration) {
|
}
|
||||||
console.log('New content is available; please refresh!')
|
]
|
||||||
store.commit('ui/serviceWorker', { updateAvailable: true, registration: registration })
|
})
|
||||||
},
|
}
|
||||||
offline () {
|
})
|
||||||
console.log('No internet connection found. App is running in offline mode.')
|
|
||||||
},
|
|
||||||
error (error) {
|
|
||||||
console.error('Error during service worker registration:', error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching'
|
||||||
|
import { NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'
|
||||||
|
import { ExpirationPlugin } from 'workbox-expiration'
|
||||||
|
import { registerRoute } from 'workbox-routing'
|
||||||
|
import { clientsClaim } from 'workbox-core'
|
||||||
|
|
||||||
|
declare let self: ServiceWorkerGlobalScope
|
||||||
|
|
||||||
|
// NOTE: Clean up outdated caches
|
||||||
|
// With each new production build, all precached assets
|
||||||
|
// that were modified are added to the cache. The old versions
|
||||||
|
// need to be removed manually.
|
||||||
|
cleanupOutdatedCaches()
|
||||||
|
|
||||||
|
// Let new service worker claim control of already open web pages
|
||||||
|
// https://developer.chrome.com/docs/workbox/modules/workbox-core/#clients-claim
|
||||||
|
clientsClaim()
|
||||||
|
|
||||||
|
// Support for an update prompt handled by VitePWA:
|
||||||
|
// https://vite-plugin-pwa.netlify.app/guide/prompt-for-update.html
|
||||||
|
self.addEventListener('message', (event) => {
|
||||||
|
if (event.data?.type === 'SKIP_WAITING') {
|
||||||
|
return self.skipWaiting()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// NOTE: Network-First cache for API calls
|
||||||
|
// We're using cache only when the user goes offline
|
||||||
|
registerRoute(({ url }) => {
|
||||||
|
if (url.pathname.startsWith('/api/v1/listen')) return false
|
||||||
|
return url.pathname.startsWith('/api/v1')
|
||||||
|
}, new NetworkFirst({
|
||||||
|
plugins: [
|
||||||
|
// Expire after a week
|
||||||
|
new ExpirationPlugin({ maxAgeSeconds: 7 * 24 * 3600 })
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
|
||||||
|
// NOTE: Stale-While-Revalidate cache for album covers
|
||||||
|
// We're serving from cache if available and making a request
|
||||||
|
// in the background to update the cache for next request
|
||||||
|
registerRoute(({ url }) => {
|
||||||
|
return url.pathname.startsWith('/media')
|
||||||
|
}, new StaleWhileRevalidate())
|
||||||
|
|
||||||
|
// Precache all assets and add routes for them
|
||||||
|
// https://developer.chrome.com/docs/workbox/reference/workbox-precaching/#method-precacheAndRoute
|
||||||
|
precacheAndRoute(self.__WB_MANIFEST)
|
|
@ -9,12 +9,6 @@ function getDefaultUrl () {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function notifyServiceWorker (registration, message) {
|
|
||||||
if (registration && registration.active) {
|
|
||||||
registration.active.postMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
|
@ -87,7 +81,7 @@ export default {
|
||||||
value = value + '/'
|
value = value + '/'
|
||||||
}
|
}
|
||||||
state.instanceUrl = value
|
state.instanceUrl = value
|
||||||
notifyServiceWorker(state.registration, { command: 'serverChosen', serverUrl: state.instanceUrl })
|
|
||||||
// append the URL to the list (and remove existing one if needed)
|
// append the URL to the list (and remove existing one if needed)
|
||||||
if (value) {
|
if (value) {
|
||||||
const index = state.knownInstances.indexOf(value)
|
const index = state.knownInstances.indexOf(value)
|
||||||
|
|
|
@ -174,11 +174,6 @@ export default {
|
||||||
orderingDirection: '-',
|
orderingDirection: '-',
|
||||||
ordering: 'creation_date'
|
ordering: 'creation_date'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
serviceWorker: {
|
|
||||||
refreshing: false,
|
|
||||||
registration: null,
|
|
||||||
updateAvailable: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
|
@ -310,9 +305,6 @@ export default {
|
||||||
state.routePreferences[route].orderingDirection = value
|
state.routePreferences[route].orderingDirection = value
|
||||||
},
|
},
|
||||||
|
|
||||||
serviceWorker: (state, value) => {
|
|
||||||
state.serviceWorker = { ...state.serviceWorker, ...value }
|
|
||||||
},
|
|
||||||
window: (state, value) => {
|
window: (state, value) => {
|
||||||
state.window = value
|
state.window = value
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,9 @@ export function parseAPIErrors (responseData: APIErrorResponse, parentField?: st
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = responseData[field]
|
const value = responseData[field]
|
||||||
if (value as string[]) {
|
if (Array.isArray(value)) {
|
||||||
errors.push(...(value as string[]).map(err => {
|
const values = value as string[]
|
||||||
|
errors.push(...values.map(err => {
|
||||||
return err.toLocaleLowerCase().includes('this field ')
|
return err.toLocaleLowerCase().includes('this field ')
|
||||||
? `${fieldName}: ${err}`
|
? `${fieldName}: ${err}`
|
||||||
: err
|
: err
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default {
|
||||||
|
|
||||||
return hours >= 1
|
return hours >= 1
|
||||||
? `${hours}:${pad(min)}:${pad(sec)}`
|
? `${hours}:${pad(min)}:${pad(sec)}`
|
||||||
: `${pad(min)}:${pad(sec)}`
|
: `${min}:${pad(sec)}`
|
||||||
},
|
},
|
||||||
durationFormatted (v: string) {
|
durationFormatted (v: string) {
|
||||||
const duration = parseInt(v)
|
const duration = parseInt(v)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {expect} from 'chai'
|
import {expect} from 'chai'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import {truncate, ago, capitalize, year, unique} from '~/filters'
|
import {truncate, ago, capitalize, year, unique} from '~/init/filters'
|
||||||
|
|
||||||
describe('filters', () => {
|
describe('filters', () => {
|
||||||
describe('truncate', () => {
|
describe('truncate', () => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var sinon = require('sinon')
|
var sinon = require('sinon')
|
||||||
import {expect} from 'chai'
|
import {expect} from 'chai'
|
||||||
|
import * as _ from 'lodash-es'
|
||||||
|
|
||||||
import store from '~/store/queue'
|
import store from '~/store/queue'
|
||||||
import { testAction } from '../../utils'
|
import { testAction } from '../../utils'
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"lib": ["DOM", "ESNext"],
|
"lib": ["DOM", "ESNext", "WebWorker"],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { defineConfig, HmrOptions } from 'vite'
|
import { defineConfig, HmrOptions } from 'vite'
|
||||||
import { createVuePlugin as Vue2 } from 'vite-plugin-vue2'
|
import { createVuePlugin as Vue2 } from 'vite-plugin-vue2'
|
||||||
import ScriptSetup from 'unplugin-vue2-script-setup/vite'
|
import ScriptSetup from 'unplugin-vue2-script-setup/vite'
|
||||||
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
// @ts-ignore
|
import { resolve } from 'path'
|
||||||
import path from 'path'
|
|
||||||
|
|
||||||
const port = +(process.env.VUE_PORT ?? 8080)
|
const port = +(process.env.VUE_PORT ?? 8080)
|
||||||
|
|
||||||
|
@ -29,6 +28,18 @@ export default defineConfig(() => ({
|
||||||
// https://github.com/antfu/unplugin-vue2-script-setup
|
// https://github.com/antfu/unplugin-vue2-script-setup
|
||||||
ScriptSetup(),
|
ScriptSetup(),
|
||||||
|
|
||||||
|
// https://github.com/antfu/vite-plugin-pwa
|
||||||
|
VitePWA({
|
||||||
|
strategies: 'injectManifest',
|
||||||
|
srcDir: 'src',
|
||||||
|
filename: 'serviceWorker.ts',
|
||||||
|
devOptions: {
|
||||||
|
enabled: true,
|
||||||
|
type: 'module',
|
||||||
|
navigateFallback: 'index.html'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'fix-fomantic-ui-css',
|
name: 'fix-fomantic-ui-css',
|
||||||
transform (src, id) {
|
transform (src, id) {
|
||||||
|
@ -41,7 +52,7 @@ export default defineConfig(() => ({
|
||||||
server: { port, hmr },
|
server: { port, hmr },
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'~': path.resolve(__dirname, './src')
|
'~': resolve(__dirname, './src')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|
840
front/yarn.lock
840
front/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue