From 865fdade29fa35aa7c16b899a58d6200ddbdd4f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20S=C3=A1nchez?= Date: Sat, 10 Feb 2024 14:41:10 +0100 Subject: [PATCH] feat!: new custom service worker build (#629) * feat: new custom service worker build * chore: extract vite build shared inline options * chore: add support for esbuild sourcemap + vite build target * chore: cleanup modules.ts * chore: change sourcemap in generated sw + add more info to logs * chore: refactor custom service worker's vite build logic * chore: allow integrations to modify build options * chore: cleanup * chore: allow custom Rollup and Vite plugins * chore: register virtual message plugin in vite --- examples/preact-router/vite.config.ts | 4 + examples/react-router/vite.config.ts | 4 + examples/solid-router/vite.config.ts | 4 + examples/svelte-routify/vite.config.js | 4 + examples/vanilla-ts-no-ip/package.json | 5 +- examples/vanilla-ts-no-ip/src/custom-sw.ts | 3 + examples/vanilla-ts-no-ip/vite.config.ts | 3 + examples/vue-router/src/claims-sw.ts | 3 + examples/vue-router/src/prompt-sw.ts | 3 + examples/vue-router/src/shims-vue.d.ts | 5 + examples/vue-router/test/build.test.ts | 5 +- examples/vue-router/vite.config.ts | 24 ++++ pnpm-lock.yaml | 7 + src/log.ts | 44 +++++- src/modules.ts | 64 +-------- src/options.ts | 14 +- src/types.ts | 74 +++++++++- src/vite-build.ts | 160 +++++++++++++++++++++ 18 files changed, 359 insertions(+), 71 deletions(-) create mode 100644 src/vite-build.ts diff --git a/examples/preact-router/vite.config.ts b/examples/preact-router/vite.config.ts index 2c689aa5..895e8979 100644 --- a/examples/preact-router/vite.config.ts +++ b/examples/preact-router/vite.config.ts @@ -49,6 +49,10 @@ if (process.env.SW === 'true') { pwaOptions.strategies = 'injectManifest' ;(pwaOptions.manifest as Partial).name = 'PWA Inject Manifest' ;(pwaOptions.manifest as Partial).short_name = 'PWA Inject' + pwaOptions.injectManifest = { + minify: false, + enableWorkboxModulesLogs: true, + } } if (claims) diff --git a/examples/react-router/vite.config.ts b/examples/react-router/vite.config.ts index 3a22dbf0..7fbd0dfe 100644 --- a/examples/react-router/vite.config.ts +++ b/examples/react-router/vite.config.ts @@ -51,6 +51,10 @@ if (process.env.SW === 'true') { pwaOptions.strategies = 'injectManifest' ;(pwaOptions.manifest as Partial).name = 'PWA Inject Manifest' ;(pwaOptions.manifest as Partial).short_name = 'PWA Inject' + pwaOptions.injectManifest = { + minify: false, + enableWorkboxModulesLogs: true, + } } if (claims) diff --git a/examples/solid-router/vite.config.ts b/examples/solid-router/vite.config.ts index 6108c888..157e59c9 100644 --- a/examples/solid-router/vite.config.ts +++ b/examples/solid-router/vite.config.ts @@ -49,6 +49,10 @@ if (process.env.SW === 'true') { pwaOptions.strategies = 'injectManifest' ;(pwaOptions.manifest as Partial).name = 'PWA Inject Manifest' ;(pwaOptions.manifest as Partial).short_name = 'PWA Inject' + pwaOptions.injectManifest = { + minify: false, + enableWorkboxModulesLogs: true, + } } if (claims) diff --git a/examples/svelte-routify/vite.config.js b/examples/svelte-routify/vite.config.js index 39f8f2fd..81d6d92d 100644 --- a/examples/svelte-routify/vite.config.js +++ b/examples/svelte-routify/vite.config.js @@ -49,6 +49,10 @@ if (process.env.SW === 'true') { pwaOptions.strategies = 'injectManifest' pwaOptions.manifest.name = 'PWA Inject Manifest' pwaOptions.manifest.short_name = 'PWA Inject' + pwaOptions.injectManifest = { + minify: false, + enableWorkboxModulesLogs: true, + } } if (claims) diff --git a/examples/vanilla-ts-no-ip/package.json b/examples/vanilla-ts-no-ip/package.json index a7d82c2d..bb9a2d62 100644 --- a/examples/vanilla-ts-no-ip/package.json +++ b/examples/vanilla-ts-no-ip/package.json @@ -5,15 +5,18 @@ "private": true, "scripts": { "dev": "rimraf dev-dist && DEBUG=vite-plugin-pwa SW_DEV=true vite --force", + "dev-custom": "rimraf dev-dist && DEBUG=vite-plugin-pwa SW=true SW_DEV=true vite --force", "run-build-sw": "DEBUG=vite-plugin-pwa BASE_URL=/ SOURCE_MAP=true vite build", + "run-build-custom-sw": "DEBUG=vite-plugin-pwa BASE_URL=/ SOURCE_MAP=true SW=true vite build", "start-sw": "nr run-build-sw && nr serve", "serve": "serve dist", "start-preview": "vite preview --port=4173", - "test-custom-sw": "DEBUG=vite-plugin-pwa BASE_URL=/ SOURCE_MAP=true SW=true vite build && SW=true vitest run && SW=true playwright test", + "test-custom-sw": "nr run-build-custom-sw && SW=true vitest run && SW=true playwright test", "test-generate-sw": "nr run-build-sw && vitest run && playwright test", "test": "nr test-generate-sw && nr test-custom-sw" }, "devDependencies": { + "lodash-es": "^4.17.21", "rimraf": "^5.0.5", "typescript": "^5.2.2", "vite": "^5.0.0", diff --git a/examples/vanilla-ts-no-ip/src/custom-sw.ts b/examples/vanilla-ts-no-ip/src/custom-sw.ts index c992508d..0b86c8f0 100644 --- a/examples/vanilla-ts-no-ip/src/custom-sw.ts +++ b/examples/vanilla-ts-no-ip/src/custom-sw.ts @@ -3,6 +3,9 @@ import { registerRoute } from 'workbox-routing' import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies' import { CacheableResponsePlugin } from 'workbox-cacheable-response' import { ExpirationPlugin } from 'workbox-expiration' +import orderBy from 'lodash-es/orderBy.js' + +console.log(orderBy) declare let self: ServiceWorkerGlobalScope diff --git a/examples/vanilla-ts-no-ip/vite.config.ts b/examples/vanilla-ts-no-ip/vite.config.ts index 7c79c11f..d3493aaa 100644 --- a/examples/vanilla-ts-no-ip/vite.config.ts +++ b/examples/vanilla-ts-no-ip/vite.config.ts @@ -5,6 +5,7 @@ import { VitePWA } from 'vite-plugin-pwa' const customSW = process.env.SW === 'true' export default defineConfig({ + mode: 'development', logLevel: 'info', define: { __DATE__: `'${new Date().toISOString()}'`, @@ -51,6 +52,8 @@ export default defineConfig({ skipWaiting: true, }, injectManifest: { + minify: false, + enableWorkboxModulesLogs: true, injectionPoint: undefined, }, devOptions: { diff --git a/examples/vue-router/src/claims-sw.ts b/examples/vue-router/src/claims-sw.ts index 32a2f72f..81b8900a 100644 --- a/examples/vue-router/src/claims-sw.ts +++ b/examples/vue-router/src/claims-sw.ts @@ -1,9 +1,12 @@ import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from 'workbox-precaching' import { clientsClaim } from 'workbox-core' import { NavigationRoute, registerRoute } from 'workbox-routing' +import { message } from 'virtual:message' declare let self: ServiceWorkerGlobalScope +console.log(message) + // self.__WB_MANIFEST is default injection point precacheAndRoute(self.__WB_MANIFEST) diff --git a/examples/vue-router/src/prompt-sw.ts b/examples/vue-router/src/prompt-sw.ts index 24325a1c..fbf47674 100644 --- a/examples/vue-router/src/prompt-sw.ts +++ b/examples/vue-router/src/prompt-sw.ts @@ -1,8 +1,11 @@ import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from 'workbox-precaching' import { NavigationRoute, registerRoute } from 'workbox-routing' +import { message } from 'virtual:message' declare let self: ServiceWorkerGlobalScope +console.log(message) + self.addEventListener('message', (event) => { if (event.data && event.data.type === 'SKIP_WAITING') self.skipWaiting() diff --git a/examples/vue-router/src/shims-vue.d.ts b/examples/vue-router/src/shims-vue.d.ts index b8724aa1..10c30055 100644 --- a/examples/vue-router/src/shims-vue.d.ts +++ b/examples/vue-router/src/shims-vue.d.ts @@ -4,3 +4,8 @@ declare module '*.vue' { const Component: ReturnType export default Component } + +declare module 'virtual:message' { + const message: string + export { message } +} diff --git a/examples/vue-router/test/build.test.ts b/examples/vue-router/test/build.test.ts index 25d8c23a..c1d240e4 100644 --- a/examples/vue-router/test/build.test.ts +++ b/examples/vue-router/test/build.test.ts @@ -23,7 +23,10 @@ describe('vue 3: test-build', () => { expect(existsSync(webManifest), `${webManifest} doesn't exist`).toBeTruthy() const swContent = readFileSync(swPath, 'utf-8') let match: RegExpMatchArray | null - if (!injectManifest) { + if (injectManifest) { + expect(swContent.includes('Message from Virtual Module Plugin'), 'missing virtual module message').toBeTruthy() + } + else { match = swContent.match(/define\(\['\.\/(workbox-\w+)'/) // vite 5 beta 8 change rollup from v3 to v4: sw deps now inlined if (match) { diff --git a/examples/vue-router/vite.config.ts b/examples/vue-router/vite.config.ts index 8e13e65d..d8fc1b7a 100644 --- a/examples/vue-router/vite.config.ts +++ b/examples/vue-router/vite.config.ts @@ -1,4 +1,5 @@ import process from 'node:process' +import type { Plugin } from 'vite' import { defineConfig } from 'vite' import Vue from '@vitejs/plugin-vue' import type { ManifestOptions, VitePWAOptions } from 'vite-plugin-pwa' @@ -44,12 +45,34 @@ const pwaOptions: Partial = { const claims = process.env.CLAIMS === 'true' const selfDestroying = process.env.SW_DESTROY === 'true' +function virtualMessagePlugin() { + const virtual = 'virtual:message' + const resolvedVirtual = `\0${virtual}` + return { + name: 'vite-plugin-test', + resolveId(id) { + return id === virtual ? resolvedVirtual : null + }, + load(id) { + if (id === resolvedVirtual) + return `export const message = 'Message from Virtual Module Plugin'` + }, + } satisfies Plugin +} + if (process.env.SW === 'true') { pwaOptions.srcDir = 'src' pwaOptions.filename = claims ? 'claims-sw.ts' : 'prompt-sw.ts' pwaOptions.strategies = 'injectManifest' ;(pwaOptions.manifest as Partial).name = 'PWA Inject Manifest' ;(pwaOptions.manifest as Partial).short_name = 'PWA Inject' + pwaOptions.injectManifest = { + minify: false, + enableWorkboxModulesLogs: true, + buildPlugins: { + vite: [virtualMessagePlugin()], + }, + } } if (claims) @@ -65,6 +88,7 @@ export default defineConfig({ }, plugins: [ Vue(), + virtualMessagePlugin(), VitePWA(pwaOptions), replace({ __DATE__: new Date().toISOString(), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7bc8bf40..293db3ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -436,6 +436,9 @@ importers: examples/vanilla-ts-no-ip: devDependencies: + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 rimraf: specifier: ^5.0.5 version: 5.0.5 @@ -7577,6 +7580,10 @@ packages: p-locate: 5.0.0 dev: true + /lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + dev: true + /lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} dev: false diff --git a/src/log.ts b/src/log.ts index ab2c3355..692f9fe1 100644 --- a/src/log.ts +++ b/src/log.ts @@ -4,8 +4,33 @@ import type { BuildResult } from 'workbox-build' import type { ResolvedConfig } from 'vite' import { cyan, dim, green, magenta, yellow } from 'kolorist' import { version } from '../package.json' +import { normalizePath } from './utils' +import type { ResolvedVitePWAOptions } from './types' -export function logWorkboxResult(strategy: string, buildResult: BuildResult, viteOptions: ResolvedConfig) { +export function logSWViteBuild( + swName: string, + viteOptions: ResolvedConfig, + format: 'es' | 'iife', +) { + const { logLevel = 'info' } = viteOptions + if (logLevel === 'silent') + return + + if (logLevel === 'info') { + console.info([ + '', + `${cyan(`PWA v${version}`)}`, + `Building ${magenta(swName)} service worker ("${magenta(format)}" format)...`, + ].join('\n')) + } +} + +export function logWorkboxResult( + strategy: ResolvedVitePWAOptions['strategies'], + buildResult: BuildResult, + viteOptions: ResolvedConfig, + format: 'es' | 'iife' | 'none' = 'none', +) { const { root, logLevel = 'info' } = viteOptions if (logLevel === 'silent') @@ -14,14 +39,21 @@ export function logWorkboxResult(strategy: string, buildResult: BuildResult, vit const { count, size, filePaths, warnings } = buildResult if (logLevel === 'info') { - console.info([ + const entries = [ '', `${cyan(`PWA v${version}`)}`, `mode ${magenta(strategy)}`, - `precache ${green(`${count} entries`)} ${dim(`(${(size / 1024).toFixed(2)} KiB)`)}`, - 'files generated', - ...filePaths.map(p => ` ${dim(relative(root, p))}`), - ].join('\n')) + ] + if (strategy === 'injectManifest') + entries.push(`format: ${magenta(format)}`) + + entries.push( + `precache ${green(`${count} entries`)} ${dim(`(${(size / 1024).toFixed(2)} KiB)`)}`, + 'files generated', + ...filePaths.map(p => ` ${dim(normalizePath(relative(root, p)))}`), + ) + + console.info(entries.join('\n')) } // log build warning diff --git a/src/modules.ts b/src/modules.ts index 52d5aa55..34431b90 100644 --- a/src/modules.ts +++ b/src/modules.ts @@ -94,64 +94,10 @@ export async function generateInjectManifest(options: ResolvedVitePWAOptions, vi return } - // we will have something like this from swSrc: - /* - // sw.js - import { precacheAndRoute } from 'workbox-precaching' - // self.__WB_MANIFEST is default injection point - precacheAndRoute(self.__WB_MANIFEST) - */ + const [workbox, buildSW] = await Promise.all([ + loadWorkboxBuild(), + import('./vite-build').then(({ buildSW }) => buildSW), + ]) - const { build } = await import('vite') - - const define: Record = { ...(viteOptions.define ?? {}) } - define['process.env.NODE_ENV'] = JSON.stringify(options.mode) - - const { format, plugins, rollupOptions } = options.injectManifestRollupOptions - - await build({ - root: viteOptions.root, - base: viteOptions.base, - resolve: viteOptions.resolve, - // don't copy anything from public folder - publicDir: false, - build: { - sourcemap: viteOptions.build.sourcemap, - lib: { - entry: options.swSrc, - name: 'app', - formats: [format], - }, - rollupOptions: { - ...rollupOptions, - plugins, - output: { - entryFileNames: options.filename, - }, - }, - outDir: options.outDir, - emptyOutDir: false, - }, - configFile: false, - define, - }) - - // don't force user to include injection point - if (!options.injectManifest.injectionPoint) - return - - await options.integration?.beforeBuildServiceWorker?.(options) - - const injectManifestOptions = { - ...options.injectManifest, - // this will not fail since there is an injectionPoint - swSrc: options.injectManifest.swDest, - } - - const { injectManifest } = await loadWorkboxBuild() - - // inject the manifest - const buildResult = await injectManifest(injectManifestOptions) - // log workbox result - logWorkboxResult('injectManifest', buildResult, viteOptions) + await buildSW(options, viteOptions, workbox) } diff --git a/src/options.ts b/src/options.ts index 85b4f1ad..3ebe6fcd 100644 --- a/src/options.ts +++ b/src/options.ts @@ -114,9 +114,14 @@ export async function resolveOptions(options: Partial, viteConfi : Object.assign({}, defaultManifest, options.manifest || {}) const { vitePlugins = defaultInjectManifestVitePlugins, - plugins = [], + plugins, rollupOptions = {}, rollupFormat = 'es', + target = viteConfig.build.target, + minify: minifySW = viteConfig.build.minify, + sourcemap = viteConfig.build.sourcemap, + enableWorkboxModulesLogs, + buildPlugins, ...userInjectManifest } = options.injectManifest || {} const injectManifest = Object.assign({}, defaultInjectManifest, userInjectManifest) @@ -191,6 +196,7 @@ export async function resolveOptions(options: Partial, viteConfi devOptions, rollupFormat, vitePlugins, + buildPlugins, selfDestroying, buildBase: buildBase ?? basePath, injectManifestRollupOptions: { @@ -198,6 +204,12 @@ export async function resolveOptions(options: Partial, viteConfi rollupOptions, format: rollupFormat, }, + injectManifestBuildOptions: { + target, + minify: minifySW, + sourcemap, + enableWorkboxModulesLogs, + }, } // calculate hash only when required diff --git a/src/types.ts b/src/types.ts index 9036235b..2817bdc3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -import type { Plugin, ResolvedConfig } from 'vite' +import type { BuildOptions, Plugin, ResolvedConfig, UserConfig } from 'vite' import type { GenerateSWOptions, InjectManifestOptions, ManifestEntry } from 'workbox-build' import type { OutputBundle, RollupOptions } from 'rollup' @@ -10,6 +10,39 @@ export type CustomInjectManifestOptions = InjectManifestOptions & { * @default 'es' */ rollupFormat?: 'es' | 'iife' + /** + * Configure the custom Vite build target option. + * + * @default Vite build target option + * @since v0.18.0 + */ + target?: BuildOptions['target'] + /** + * Configure the custom Vite build minify option. + * + * @default Vite build minify option + * @since v0.18.0 + */ + minify?: BuildOptions['minify'] + /** + * Configure the custom Vite build sourcemap option. + * + * @default Vite build sourcemap option + * @since v0.18.0 + */ + sourcemap?: BuildOptions['sourcemap'] + /** + * Should use `process.env.NODE_ENV` to remove dead code? + * + * If you want to keep logs from `workbox` modules, you can set this option to `true`, + * the plugin will configure `process.env.NODE_ENV` to `"development"`. + * + * If this option is not configured, the plugin will use `process.env.NODE_ENV`. + * + * @default `process.env.NODE_ENV === 'production'` + * @since v0.18.0 + */ + enableWorkboxModulesLogs?: true /** * `Vite` plugin ids to use on `Rollup` build. * @@ -26,8 +59,25 @@ export type CustomInjectManifestOptions = InjectManifestOptions & { * Both configurations cannot be shared, and so you'll need to duplicate the configuration, with the exception of `define`. * * **WARN**: this option is for advanced usage, beware, you can break your application build. + * + * This option will be ignored if `buildPlugins.rollup` is configured. + * + * @deprecated use `buildPlugins` instead */ plugins?: Plugin[] + /** + * Since `v0.18.0` you can add custom Rollup and/or Vite plugins to build your service worker. + * + * **WARN**: don't share plugins between the application and the service worker build, you need to include new plugins for each configuration. + * + * If you are using `plugins` option, use this option to configure the Rollup plugins or move them to `vite` option. + * + * **WARN**: this option is for advanced usage, beware, you can break your application build. + */ + buildPlugins?: { + rollup?: RollupOptions['plugins'] + vite?: UserConfig['plugins'] + } /** * Since `v0.15.0` you can add custom Rollup options to build your service worker: we expose the same configuration to build a worker using Vite. */ @@ -48,8 +98,19 @@ export interface PWAIntegration { */ export interface VitePWAOptions { /** - * Build mode + * Build mode. * + * From `v0.18.0` this option is ignored when using `injectManifest` strategy: + * - the new Vite build will use the same mode as the application when using `injectManifest` strategy. + * - if you don't want to minify your service worker, configure `injectManifest.minify = false` in your PWA configuration. + * - if you want the sourcemap only for the service worker, configure `injectManifest.sourcemap = true` in your PWA configuration. + * - if you want workbox logs in your service worker when using production build, configure `injectManifest.enableWorkboxModulesLogs = true` in your PWA configuration. + * - you can use `import.meta.env.MODE` to access the Vite mode inside your service worker. + * - you can use `import.meta.env.DEV` or `import.meta.env.PROD` to check if the service worker is + * running on development or production (equivalent to `process.env.NODE_ENV`), + * check Vite [NODE_ENV and Modes](https://vitejs.dev/guide/env-and-mode#node-env-and-modes) docs. + * + * @see https://vitejs.dev/guide/env-and-mode#node-env-and-modes * @default process.env.NODE_ENV or "production" */ mode?: 'development' | 'production' @@ -190,7 +251,7 @@ export interface VitePWAOptions { export interface ResolvedServiceWorkerOptions { format: 'es' | 'iife' - plugins: Plugin[] + plugins?: Plugin[] rollupOptions: RollupOptions } @@ -201,7 +262,14 @@ export interface ResolvedVitePWAOptions extends Required { injectManifest: InjectManifestOptions rollupFormat: 'es' | 'iife' vitePlugins: InjectManifestVitePlugins + buildPlugins?: CustomInjectManifestOptions['buildPlugins'] injectManifestRollupOptions: ResolvedServiceWorkerOptions + injectManifestBuildOptions: { + target?: BuildOptions['target'] + minify?: BuildOptions['minify'] + sourcemap?: BuildOptions['sourcemap'] + enableWorkboxModulesLogs?: true + } } export interface ShareTargetFiles { diff --git a/src/vite-build.ts b/src/vite-build.ts new file mode 100644 index 00000000..f4f57eba --- /dev/null +++ b/src/vite-build.ts @@ -0,0 +1,160 @@ +import { basename, relative } from 'node:path' +import { promises as fs } from 'node:fs' +import type { InlineConfig, ResolvedConfig } from 'vite' +import type { ResolvedVitePWAOptions } from './types' +import { logSWViteBuild, logWorkboxResult } from './log' +import { normalizePath } from './utils' + +export async function buildSW( + options: ResolvedVitePWAOptions, + viteOptions: ResolvedConfig, + workbox: typeof import('workbox-build'), +) { + // we will have something like this from swSrc: + /* + // sw.js + import { precacheAndRoute } from 'workbox-precaching' + // self.__WB_MANIFEST is default injection point + precacheAndRoute(self.__WB_MANIFEST) + */ + + // allow integrations to modify also build options + await options.integration?.beforeBuildServiceWorker?.(options) + + const { build } = await import('vite') + + const { + inlineConfig, + format, + swName, + swMjsName, + } = prepareViteBuild(options, viteOptions) + + // log sw build + logSWViteBuild(normalizePath(relative(viteOptions.root, options.swSrc)), viteOptions, format) + + await build(inlineConfig) + + if (format !== 'iife') { + await fs.rename( + `${options.outDir}/${swMjsName}`, + `${options.outDir}/${swName}`, + ) + const sourceMap = await fs.lstat(`${options.outDir}/${swMjsName}.map`).then(s => s.isFile()).catch(() => false) + if (sourceMap) { + await Promise.all([ + fs.readFile(`${options.outDir}/${swName}`, 'utf-8').then(content => fs.writeFile( + `${options.outDir}/${swName}`, + content.replace(`${swMjsName}.map`, `${swName}.map`), + 'utf-8', + )), + fs.rename(`${options.outDir}/${swMjsName}.map`, `${options.outDir}/${swName}.map`), + ]) + } + } + + // don't force user to include injection point + if (!options.injectManifest.injectionPoint) + return + + const injectManifestOptions = { + ...options.injectManifest, + // this will not fail since there is an injectionPoint + swSrc: options.injectManifest.swDest, + } + + const { injectManifest } = workbox + + // inject the manifest + const buildResult = await injectManifest(injectManifestOptions) + // log workbox result + logWorkboxResult('injectManifest', buildResult, viteOptions, format) +} + +function prepareViteBuild( + options: ResolvedVitePWAOptions, + viteOptions: ResolvedConfig, +) { + const define: Record = { ...(viteOptions.define ?? {}) } + const nodeEnv = options.injectManifestBuildOptions.enableWorkboxModulesLogs + ? 'development' + // prevent tsup replacing `process.env` + // eslint-disable-next-line dot-notation + : (process['env']['NODE_ENV'] || 'production') as ('production' | 'development') + + define['process.env.NODE_ENV'] = JSON.stringify(nodeEnv) + + const buildPlugins = options.buildPlugins + const { format, plugins, rollupOptions } = options.injectManifestRollupOptions + + const inlineConfig = { + root: viteOptions.root, + base: viteOptions.base, + resolve: viteOptions.resolve, + mode: viteOptions.mode, + // don't copy anything from public folder + publicDir: false, + build: { + target: options.injectManifestBuildOptions.target, + minify: options.injectManifestBuildOptions.minify, + sourcemap: options.injectManifestBuildOptions.sourcemap, + outDir: options.outDir, + emptyOutDir: false, + }, + configFile: false, + define, + plugins: buildPlugins?.vite, + } satisfies InlineConfig + + const swName = basename(options.swDest) + const swMjsName = swName.replace(/\.js$/, '.mjs') + + if (format !== 'iife') { + if (viteOptions.build.sourcemap) { + Object.assign(inlineConfig, { + ...inlineConfig, + esbuild: { + sourcemap: viteOptions.build.sourcemap === 'hidden' ? true : viteOptions.build.sourcemap, + }, + } satisfies InlineConfig) + } + + Object.assign(inlineConfig.build, { + ...inlineConfig.build, + modulePreload: false, + rollupOptions: { + ...rollupOptions, + plugins: buildPlugins?.rollup ?? plugins, + input: options.swSrc, + output: { + entryFileNames: swMjsName, + inlineDynamicImports: true, + }, + }, + } satisfies InlineConfig['build']) + } + else { + Object.assign(inlineConfig.build, { + ...inlineConfig.build, + lib: { + entry: options.swSrc, + name: 'app', + formats: [format], + }, + rollupOptions: { + ...rollupOptions, + plugins: buildPlugins?.rollup ?? plugins, + output: { + entryFileNames: swName, + }, + }, + }) + } + + return { + inlineConfig, + swName, + swMjsName, + format, + } +}