Skip to content

Commit

Permalink
fix: add SPA support (#97)
Browse files Browse the repository at this point in the history
* fix: add SPA support

* chore: include json and js files in `prerendered/dependencies` folder

* chore: remove js files from `prerendered/dependencies` glob
  • Loading branch information
userquin authored Dec 31, 2024
1 parent 18702f4 commit 73b889b
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 4 deletions.
62 changes: 58 additions & 4 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { resolve } from 'node:path'
import type { ResolvedConfig } from 'vite'
import type { VitePWAOptions } from 'vite-plugin-pwa'
import type { ManifestTransform } from 'workbox-build'
import type { ManifestEntry, ManifestTransform } from 'workbox-build'
import type { KitOptions } from './types'

export function configureSvelteKitOptions(
Expand Down Expand Up @@ -76,6 +76,7 @@ export function configureSvelteKitOptions(
if (!config.manifestTransforms) {
config.manifestTransforms = [createManifestTransform(
base,
config.globDirectory,
options.strategies === 'injectManifest'
? undefined
: (options.manifestFilename ?? 'manifest.webmanifest'),
Expand All @@ -92,7 +93,12 @@ export function configureSvelteKitOptions(
}
}

function createManifestTransform(base: string, webManifestName?: string, options?: KitOptions): ManifestTransform {
function createManifestTransform(
base: string,
outDir: string,
webManifestName?: string,
options?: KitOptions,
): ManifestTransform {
return async (entries) => {
const defaultAdapterFallback = 'prerendered/fallback.html'
const suffix = options?.trailingSlash === 'always' ? '/' : ''
Expand All @@ -112,9 +118,12 @@ function createManifestTransform(base: string, webManifestName?: string, options
let url = e.url
// client assets in `.svelte-kit/output/client` folder.
// SSG pages in `.svelte-kit/output/prerendered/pages` folder.
// static adapter with load functions in `.svelte-kit/output/prerendered/dependencies/<page>/__data.json`.
// fallback page in `.svelte-kit/output/prerendered` folder (fallback.html is the default).
if (url.startsWith('client/'))
url = url.slice(7)
else if (url.startsWith('prerendered/dependencies/'))
url = url.slice(25)
else if (url.startsWith('prerendered/pages/'))
url = url.slice(18)
else if (url === defaultAdapterFallback)
Expand Down Expand Up @@ -149,6 +158,25 @@ function createManifestTransform(base: string, webManifestName?: string, options
return e
})

if (options?.spa && options?.adapterFallback) {
const name = typeof options.spa === 'object' && options.spa.fallbackMapping
? options.spa.fallbackMapping
: options.adapterFallback
if (typeof options.spa === 'object' && typeof options.spa.fallbackRevision === 'function') {
manifest.push({
url: name,
revision: await options.spa.fallbackRevision(),
size: 0,
})
}
else {
manifest.push(await buildManifestEntry(
name,
resolve(outDir, 'client/_app/version.json'),
))
}
}

if (!webManifestName)
return { manifest }

Expand All @@ -159,7 +187,7 @@ function createManifestTransform(base: string, webManifestName?: string, options
function buildGlobPatterns(globPatterns?: string[]) {
if (globPatterns) {
if (!globPatterns.some(g => g.startsWith('prerendered/')))
globPatterns.push('prerendered/**/*.html')
globPatterns.push('prerendered/**/*.{html,json}')

if (!globPatterns.some(g => g.startsWith('client/')))
globPatterns.push('client/**/*.{js,css,ico,png,svg,webp,webmanifest}')
Expand All @@ -170,7 +198,7 @@ function buildGlobPatterns(globPatterns?: string[]) {
return globPatterns
}

return ['client/**/*.{js,css,ico,png,svg,webp,webmanifest}', 'prerendered/**/*.html']
return ['client/**/*.{js,css,ico,png,svg,webp,webmanifest}', 'prerendered/**/*.{html,json}']
}

function buildGlobIgnores(globIgnores?: string[]) {
Expand All @@ -183,3 +211,29 @@ function buildGlobIgnores(globIgnores?: string[]) {

return ['server/**']
}

async function buildManifestEntry(url: string, path: string): Promise<ManifestEntry & { size: number }> {
const [crypto, createReadStream] = await Promise.all([
import('node:crypto').then(m => m.default),
import('node:fs').then(m => m.createReadStream),
])

return new Promise((resolve, reject) => {
const cHash = crypto.createHash('MD5')
const stream = createReadStream(path)
stream.on('error', (err) => {
reject(err)
})
stream.on('data', (chunk) => {
// @ts-expect-error TS2345: Argument of type string | Buffer is not assignable to parameter of type BinaryLike
cHash.update(chunk)
})
stream.on('end', () => {
return resolve({
url,
size: 0,
revision: `${cHash.digest('hex')}`,
})
})
})
}
26 changes: 26 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,32 @@ export interface KitOptions {
* @default false
*/
includeVersionFile?: boolean

/**
* Enable SPA mode for the application.
*
* By default, the plugin will use `adapterFallback` to include the entry in the service worker
* precache manifest.
*
* If you are using a logical name for the fallback, you can use the object syntax with the
* `fallbackMapping`.
*
* For example, if you're using `fallback: 'app.html'` in your static adapter and your server
* is redirecting to `/app`, you can configure `fallbackMapping: '/app'`.
*
* Since the static adapter will run after the PWA plugin generates the service worker,
* the PWA plugin doesn't have access to the adapter fallback page to include the revision in the
* service worker precache manifest.
* To generate the revision for the fallback page, the PWA plugin will use the
* `.svelte-kit/output/client/_app/version.json` file.
* You can configure the `fallbackRevision` to generate a custom revision.
*
* @see https://svelte.dev/docs/kit/single-page-apps
*/
spa?: true | {
fallbackMapping?: string
fallbackRevision?: () => Promise<string>
}
}

export interface SvelteKitPWAOptions extends Partial<VitePWAOptions> {
Expand Down

0 comments on commit 73b889b

Please sign in to comment.