diff --git a/build/sw_plugin.js b/build/sw_plugin.js index 115e8ca3a..f8141920d 100644 --- a/build/sw_plugin.js +++ b/build/sw_plugin.js @@ -1,7 +1,7 @@ import { readFile } from 'node:fs/promises' -import { exactRegex } from '@rolldown/pluginutils' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' +import * as esbuild from 'esbuild' import { build } from 'vite' import { @@ -15,6 +15,110 @@ const getSWMessagesAsText = async () => { } const projectRoot = dirname(dirname(fileURLToPath(import.meta.url))) +const swEnvName = 'virtual:pleroma-fe/service_worker_env' +const swEnvNameResolved = '\0' + swEnvName +const getDevSwEnv = () => `self.serviceWorkerOption = { assets: [] };` +const getProdSwEnv = ({ assets }) => + `self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };` + +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 + }) + let localConfig + + return { + name: 'dev-sw-plugin', + apply: 'serve', + configResolved(config) { + localConfig = config + }, + resolveId(id) { + const name = id.startsWith('/') ? id.slice(1) : id + if (name === swDest) { + return swFullSrc + } else if (name === swEnvName) { + return swEnvNameResolved + } + return null + }, + async load(id) { + if (id === swFullSrc) { + return readFile(swFullSrc, 'utf-8') + } else if (id === swEnvNameResolved) { + return getDevSwEnv() + } + return null + }, + /** + * vite does not bundle the service worker + * 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 === swFullSrc) { + const res = await esbuild.build({ + entryPoints: [swSrc], + define: { + 'process.env': JSON.stringify(localConfig.define['process.env']), + 'COMMIT_HASH': JSON.stringify(localConfig.define['COMMIT_HASH']), + }, + 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(), + }), + ) + }, + }, + { + name: 'sw-env', + setup(b) { + b.onResolve( + { filter: new RegExp('^' + swEnvName + '$') }, + (args) => ({ + path: args.path, + namespace: 'sw-env', + }), + ) + b.onLoad({ filter: /.*/, namespace: 'sw-env' }, () => ({ + contents: getDevSwEnv(), + })) + }, + }, + ], + }) + const text = res.outputFiles[0].text + return text + } + }, + } +} // Idea taken from // https://github.com/vite-pwa/vite-plugin-pwa/blob/main/src/plugins/build.ts @@ -23,17 +127,12 @@ const projectRoot = dirname(dirname(fileURLToPath(import.meta.url))) // Run another vite build just for the service worker targeting iife at // the end of the build. export const buildSwPlugin = ({ swSrc, swDest }) => { - const swFullSrc = resolve(projectRoot, swSrc) - const swEnvName = 'virtual:pleroma-fe/service_worker_env' - const swEnvNameResolved = '\0' + swEnvName - - let rootConfig, config - + let config return { name: 'build-sw-plugin', enforce: 'post', + apply: 'build', configResolved(resolvedConfig) { - rootConfig = resolvedConfig config = { define: resolvedConfig.define, resolve: resolvedConfig.resolve, @@ -64,49 +163,23 @@ export const buildSwPlugin = ({ swSrc, swDest }) => { const assets = Object.keys(bundle) .filter((name) => !/\.map$/.test(name)) .map((name) => '/' + name) - config.plugins.push({ name: 'build-sw-env-plugin', - mode: 'production', - resolveId: { - filter: { id: exactRegex(swEnvName) }, - handler: () => swEnvNameResolved, - }, - load: { - filter: { id: exactRegex(swEnvNameResolved) }, - handler() { - return `self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };` + resolveId(id) { + if (id === swEnvName) { + return swEnvNameResolved } + return null + }, + load(id) { + if (id === swEnvNameResolved) { + return getProdSwEnv({ assets }) + } + return null }, }) }, }, - resolveId: { - filter: { id: new RegExp(swDest) }, - handler() { - return swFullSrc - } - }, - load: { - filter: { id: new RegExp(swFullSrc) }, - async handler() { - config.plugins.push({ - name: 'dummy-sw-env', - mode: 'development', - resolveId: { - filter: { id: exactRegex(swEnvName) }, - handler: () => swEnvNameResolved, - }, - load: { - filter: { id: exactRegex(swEnvNameResolved) }, - handler: () => 'self.serviceWorkerOption = { assets: [] }', - } - }) - - const swBundle = await build(config) - return swBundle.output[0] - } - }, closeBundle: { order: 'post', sequential: true, diff --git a/vite.config.js b/vite.config.js index 63508932e..9586b1aeb 100644 --- a/vite.config.js +++ b/vite.config.js @@ -14,6 +14,7 @@ import emojisPlugin from './build/emojis_plugin.js' import mswPlugin from './build/msw_plugin.js' import { buildSwPlugin, + devSwPlugin, swMessagesPlugin, } from './build/sw_plugin.js' @@ -141,6 +142,7 @@ export default defineConfig(async ({ mode, command }) => { // outDir: 'custom-dir', // optional, defaults to Vite's build.outDir }, }), + devSwPlugin({ swSrc, swDest, transformSW, alias }), buildSwPlugin({ swSrc, swDest }), swMessagesPlugin(), emojisPlugin(),