Skip to content

Commit

Permalink
feat: add injectManifest strategies (#15)
Browse files Browse the repository at this point in the history
* feat: add injectManifest strategies

* chore: simplify code

Co-authored-by: Anthony Fu <[email protected]>

Co-authored-by: Anthony Fu <[email protected]>
  • Loading branch information
hannoeru and antfu authored Jan 16, 2021
1 parent a15d54e commit e8e0077
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 56 deletions.
3 changes: 2 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"build": "cross-env DEBUG=vite-plugin-pwa:* vite build"
},
"dependencies": {
"vue": "^3.0.5"
"vue": "^3.0.5",
"workbox-precaching": "^6.0.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^1.0.5",
Expand Down
3 changes: 3 additions & 0 deletions example/public/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { precacheAndRoute } from 'workbox-precaching'

precacheAndRoute(self.__WB_MANIFEST)
10 changes: 6 additions & 4 deletions example/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import Vue from '@vitejs/plugin-vue'
import { VitePWA } from 'vite-plugin-pwa'

const config: UserConfig = {
build: {
base: 'test'
},
// build: {
// base: 'test',
// },
plugins: [
Vue(),
VitePWA(),
VitePWA({
strategies: 'injectManifest',
}),
],
}

Expand Down
2 changes: 2 additions & 0 deletions pnpm-lock.yaml

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

64 changes: 64 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import fs from 'fs'
import { resolve } from 'path'
import { ResolvedConfig } from 'vite'
import { GenerateSWConfig, InjectManifestConfig } from 'workbox-build'
import { ManifestOptions, VitePWAOptions, ResolvedVitePWAOptions } from './types'
import { cachePreset } from './cache'

export function resolveOptions(options: Partial<VitePWAOptions>, viteConfig: ResolvedConfig): ResolvedVitePWAOptions {
const root = viteConfig.root
const pkg = fs.existsSync('package.json')
? JSON.parse(fs.readFileSync('package.json', 'utf-8'))
: {}
const {
srcDir = 'public',
outDir = viteConfig.build.outDir || 'dist',
filename = 'sw.js',
strategies = 'generateSW',
} = options

const swSrc = resolve(root, srcDir, filename)
const swDest = resolve(root, outDir, filename)
const outDirRoot = resolve(root, outDir)

const defaultWorkbox: GenerateSWConfig = {
swDest,
globDirectory: outDirRoot,
offlineGoogleAnalytics: false,
runtimeCaching: cachePreset,
// prevent tsup replacing `process.env`
// eslint-disable-next-line dot-notation
mode: process['env']['NODE_ENV'] || 'production',
}

const defaultInjectManifest: InjectManifestConfig = {
swSrc,
swDest,
globDirectory: outDirRoot,
injectionPoint: 'self.__WB_MANIFEST',
}

const defaultManifest: Partial<ManifestOptions> = {
name: pkg.name,
short_name: pkg.name,
start_url: '/',
display: 'standalone',
background_color: '#ffffff',
lang: 'en',
}

const workbox = Object.assign({}, defaultWorkbox, options.workbox || {})
const manifest = Object.assign({}, defaultManifest, options.manifest || {})
const injectManifest = Object.assign({}, defaultInjectManifest, options.injectManifest || {})

return {
swDest,
srcDir,
outDir,
filename,
strategies,
workbox,
manifest,
injectManifest,
}
}
19 changes: 19 additions & 0 deletions src/html.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { join } from 'path'
import { ResolvedVitePWAOptions } from './types'

export function injectServiceWorker(html: string, base: string, options: ResolvedVitePWAOptions) {
const basePath = base.startsWith('/') ? `/${base}` : base
return html.replace(
'</head>',
`
<link rel="manifest" href="${join(basePath, 'manifest.webmanifest')}">
<script>
if('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('${join(basePath, options.filename)}', { scope: './' })
})
}
</script>
</head>`,
)
}
64 changes: 14 additions & 50 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,43 @@
import fs from 'fs'
import { resolve } from 'path'
import type { Plugin, ResolvedConfig } from 'vite'
import { generateSW, GenerateSWConfig } from 'workbox-build'
import { cachePreset } from './cache'
import { ManifestOptions, VitePWAOptions } from './types'
import { generateSW, injectManifest } from 'workbox-build'
import { injectServiceWorker } from './html'
import { ResolvedVitePWAOptions, VitePWAOptions } from './types'
import { resolveOptions } from './config'

export function VitePWA(options: Partial<VitePWAOptions> = {}): Plugin {
let viteConfig: ResolvedConfig | undefined
let workbox: GenerateSWConfig | undefined
let manifest: Partial<ManifestOptions> = {}
let outDir = 'dist'
let resolvedOptions: ResolvedVitePWAOptions | undefined

return {
name: 'vite-plugin-pwa',
enforce: 'post',
apply: 'build',
configResolved(config) {
viteConfig = config
const root = viteConfig.root
const pkg = fs.existsSync('package.json')
? JSON.parse(fs.readFileSync('package.json', 'utf-8'))
: {}
outDir = options.outDir || config.build.outDir || 'dist'

const defaultWorkbox: GenerateSWConfig = {
swDest: resolve(root, `${outDir}/sw.js`),
globDirectory: resolve(root, outDir),
offlineGoogleAnalytics: false,
runtimeCaching: cachePreset,
// prevent tsup replacing `process.env`
// eslint-disable-next-line dot-notation
mode: process['env']['NODE_ENV'] || 'production',
}

const defaultManifest: Partial<ManifestOptions> = {
name: pkg.name,
short_name: pkg.name,
start_url: '/',
display: 'standalone',
background_color: '#ffffff',
lang: 'en',
}

workbox = Object.assign({}, defaultWorkbox, options.workbox || {})
manifest = Object.assign({}, defaultManifest, options.manifest || {})
resolvedOptions = resolveOptions(options, viteConfig)
},
transformIndexHtml: {
enforce: 'post',
transform(html) {
return html.replace(
'</head>',
`
<link rel="manifest" href="${viteConfig!.build.base}manifest.webmanifest">
<script>
if('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('${viteConfig!.build.base}sw.js', { scope: './' })
})
}
</script>
</head>`,
)
const base = viteConfig!.build.base
return injectServiceWorker(html, base, resolvedOptions!)
},
},
generateBundle(_, bundle) {
bundle['manifest.webmanifest'] = {
isAsset: true,
type: 'asset',
name: undefined,
source: `${JSON.stringify(manifest, null, 2)}\n`,
source: `${JSON.stringify(resolvedOptions!.manifest, null, 2)}\n`,
fileName: 'manifest.webmanifest',
}
},
buildEnd() {
generateSW(workbox!)
const strategies = resolvedOptions!.strategies
if (strategies === 'generateSW')
generateSW(resolvedOptions!.workbox)
if (strategies === 'injectManifest')
injectManifest(resolvedOptions!.injectManifest)
},
}
}
Expand Down
25 changes: 24 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GenerateSWConfig } from 'workbox-build'
import { GenerateSWConfig, InjectManifestConfig } from 'workbox-build'

export interface ManifestOptions {
/**
Expand Down Expand Up @@ -59,7 +59,30 @@ export interface ManifestOptions {
* Plugin options.
*/
export interface VitePWAOptions {
/**
* Default: 'public'
*/
srcDir?: string
/**
* Default: 'dist'
*/
outDir?: string
/**
* Default: 'sw.js'
*/
filename?: string
/**
* Default: 'generateSW'
*/
strategies?: 'generateSW' | 'injectManifest'
manifest: Partial<ManifestOptions>
workbox: Partial<GenerateSWConfig>
injectManifest: Partial<InjectManifestConfig>
}

export interface ResolvedVitePWAOptions extends Required<VitePWAOptions> {
swDest: string
manifest: ManifestOptions
workbox: GenerateSWConfig
injectManifest: InjectManifestConfig
}

0 comments on commit e8e0077

Please sign in to comment.