pleroma-fe/build/sw_plugin.js

211 lines
5.9 KiB
JavaScript
Raw Normal View History

2025-02-28 10:52:04 -05:00
import { readFile } from 'node:fs/promises'
2026-01-06 16:23:17 +02:00
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
2025-02-28 13:29:27 -05:00
import * as esbuild from 'esbuild'
2026-01-06 16:23:17 +02:00
import { build } from 'vite'
2026-01-08 17:26:52 +02:00
2026-01-06 16:22:52 +02:00
import {
generateServiceWorkerMessages,
i18nFiles,
} from './service_worker_messages.js'
2025-02-28 10:52:04 -05:00
2025-02-28 13:29:27 -05:00
const getSWMessagesAsText = async () => {
const messages = await generateServiceWorkerMessages()
return `export default ${JSON.stringify(messages, undefined, 2)}`
}
2025-02-28 10:52:04 -05:00
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)))
2025-03-04 18:39:20 -05:00
const swEnvName = 'virtual:pleroma-fe/service_worker_env'
const swEnvNameResolved = '\0' + swEnvName
const getDevSwEnv = () => `self.serviceWorkerOption = { assets: [] };`
2026-01-06 16:22:52 +02:00
const getProdSwEnv = ({ assets }) =>
`self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };`
2025-03-04 18:39:20 -05:00
2026-01-06 16:22:52 +02:00
export const devSwPlugin = ({ swSrc, swDest, transformSW, alias }) => {
2025-02-28 13:29:27 -05:00
const swFullSrc = resolve(projectRoot, swSrc)
const esbuildAlias = {}
Object.entries(alias).forEach(([source, dest]) => {
esbuildAlias[source] = dest.startsWith('/') ? projectRoot + dest : dest
})
2025-02-28 10:52:04 -05:00
return {
name: 'dev-sw-plugin',
apply: 'serve',
2026-01-06 17:32:22 +02:00
configResolved() {
/* no-op */
},
2026-01-06 16:22:52 +02:00
resolveId(id) {
2025-02-28 10:52:04 -05:00
const name = id.startsWith('/') ? id.slice(1) : id
if (name === swDest) {
2025-02-28 13:29:27 -05:00
return swFullSrc
2025-03-04 18:39:20 -05:00
} else if (name === swEnvName) {
return swEnvNameResolved
2025-02-28 10:52:04 -05:00
}
return null
},
2026-01-06 16:22:52 +02:00
async load(id) {
2025-02-28 13:29:27 -05:00
if (id === swFullSrc) {
return readFile(swFullSrc, 'utf-8')
2025-03-04 18:39:20 -05:00
} else if (id === swEnvNameResolved) {
return getDevSwEnv()
2025-02-28 10:52:04 -05:00
}
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
*/
2026-01-06 16:22:52 +02:00
async transform(code, id) {
2025-02-28 13:29:27 -05:00
if (id === swFullSrc && transformSW) {
const res = await esbuild.build({
entryPoints: [swSrc],
bundle: true,
write: false,
outfile: 'sw-pleroma.js',
alias: esbuildAlias,
2026-01-06 16:22:52 +02:00
plugins: [
{
name: 'vite-like-root-resolve',
setup(b) {
b.onResolve({ filter: new RegExp(/^\//) }, (args) => ({
path: resolve(projectRoot, args.path.slice(1)),
2025-02-28 13:29:27 -05:00
}))
2026-01-06 16:22:52 +02:00
},
},
{
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(),
2025-03-04 18:39:20 -05:00
}))
2026-01-06 16:22:52 +02:00
},
},
],
2025-02-28 13:29:27 -05:00
})
const text = res.outputFiles[0].text
return text
}
2026-01-06 16:22:52 +02:00
},
2025-02-28 10:52:04 -05:00
}
}
// Idea taken from
// https://github.com/vite-pwa/vite-plugin-pwa/blob/main/src/plugins/build.ts
// rollup does not support compiling to iife if we want to code-split;
// however, we must compile the service worker to iife because of browser support.
// Run another vite build just for the service worker targeting iife at
// the end of the build.
2026-01-06 16:22:52 +02:00
export const buildSwPlugin = ({ swSrc, swDest }) => {
2025-02-28 10:52:04 -05:00
let config
return {
name: 'build-sw-plugin',
enforce: 'post',
apply: 'build',
2026-01-06 16:22:52 +02:00
configResolved(resolvedConfig) {
2025-02-28 10:52:04 -05:00
config = {
define: resolvedConfig.define,
resolve: resolvedConfig.resolve,
plugins: [swMessagesPlugin()],
publicDir: false,
build: {
...resolvedConfig.build,
lib: {
entry: swSrc,
formats: ['iife'],
2026-01-06 16:22:52 +02:00
name: 'sw_pleroma',
2025-02-28 10:52:04 -05:00
},
emptyOutDir: false,
rollupOptions: {
output: {
2026-01-06 16:22:52 +02:00
entryFileNames: swDest,
},
},
2025-02-28 10:52:04 -05:00
},
2026-01-06 16:22:52 +02:00
configFile: false,
2025-02-28 10:52:04 -05:00
}
},
2025-03-04 18:39:20 -05:00
generateBundle: {
order: 'post',
sequential: true,
2026-01-06 16:22:52 +02:00
async handler(_, bundle) {
2025-03-04 18:39:20 -05:00
const assets = Object.keys(bundle)
2026-01-06 16:22:52 +02:00
.filter((name) => !/\.map$/.test(name))
.map((name) => '/' + name)
2025-03-04 18:39:20 -05:00
config.plugins.push({
name: 'build-sw-env-plugin',
2026-01-06 16:22:52 +02:00
resolveId(id) {
2025-03-04 18:39:20 -05:00
if (id === swEnvName) {
return swEnvNameResolved
}
return null
},
2026-01-06 16:22:52 +02:00
load(id) {
2025-03-04 18:39:20 -05:00
if (id === swEnvNameResolved) {
return getProdSwEnv({ assets })
}
return null
2026-01-06 16:22:52 +02:00
},
2025-03-04 18:39:20 -05:00
})
2026-01-06 16:22:52 +02:00
},
2025-03-04 18:39:20 -05:00
},
2025-02-28 10:52:04 -05:00
closeBundle: {
order: 'post',
sequential: true,
2026-01-06 16:22:52 +02:00
async handler() {
console.info('Building service worker for production')
2025-02-28 10:52:04 -05:00
await build(config)
2026-01-06 16:22:52 +02:00
},
},
2025-02-28 10:52:04 -05:00
}
}
const swMessagesName = 'virtual:pleroma-fe/service_worker_messages'
const swMessagesNameResolved = '\0' + swMessagesName
export const swMessagesPlugin = () => {
return {
name: 'sw-messages-plugin',
2026-01-06 16:22:52 +02:00
resolveId(id) {
2025-02-28 10:52:04 -05:00
if (id === swMessagesName) {
2026-01-06 16:22:52 +02:00
Object.values(i18nFiles).forEach((f) => {
2025-02-28 13:29:27 -05:00
this.addWatchFile(f)
})
2025-02-28 10:52:04 -05:00
return swMessagesNameResolved
} else {
return null
}
},
2026-01-06 16:22:52 +02:00
async load(id) {
2025-02-28 10:52:04 -05:00
if (id === swMessagesNameResolved) {
2025-02-28 13:29:27 -05:00
return await getSWMessagesAsText()
2025-02-28 10:52:04 -05:00
}
return null
2026-01-06 16:22:52 +02:00
},
2025-02-28 10:52:04 -05:00
}
}