返回 slidev
pwa.ts
根目录 / packages / slidev / node / vite / pwa.ts
1 import type { ResolvedSlidevOptions } from '@slidev/types'
2 import type { Plugin, PluginOption } from 'vite'
3 import { importOptionalDependency, promptForOptionalInstallation } from '../resolver'
4
5 type VitePWAFn = (options: Record<string, any>) => PluginOption
6
7 const PWA_PACKAGE = 'vite-plugin-pwa'
8
9 export async function createPWAPlugin(options: ResolvedSlidevOptions): Promise<PluginOption> {
10 const matchMode = (mode: string | boolean) => mode === true || mode === options.mode
11 const enabled = matchMode(options.data.config.pwa)
12
13 // PWA off (the default): `vite-plugin-pwa` is an optional peer dependency, so
14 // don't require it at all. A tiny stub keeps the client's guarded
15 // `import('virtual:pwa-register')` resolvable so no PWA install is needed.
16 if (!enabled)
17 return createPWARegisterStubPlugin()
18
19 const VitePWA = await resolveVitePWA()
20
21 const base = options.base ?? '/'
22 const title = options.data.config.title || 'Slidev'
23
24 return VitePWA({
25 registerType: 'autoUpdate',
26 injectRegister: null,
27 base,
28 workbox: {
29 maximumFileSizeToCacheInBytes: 100 * 1024 * 1024,
30 globPatterns: [
31 '**/*.{js,css,html,woff,woff2,ttf,png,jpg,jpeg,svg,gif,webp,avif,ico,mp4,webm,ogv,mov,m4v,mp3,wav,ogg,oga,m4a,aac,flac,opus,weba}',
32 ],
33 navigateFallback: 'index.html',
34 navigateFallbackDenylist: [/^\/(presenter|notes|overview|print|export)/],
35 },
36 // No icons: a PWA feature must not depend on a remote/CDN asset, and the
37 // deck's own favicon may be a URL. Manifest stays valid without icons.
38 manifest: {
39 name: title,
40 short_name: 'Slidev',
41 start_url: base,
42 display: 'fullscreen',
43 theme_color: '#121212',
44 background_color: '#121212',
45 },
46 })
47 }
48
49 /**
50 * Resolve `vite-plugin-pwa`'s `VitePWA` factory, prompting the user to install
51 * the optional peer dependency when the `pwa` option is enabled but the package
52 * is missing.
53 */
54 async function resolveVitePWA(): Promise<VitePWAFn> {
55 let mod = await importOptionalDependency<{ VitePWA?: VitePWAFn, default?: { VitePWA?: VitePWAFn } }>(PWA_PACKAGE)
56
57 if (!mod?.VitePWA && !mod?.default?.VitePWA) {
58 await promptForOptionalInstallation(PWA_PACKAGE, 'The "pwa" option')
59 mod = await importOptionalDependency(PWA_PACKAGE)
60 }
61
62 const VitePWA = mod?.VitePWA ?? mod?.default?.VitePWA
63 if (!VitePWA)
64 throw new Error(`[slidev] Failed to load "${PWA_PACKAGE}", which is required by the "pwa" option.`)
65
66 return VitePWA
67 }
68
69 /**
70 * When PWA is disabled, resolve `virtual:pwa-register` to a no-op module. The
71 * client only imports it behind the `__SLIDEV_FEATURE_PWA__` guard (stripped
72 * from the build when off), but the guarded dynamic import must still resolve
73 * during dev — without pulling in the optional `vite-plugin-pwa` dependency.
74 */
75 function createPWARegisterStubPlugin(): Plugin {
76 const VIRTUAL_ID = 'virtual:pwa-register'
77 const RESOLVED_ID = `\0${VIRTUAL_ID}`
78 return {
79 name: 'slidev:pwa-register-stub',
80 resolveId(id) {
81 return id === VIRTUAL_ID ? RESOLVED_ID : undefined
82 },
83 load(id) {
84 if (id === RESOLVED_ID)
85 return 'export function registerSW() { return () => Promise.resolve() }'
86 },
87 }
88 }
89
89 lines TYPESCRIPT