Skip to content

Commit

Permalink
feat!: new custom service worker build (#629)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
userquin committed Feb 10, 2024
1 parent 5c598d8 commit 865fdad
Show file tree
Hide file tree
Showing 18 changed files with 359 additions and 71 deletions.
4 changes: 4 additions & 0 deletions examples/preact-router/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ if (process.env.SW === 'true') {
pwaOptions.strategies = 'injectManifest'
;(pwaOptions.manifest as Partial<ManifestOptions>).name = 'PWA Inject Manifest'
;(pwaOptions.manifest as Partial<ManifestOptions>).short_name = 'PWA Inject'
pwaOptions.injectManifest = {
minify: false,
enableWorkboxModulesLogs: true,
}
}

if (claims)
Expand Down
4 changes: 4 additions & 0 deletions examples/react-router/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ if (process.env.SW === 'true') {
pwaOptions.strategies = 'injectManifest'
;(pwaOptions.manifest as Partial<ManifestOptions>).name = 'PWA Inject Manifest'
;(pwaOptions.manifest as Partial<ManifestOptions>).short_name = 'PWA Inject'
pwaOptions.injectManifest = {
minify: false,
enableWorkboxModulesLogs: true,
}
}

if (claims)
Expand Down
4 changes: 4 additions & 0 deletions examples/solid-router/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ if (process.env.SW === 'true') {
pwaOptions.strategies = 'injectManifest'
;(pwaOptions.manifest as Partial<ManifestOptions>).name = 'PWA Inject Manifest'
;(pwaOptions.manifest as Partial<ManifestOptions>).short_name = 'PWA Inject'
pwaOptions.injectManifest = {
minify: false,
enableWorkboxModulesLogs: true,
}
}

if (claims)
Expand Down
4 changes: 4 additions & 0 deletions examples/svelte-routify/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion examples/vanilla-ts-no-ip/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions examples/vanilla-ts-no-ip/src/custom-sw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions examples/vanilla-ts-no-ip/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()}'`,
Expand Down Expand Up @@ -51,6 +52,8 @@ export default defineConfig({
skipWaiting: true,
},
injectManifest: {
minify: false,
enableWorkboxModulesLogs: true,
injectionPoint: undefined,
},
devOptions: {
Expand Down
3 changes: 3 additions & 0 deletions examples/vue-router/src/claims-sw.ts
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
3 changes: 3 additions & 0 deletions examples/vue-router/src/prompt-sw.ts
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
5 changes: 5 additions & 0 deletions examples/vue-router/src/shims-vue.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ declare module '*.vue' {
const Component: ReturnType<typeof defineComponent>
export default Component
}

declare module 'virtual:message' {
const message: string
export { message }
}
5 changes: 4 additions & 1 deletion examples/vue-router/test/build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
24 changes: 24 additions & 0 deletions examples/vue-router/vite.config.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -44,12 +45,34 @@ const pwaOptions: Partial<VitePWAOptions> = {
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<ManifestOptions>).name = 'PWA Inject Manifest'
;(pwaOptions.manifest as Partial<ManifestOptions>).short_name = 'PWA Inject'
pwaOptions.injectManifest = {
minify: false,
enableWorkboxModulesLogs: true,
buildPlugins: {
vite: [virtualMessagePlugin()],
},
}
}

if (claims)
Expand All @@ -65,6 +88,7 @@ export default defineConfig({
},
plugins: [
Vue(),
virtualMessagePlugin(),
VitePWA(pwaOptions),
replace({
__DATE__: new Date().toISOString(),
Expand Down
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 38 additions & 6 deletions src/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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
Expand Down
64 changes: 5 additions & 59 deletions src/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any> = { ...(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)
}
14 changes: 13 additions & 1 deletion src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,14 @@ export async function resolveOptions(options: Partial<VitePWAOptions>, 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)
Expand Down Expand Up @@ -191,13 +196,20 @@ export async function resolveOptions(options: Partial<VitePWAOptions>, viteConfi
devOptions,
rollupFormat,
vitePlugins,
buildPlugins,
selfDestroying,
buildBase: buildBase ?? basePath,
injectManifestRollupOptions: {
plugins,
rollupOptions,
format: rollupFormat,
},
injectManifestBuildOptions: {
target,
minify: minifySW,
sourcemap,
enableWorkboxModulesLogs,
},
}

// calculate hash only when required
Expand Down

0 comments on commit 865fdad

Please sign in to comment.