Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use ManifestTransform when using @islands/pwa #144

Merged
merged 4 commits into from
Jul 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions docs/iles.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ export default defineConfig({
],
},
workbox: {
// exclude html files here: the pwa module will calculate their hash and add them to the sw precache
globPatterns: ['**/*.{js,css,svg,ico,png,avif,json,xml}'],
globPatterns: ['**/*.{js,css,svg,ico,png,avif,json,xml,html}'],
runtimeCaching: [
{
urlPattern: new RegExp('https://unpkg.com/.*', 'i'),
Expand Down
16 changes: 1 addition & 15 deletions docs/src/pages/guide/pwa.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,5 @@ registerSW({ immediate: true })
## Specifying the Vite plugin manually

When specifying <kbd>[vite-plugin-pwa]</kbd> manually, <kbd>[@islands/pwa]</kbd>
will detect it and use it:
will detect it and will throw an error, you must configure it using <kbd>[@islands/pwa]</kbd>.

```ts
import { defineConfig } from 'iles'
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
modules: [
'@islands/pwa',
],
vite: {
plugins: [VitePWA(options)],
},
})
```
<Caption>`iles.config.ts`</Caption>
2 changes: 1 addition & 1 deletion packages/pwa/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"homepage": "https://github.com/ElMassimo/iles",
"bugs": "https://github.com/ElMassimo/iles/issues",
"dependencies": {
"vite-plugin-pwa": "^0.12.0"
"vite-plugin-pwa": "^0.12.2"
},
"devDependencies": {
"iles": "workspace:*"
Expand Down
145 changes: 86 additions & 59 deletions packages/pwa/src/pwa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,85 @@ import fs from 'fs'
import crypto from 'crypto'
import { resolve } from 'path'
import { performance } from 'perf_hooks'
import type { IlesModule, UserConfig } from 'iles'
import type { IlesModule, RouteToRender, UserConfig } from 'iles'
import type { VitePluginPWAAPI, VitePWAOptions } from 'vite-plugin-pwa'
import type { ManifestEntry } from 'workbox-build'
import type { ManifestEntry, ManifestTransform } from 'workbox-build'
import { VitePWA } from 'vite-plugin-pwa'

interface ManifestTransformData {
outDir: string
pages: RouteToRender[]
}

type EnableManifestTransform = () => ManifestTransformData

function timeSince (start: number): string {
const diff = performance.now() - start
return diff < 750 ? `${Math.round(diff)}ms` : `${(diff / 1000).toFixed(1)}s`
}

function configureDefaults (config: UserConfig, options: Partial<VitePWAOptions> = {}): Partial<VitePWAOptions> {
function buildManifestEntry (
url: string,
path: string,
): Promise<ManifestEntry> {
return new Promise((resolve, reject) => {
const cHash = crypto.createHash('MD5')
const stream = fs.createReadStream(path)
stream.on('error', (err) => {
reject(err)
})
stream.on('data', (chunk) => {
cHash.update(chunk)
})
stream.on('end', () => {
return resolve({
url,
revision: `${cHash.digest('hex')}`,
})
})
})
}

async function buildManifestEntryTransform (
ssgUrl: string,
path: string,
): Promise<ManifestEntry & { size: number }> {
const [size, { url, revision }] = await Promise.all([
new Promise<number>((resolve, reject) => {
fs.lstat(path, (err, stats) => {
if (err)
reject(err)
else
resolve(stats.size)
})
}),
buildManifestEntry(ssgUrl, path),
])
return { url, revision, size }
}

function createManifestTransform (enableManifestTransform: EnableManifestTransform): ManifestTransform {
return async (entries) => {
const data = enableManifestTransform()
if (data) {
const { outDir, pages } = data
const manifest = entries.filter(e => !e.url.endsWith('.html'))
const addRoutes = await Promise.all(pages.map((r) => {
return buildManifestEntryTransform(r.path, resolve(outDir, r.outputFilename))
}))
manifest.push(...addRoutes)
return { manifest }
}

return { manifest: entries }
}
}

function configureDefaults (
config: UserConfig,
enableManifestTransform: EnableManifestTransform,
options: Partial<VitePWAOptions> = {},
): Partial<VitePWAOptions> {
const {
strategies = 'generateSW',
registerType = 'prompt',
Expand All @@ -30,63 +98,25 @@ function configureDefaults (config: UserConfig, options: Partial<VitePWAOptions>
registerType,
injectRegister,
}
// we use route names: use Vite base or its default
if (!useWorkbox.navigateFallback || useWorkbox.navigateFallback.endsWith('.html'))
const prettyUrls = config.prettyUrls ?? true
if (!useWorkbox.navigateFallback && prettyUrls)
useWorkbox.navigateFallback = config.vite?.base ?? '/'

if (registerType === 'autoUpdate') {
if (useWorkbox.clientsClaim === undefined)
useWorkbox.clientsClaim = true

if (useWorkbox.skipWaiting === undefined)
useWorkbox.skipWaiting = true
}

// we don't need registerSW.js if not configured
if (injectRegister === undefined)
newOptions.injectRegister = null

newOptions.workbox = useWorkbox

newOptions.workbox.manifestTransforms = newOptions.workbox.manifestTransforms ?? []
newOptions.workbox.manifestTransforms.push(createManifestTransform(enableManifestTransform))

return newOptions
}

// we don't need registerSW.js if not configured
if (injectRegister === undefined) {
return {
...rest,
strategies,
registerType,
injectManifest,
injectRegister: null,
}
}
options.injectManifest = options.injectManifest ?? {}
options.injectManifest.manifestTransforms = injectManifest.manifestTransforms ?? []
options.injectManifest.manifestTransforms.push(createManifestTransform(enableManifestTransform))

return options
}

function buildManifestEntry (
url: string,
path: string,
): Promise<ManifestEntry> {
return new Promise((resolve, reject) => {
const cHash = crypto.createHash('MD5')
const stream = fs.createReadStream(path)
stream.on('error', (err) => {
reject(err)
})
stream.on('data', (chunk) => {
cHash.update(chunk)
})
stream.on('end', () => {
return resolve({
url,
revision: `${cHash.digest('hex')}`,
})
})
})
}

/**
* An iles module that configures vite-plugin-pwa and
* regenerates the PWA when SSG is finished.
Expand All @@ -95,15 +125,19 @@ function buildManifestEntry (
*/
export default function IlesPWA (options: Partial<VitePWAOptions> = {}): IlesModule {
let api: VitePluginPWAAPI | undefined
let data: ManifestTransformData | undefined
const enableManifestTransform: EnableManifestTransform = () => {
return data!
}
return {
name: '@islands/pwa',
config (config) {
const plugin = config.vite?.plugins?.flat(Infinity).find(p => p.name === 'vite-plugin-pwa')
if (plugin) {
api = plugin.api
throw new Error('Remove the vite-plugin-pwa plugin from Vite plugins entry in iles config file, configure it via @islands/pwa plugin')
}
else {
const pluginPWA = VitePWA(configureDefaults(config, options))
const pluginPWA = VitePWA(configureDefaults(config, enableManifestTransform, options))
api = pluginPWA.find(p => p.name === 'vite-plugin-pwa')?.api
return {
vite: {
Expand All @@ -117,14 +151,7 @@ export default function IlesPWA (options: Partial<VitePWAOptions> = {}): IlesMod
if (api && !api.disabled) {
console.info('Regenerating PWA service worker...')
const startTime = performance.now()
const addRoutes = await Promise.all(pages.map((r) => {
return buildManifestEntry(r.path, resolve(outDir, r.outputFilename))
}))
api.extendManifestEntries((manifestEntries) => {
// just add the routes: the returned value will override existing entry
manifestEntries.push(...addRoutes)
return undefined
})
data = { outDir, pages }
// generate the manifest.webmanifest file
api.generateBundle()
// regenerate the sw
Expand Down
Loading