diff --git a/build/service_worker_messages.js b/build/service_worker_messages.js index 167b9d39a..c078e8563 100644 --- a/build/service_worker_messages.js +++ b/build/service_worker_messages.js @@ -3,14 +3,20 @@ import { readFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' +const i18nDir = resolve( + dirname(dirname(fileURLToPath(import.meta.url))), + 'src/i18n' +) + +export const i18nFiles = languages.reduce((acc, lang) => { + const name = langCodeToJsonName(lang) + const file = resolve(i18nDir, name + '.json') + acc[lang] = file + return acc +}, {}) + export const generateServiceWorkerMessages = async () => { - const i18nDir = resolve( - dirname(dirname(fileURLToPath(import.meta.url))), - 'src/i18n' - ) - const msgArray = await Promise.all(languages.map(async lang => { - const name = langCodeToJsonName(lang) - const file = resolve(i18nDir, name + '.json') + const msgArray = await Promise.all(Object.entries(i18nFiles).map(async ([lang, file]) => { const fileContent = await readFile(file, 'utf-8') const msg = { notifications: JSON.parse(fileContent).notifications || {} diff --git a/build/sw_plugin.js b/build/sw_plugin.js index a4f08660d..90ab856ad 100644 --- a/build/sw_plugin.js +++ b/build/sw_plugin.js @@ -1,28 +1,43 @@ import { fileURLToPath } from 'node:url' -import { dirname } from 'node:path' +import { dirname, resolve } from 'node:path' import { readFile } from 'node:fs/promises' import { build } from 'vite' -import { generateServiceWorkerMessages } from './service_worker_messages.js' +import * as esbuild from 'esbuild' +import { generateServiceWorkerMessages, i18nFiles } from './service_worker_messages.js' +const getSWMessagesAsText = async () => { + const messages = await generateServiceWorkerMessages() + return `export default ${JSON.stringify(messages, undefined, 2)}` +} const projectRoot = dirname(dirname(fileURLToPath(import.meta.url))) export const devSwPlugin = ({ swSrc, swDest, + transformSW, + alias }) => { + const swFullSrc = resolve(projectRoot, swSrc) + const esbuildAlias = {} + Object.entries(alias).forEach(([source, dest]) => { + esbuildAlias[source] = dest.startsWith('/') ? projectRoot + dest : dest + }) + return { name: 'dev-sw-plugin', apply: 'serve', + configResolved (conf) { + }, resolveId (id) { const name = id.startsWith('/') ? id.slice(1) : id if (name === swDest) { - return swSrc + return swFullSrc } return null }, async load (id) { - if (id === swSrc) { - return readFile(swSrc, 'utf-8') + if (id === swFullSrc) { + return readFile(swFullSrc, 'utf-8') } return null }, @@ -31,27 +46,45 @@ export const devSwPlugin = ({ * during dev, and firefox does not support ESM as service worker * https://bugzilla.mozilla.org/show_bug.cgi?id=1360870 */ - // async transform (code, id) { - // if (id === swSrc) { - // console.log('load virtual') - // const res = await build({ - // entryPoints: [swSrc], - // bundle: true, - // write: false, - // outfile: 'sw-pleroma.js', - // alias: { - // 'src': projectRoot + '/src', - // }, - // define: { - // 'import.meta.glob': 'require' - // } - // }) - // console.log('res', res) - // const text = res.outputFiles[0].text - // console.log('text', text) - // return text - // } - // } + async transform (code, id) { + if (id === swFullSrc && transformSW) { + const res = await esbuild.build({ + entryPoints: [swSrc], + bundle: true, + write: false, + outfile: 'sw-pleroma.js', + alias: esbuildAlias, + plugins: [{ + name: 'vite-like-root-resolve', + setup (b) { + b.onResolve( + { filter: new RegExp(/^\//) }, + args => ({ + path: resolve(projectRoot, args.path.slice(1)) + }) + ) + } + }, { + name: 'sw-messages', + setup (b) { + b.onResolve( + { filter: new RegExp('^' + swMessagesName + '$') }, + args => ({ + path: args.path, + namespace: 'sw-messages' + })) + b.onLoad( + { filter: /.*/, namespace: 'sw-messages' }, + async () => ({ + contents: await getSWMessagesAsText() + })) + } + }] + }) + const text = res.outputFiles[0].text + return text + } + } } } @@ -112,6 +145,9 @@ export const swMessagesPlugin = () => { name: 'sw-messages-plugin', resolveId (id) { if (id === swMessagesName) { + Object.values(i18nFiles).forEach(f => { + this.addWatchFile(f) + }) return swMessagesNameResolved } else { return null @@ -119,8 +155,7 @@ export const swMessagesPlugin = () => { }, async load (id) { if (id === swMessagesNameResolved) { - const messages = await generateServiceWorkerMessages() - return `export default ${JSON.stringify(messages, undefined, 2)}` + return await getSWMessagesAsText() } return null } diff --git a/src/sw.js b/src/sw.js index 2929b0e59..3c53d5339 100644 --- a/src/sw.js +++ b/src/sw.js @@ -4,6 +4,9 @@ import { storage } from 'src/lib/storage.js' import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js' import { prepareNotificationObject } from './services/notification_utils/notification_utils.js' import { createI18n } from 'vue-i18n' +// Collects all messages for service workers +// Needed because service workers cannot use dynamic imports +// See /build/sw_plugin.js for more information import messages from 'virtual:pleroma-fe/service_worker_messages' const i18n = createI18n({ diff --git a/vite.config.js b/vite.config.js index 7d1310242..b308c98c9 100644 --- a/vite.config.js +++ b/vite.config.js @@ -8,6 +8,7 @@ import { VitePWA } from 'vite-plugin-pwa' import { devSwPlugin, buildSwPlugin, swMessagesPlugin } from './build/sw_plugin.js' import copyPlugin from './build/copy_plugin.js' +const localConfigPath = '/config/local.json' const getLocalDevSettings = async () => { try { const settings = (await import('./config/local.json')).default @@ -16,20 +17,38 @@ const getLocalDevSettings = async () => { // and that's how actual BE reports its url settings.target = settings.target.replace(/\/$/, '') } - console.info('Using local dev server settings (/config/local.json):') + console.info(`Using local dev server settings (${localConfigPath}):`) console.info(JSON.stringify(settings, null, 2)) return settings } catch (e) { - console.info('Local dev server settings not found (/config/local.json)', e) + console.info(`Local dev server settings not found (${localConfigPath}), using default`, e) return {} } } const projectRoot = dirname(fileURLToPath(import.meta.url)) +const getTransformSWSettings = (settings) => { + if ('transformSW' in settings) { + return settings.transformSW + } else { + console.info( + '`transformSW` is not present in your local settings.\n' + + 'This option controls whether the service worker should be bundled and transformed into iife (immediately-invoked function expression) during development.\n' + + 'If set to false, the service worker will be served as-is, as an ES Module.\n' + + 'Some browsers (e.g. Firefox) does not support ESM service workers.\n' + + 'To avoid surprises, it is defaulted to true, but this can be slow.\n' + + 'If you are using a browser that supports ESM service workers, you can set this option to false.\n' + + `No matter your choice, you can set the transformSW option in ${localConfigPath} in to disable this message.` + ) + return true + } +} + export default defineConfig(async ({ mode, command }) => { const settings = await getLocalDevSettings() const target = settings.target || 'http://localhost:4000/' + const transformSW = getTransformSWSettings(settings) const proxy = { '/api': { target, @@ -60,6 +79,11 @@ export default defineConfig(async ({ mode, command }) => { const swSrc = 'src/sw.js' const swDest = 'sw-pleroma.js' + const alias = { + src: '/src', + components: '/src/components', + ...(mode === 'test' ? { vue: 'vue/dist/vue.esm-bundler.js' } : {}) + } return { plugins: [ @@ -76,7 +100,7 @@ export default defineConfig(async ({ mode, command }) => { } }), vueJsx(), - devSwPlugin({ swSrc, swDest }), + devSwPlugin({ swSrc, swDest, transformSW, alias }), buildSwPlugin({ swSrc, swDest }), swMessagesPlugin(), copyPlugin({ @@ -85,16 +109,12 @@ export default defineConfig(async ({ mode, command }) => { }) ], resolve: { - alias: { - src: '/src', - components: '/src/components', - ...(mode === 'test' ? { vue: 'vue/dist/vue.esm-bundler.js' } : {}) - } + alias }, define: { 'process.env': JSON.stringify({ NODE_ENV: command === 'serve' ? 'development' : 'production', - HAS_MODULE_SERVICE_WORKER: command === 'serve' + HAS_MODULE_SERVICE_WORKER: command === 'serve' && !transformSW }), 'COMMIT_HASH': JSON.stringify('DEV'), 'DEV_OVERRIDES': JSON.stringify({})