diff --git a/src/helpers/getFullBaseUrl.ts b/src/helpers/getFullBaseUrl.ts new file mode 100644 index 0000000000..b0d9ef595f --- /dev/null +++ b/src/helpers/getFullBaseUrl.ts @@ -0,0 +1,14 @@ +/** + * Get full BASE_URL + * - including path + * - will always end with a trailing slash + */ +export function getFullBaseUrl() { + // (1) The injected BASE_URL is declared from the `resolvedBase` that might miss a trailing slash... + // see: https://github.com/vitejs/vite/blob/b35fe883fdc699ac1450882562872095abe9959b/packages/vite/src/node/config.ts#LL614C25-L614C25 + const rawBase = import.meta.env.BASE_URL + // (2) so we readd a slash like done here + // https://github.com/vitejs/vite/blob/b35fe883fdc699ac1450882562872095abe9959b/packages/vite/src/node/config.ts#L643 + // See this comment: https://github.com/vitejs/vite/pull/10723#issuecomment-1303627478 + return rawBase.endsWith('/') ? rawBase : rawBase + '/' +} \ No newline at end of file diff --git a/src/registerServiceWorker.ts b/src/registerServiceWorker.ts index a497d63ad2..8e0a30d2f6 100644 --- a/src/registerServiceWorker.ts +++ b/src/registerServiceWorker.ts @@ -2,8 +2,10 @@ import {register} from 'register-service-worker' +import {getFullBaseUrl} from './helpers/getFullBaseUrl' + if (import.meta.env.PROD) { - register('/sw.js', { + register(getFullBaseUrl() + 'sw.js', { ready() { console.log('App is being served from cache by a service worker.') }, diff --git a/src/router/index.ts b/src/router/index.ts index 6670e854d4..d031f34b57 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -81,7 +81,7 @@ const EditTeamComponent = () => import('@/views/teams/EditTeam.vue') const NewTeamComponent = () => import('@/views/teams/NewTeam.vue') const router = createRouter({ - history: createWebHistory(), + history: createWebHistory(import.meta.env.BASE_URL), scrollBehavior(to, from, savedPosition) { // If the user is using their forward/backward keys to navigate, we want to restore the scroll view if (savedPosition) { diff --git a/src/sw.ts b/src/sw.ts index b59a36ef0d..c687a1938a 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -1,10 +1,16 @@ /* eslint-disable no-console */ /* eslint-disable no-undef */ +import {getFullBaseUrl} from './helpers/getFullBaseUrl' + +declare let self: ServiceWorkerGlobalScope + +const fullBaseUrl = getFullBaseUrl() const workboxVersion = 'v6.5.4' -importScripts( `/workbox-${workboxVersion}/workbox-sw.js`) + +importScripts(`${fullBaseUrl}workbox-${workboxVersion}/workbox-sw.js`) workbox.setConfig({ - modulePathPrefix: `/workbox-${workboxVersion}`, + modulePathPrefix: `${fullBaseUrl}workbox-${workboxVersion}`, debug: Boolean(import.meta.env.VITE_WORKBOX_DEBUG), }) @@ -47,7 +53,7 @@ self.addEventListener('notificationclick', function (event) { switch (event.action) { case 'show-task': - clients.openWindow(`/tasks/${taskId}`) + clients.openWindow(`${fullBaseUrl}tasks/${taskId}`) break } }) diff --git a/tsconfig.app.json b/tsconfig.app.json index e059350b1e..b095907f04 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -5,7 +5,7 @@ "compilerOptions": { "composite": true, "baseUrl": ".", - "lib": ["ESNext"], + "lib": ["ESNext", "DOM", "WebWorker"], "importHelpers": true, "sourceMap": true, diff --git a/vite.config.ts b/vite.config.ts index 61b16162b7..b99634295c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,13 +2,13 @@ import {defineConfig, type PluginOption} from 'vite' import vue from '@vitejs/plugin-vue' import legacyFn from '@vitejs/plugin-legacy' -import { URL, fileURLToPath } from 'node:url' -import { dirname, resolve } from 'node:path' +import {URL, fileURLToPath} from 'node:url' +import {dirname, resolve} from 'node:path' import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite' -import {VitePWA} from 'vite-plugin-pwa' +import {VitePWA} from 'vite-plugin-pwa' import VitePluginInjectPreload from 'vite-plugin-inject-preload' -import {visualizer} from 'rollup-plugin-visualizer' +import {visualizer} from 'rollup-plugin-visualizer' import svgLoader from 'vite-svg-loader' import postcssPresetEnv from 'postcss-preset-env' import postcssEasings from 'postcss-easings' @@ -50,135 +50,135 @@ function createFontMatcher(fontNames: string[]) { // https://vitejs.dev/config/ export default defineConfig({ - // https://vitest.dev/config/ - test: { - environment: 'happy-dom', - }, - css: { - preprocessorOptions: { - scss: { - additionalData: PREFIXED_SCSS_STYLES, - charset: false, // fixes "@charset" must be the first rule in the file" warnings - }, + base: process.env.VIKUNJA_FRONTEND_BASE, + // https://vitest.dev/config/ + test: { + environment: 'happy-dom', + }, + css: { + preprocessorOptions: { + scss: { + additionalData: PREFIXED_SCSS_STYLES, + charset: false, // fixes "@charset" must be the first rule in the file" warnings }, - postcss: { - plugins: [ - postcssEasings(), - postcssEasingGradients(), + }, + postcss: { + plugins: [ + postcssEasings(), + postcssEasingGradients(), postcssPresetEnv(), - ], - }, - }, - plugins: [ - vue({ - reactivityTransform: true, - }), - legacy, - svgLoader({ - // Since the svgs are already manually optimized via https://jakearchibald.github.io/svgomg/ - // we don't need to optimize them again. - svgo: false, - }), - VueI18nPlugin({ - // TODO: only install needed stuff - // Whether to install the full set of APIs, components, etc. provided by Vue I18n. - // By default, all of them will be installed. - fullInstall: true, - include: resolve(dirname(pathSrc), './src/i18n/lang/**'), - }), - // https://github.com/Applelo/vite-plugin-inject-preload - VitePluginInjectPreload({ - files: [{ - match: createFontMatcher(['Quicksand', 'OpenSans', 'OpenSans-Italic']), - attributes: {crossorigin: 'anonymous'}, - }], - injectTo: 'custom', - }), - VitePWA({ - srcDir: 'src', - filename: 'sw.ts', - base: '/', - strategies: 'injectManifest', - injectRegister: false, - manifest: { - name: 'Vikunja', - short_name: 'Vikunja', - theme_color: '#1973ff', - icons: [ - { - src: './images/icons/android-chrome-192x192.png', - sizes: '192x192', - type: 'image/png', - }, - { - src: './images/icons/android-chrome-512x512.png', - sizes: '512x512', - type: 'image/png', - }, - { - src: './images/icons/icon-maskable.png', - sizes: '1024x1024', - type: 'image/png', - purpose: 'maskable', - }, - ], - start_url: '.', - display: 'standalone', - background_color: '#000000', - shortcuts: [ - { - name: 'Overview', - url: '/', - }, - { - name: 'Namespaces And Lists Overview', - short_name: 'Namespaces & Lists', - url: '/namespaces', - }, - { - name: 'Tasks Next Week', - short_name: 'Next Week', - url: '/tasks/by/week', - }, - { - name: 'Tasks Next Month', - short_name: 'Next Month', - url: '/tasks/by/month', - }, - { - name: 'Teams Overview', - short_name: 'Teams', - url: '/teams', - }, - ], - }, - }), - ], - resolve: { - alias: [ - { - find: '@', - replacement: pathSrc, - }, ], - extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'], }, - server: { - host: '127.0.0.1', // see: https://github.com/vitejs/vite/pull/8543 - port: 4173, - strictPort: true, - }, - build: { - target: 'esnext', - rollupOptions: { - plugins: [ - visualizer({ - filename: 'stats.html', - gzipSize: true, - // template: 'sunburst', - // brotliSize: true, - }) as PluginOption, + }, + plugins: [ + vue({ + reactivityTransform: true, + }), + legacy, + svgLoader({ + // Since the svgs are already manually optimized via https://jakearchibald.github.io/svgomg/ + // we don't need to optimize them again. + svgo: false, + }), + VueI18nPlugin({ + // TODO: only install needed stuff + // Whether to install the full set of APIs, components, etc. provided by Vue I18n. + // By default, all of them will be installed. + fullInstall: true, + include: resolve(dirname(pathSrc), './src/i18n/lang/**'), + }), + // https://github.com/Applelo/vite-plugin-inject-preload + VitePluginInjectPreload({ + files: [{ + match: createFontMatcher(['Quicksand', 'OpenSans', 'OpenSans-Italic']), + attributes: {crossorigin: 'anonymous'}, + }], + injectTo: 'custom', + }), + VitePWA({ + srcDir: 'src', + filename: 'sw.ts', + strategies: 'injectManifest', + injectRegister: false, + manifest: { + name: 'Vikunja', + short_name: 'Vikunja', + theme_color: '#1973ff', + icons: [ + { + src: './images/icons/android-chrome-192x192.png', + sizes: '192x192', + type: 'image/png', + }, + { + src: './images/icons/android-chrome-512x512.png', + sizes: '512x512', + type: 'image/png', + }, + { + src: './images/icons/icon-maskable.png', + sizes: '1024x1024', + type: 'image/png', + purpose: 'maskable', + }, + ], + start_url: '.', + display: 'standalone', + background_color: '#000000', + shortcuts: [ + { + name: 'Overview', + url: '/', + }, + { + name: 'Namespaces And Lists Overview', + short_name: 'Namespaces & Lists', + url: '/namespaces', + }, + { + name: 'Tasks Next Week', + short_name: 'Next Week', + url: '/tasks/by/week', + }, + { + name: 'Tasks Next Month', + short_name: 'Next Month', + url: '/tasks/by/month', + }, + { + name: 'Teams Overview', + short_name: 'Teams', + url: '/teams', + }, ], }, + }), + ], + resolve: { + alias: [ + { + find: '@', + replacement: pathSrc, + }, + ], + extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'], + }, + server: { + host: '127.0.0.1', // see: https://github.com/vitejs/vite/pull/8543 + port: 4173, + strictPort: true, + }, + build: { + target: 'esnext', + rollupOptions: { + plugins: [ + visualizer({ + filename: 'stats.html', + gzipSize: true, + // template: 'sunburst', + // brotliSize: true, + }) as PluginOption, + ], }, + }, })