Skip to content

Commit

Permalink
fix: try to make webmanifest work
Browse files Browse the repository at this point in the history
  • Loading branch information
Clovis committed Jan 13, 2025
1 parent 708a504 commit 4f10d75
Show file tree
Hide file tree
Showing 4 changed files with 17 additions and 242 deletions.
3 changes: 1 addition & 2 deletions elk/composables/setups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { Directions, LocaleObject } from '@nuxtjs/i18n'

export function setupPageHeader() {
const { locale, locales, t } = useI18n()
const colorMode = useColorMode()
const buildInfo = useBuildInfo()
const enablePinchToZoom = usePreferences('enablePinchToZoom')

Expand Down Expand Up @@ -55,7 +54,7 @@ export function setupPageHeader() {
? () => [{
key: 'webmanifest',
rel: 'manifest',
href: `/manifest-${locale.value}${colorMode.value === 'dark' ? '-dark' : ''}.webmanifest`,
href: `/manifest.webmanifest`,
}]
: [],
})
Expand Down
190 changes: 6 additions & 184 deletions elk/modules/pwa/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { readFile } from 'node:fs/promises'
import { createResolver } from '@nuxt/kit'
import { getEnv } from '../../config/env'
import { currentLocales } from '../../config/i18n'
import { THEME_COLORS } from '../../constants/index'

export type LocalizedWebManifest = Record<string, Partial<ManifestOptions>>

Expand All @@ -13,9 +12,8 @@ export const pwaLocales = currentLocales
type WebManifestEntry = Pick<ManifestOptions, 'name' | 'short_name' | 'description' | 'screenshots' | 'shortcuts'>
type RequiredWebManifestEntry = Required<WebManifestEntry & Pick<ManifestOptions, 'dir' | 'lang' | 'screenshots' | 'shortcuts'>>

export async function createI18n(): Promise<LocalizedWebManifest> {
export async function createI18n(): Promise<RequiredWebManifestEntry> {
const { env } = await getEnv()
const envName = `${env === 'release' ? '' : `(${env})`}`
const { action, nav, pwa } = await readI18nFile('en.json')

const defaultManifest: Required<WebManifestEntry> = pwa.webmanifest[env]
Expand Down Expand Up @@ -142,110 +140,14 @@ export async function createI18n(): Promise<LocalizedWebManifest> {
}]
}

const locales: RequiredWebManifestEntry[] = await Promise.all(
pwaLocales
.filter(l => l.code !== 'en-US')
.map(async ({ code, dir = 'ltr', file, files }) => {
// read locale file or files
const { action, app_desc_short, app_name, nav, pwa } = file
? await readI18nFile(file as string)
: await findBestWebManifestData(files as string[], env)
const entry = pwa?.webmanifest?.[env] ?? {}

if (!entry.name && app_name)
entry.name = dir === 'rtl' ? `${envName} ${app_name}` : `${app_name} ${envName}`

if (!entry.short_name && app_name)
entry.short_name = dir === 'rtl' ? `${envName} ${app_name}` : `${app_name} ${envName}`

if (!entry.description && app_desc_short)
entry.description = app_desc_short

// clone default screenshots and shortcuts
const useScreenshots = [...defaultScreenshots.map(screenshot => ({ ...screenshot }))]
const useShortcuts = [...defaultShortcuts.map(shortcut => ({ ...shortcut }))]

const pwaScreenshots = pwa?.screenshots
if (pwaScreenshots) {
useScreenshots.forEach((screenshot, idx) => {
if (idx === 0 && pwaScreenshots?.dark)
screenshot.label = pwaScreenshots.dark

if (idx === 1 && pwaScreenshots?.light)
screenshot.label = pwaScreenshots.light
})
}

useShortcuts.forEach((shortcut, idx) => {
if (idx === 0 && nav?.home)
shortcut.name = nav.home

if (idx === 1 && nav?.local)
shortcut.name = nav.local

if (idx === 2 && nav?.notifications)
shortcut.name = nav.notifications

if (idx === 3 && action?.compose)
shortcut.name = action?.compose

if (idx === 4 && nav?.settings)
shortcut.name = nav.settings
})

return <RequiredWebManifestEntry>{
...defaultManifest,
...entry,
lang: code,
dir,
screenshots: useScreenshots,
shortcuts: useShortcuts,
}
}),
)
locales.push({
return {
...defaultManifest,
lang: 'en-US',
dir: 'ltr',
screenshots: defaultScreenshots,
lang: 'en_US',
...manifestEntries,
shortcuts: defaultShortcuts,
})
return locales.reduce((acc, {
lang,
dir,
name,
short_name,
description,
shortcuts,
screenshots,
}) => {
acc[lang] = {
lang,
name,
short_name,
description,
dir,
background_color: THEME_COLORS.backgroundLight,
theme_color: THEME_COLORS.themeLight,
...manifestEntries,
shortcuts,
screenshots,
}
acc[`${lang}-dark`] = {
lang,
name,
short_name,
description,
dir,
background_color: THEME_COLORS.backgroundDark,
theme_color: THEME_COLORS.themeDark,
...manifestEntries,
shortcuts,
screenshots,
}

return acc
}, {} as LocalizedWebManifest)
screenshots: defaultScreenshots,
}
}

async function readI18nFile(file: string) {
Expand All @@ -254,83 +156,3 @@ async function readI18nFile(file: string) {
await readFile(resolve(`../../locales/${file}`), 'utf-8'),
).toString())
}

interface PWAEntry {
webmanifest?: Record<string, {
name?: string
short_name?: string
description?: string
}>
screenshots?: Record<string, string>
shortcuts?: Record<string, string>
}

interface JsonEntry {
pwa?: PWAEntry
app_name?: string
app_desc_short?: string
action?: Record<string, any>
nav?: Record<string, any>
screenshots?: Record<string, string>
}

async function findBestWebManifestData(files: string[], env: string) {
const entries: JsonEntry[] = await Promise.all(files.map(async (file) => {
const { action, app_name, app_desc_short, nav, pwa } = await readI18nFile(file)
return { action, app_name, app_desc_short, nav, pwa }
}))

let pwa: PWAEntry | undefined
let app_name: string | undefined
let app_desc_short: string | undefined
const action: Record<string, any> = {}
const nav: Record<string, any> = {}

for (const entry of entries) {
const webmanifest = entry?.pwa?.webmanifest?.[env]
if (webmanifest) {
if (pwa) {
if (webmanifest.name)
pwa.webmanifest![env].name = webmanifest.name

if (webmanifest.short_name)
pwa.webmanifest![env].short_name = webmanifest.short_name

if (webmanifest.description)
pwa.webmanifest![env].description = webmanifest.description
}
else {
pwa = entry.pwa
}
}

if (entry.app_name)
app_name = entry.app_name

if (entry.app_desc_short)
app_desc_short = entry.app_desc_short

if (entry.nav) {
['home', 'local', 'notifications', 'settings'].forEach((key) => {
const value = entry.nav![key]
if (value)
nav[key] = value
})
}

if (entry.action?.compose)
action.compose = entry.action.compose

if (entry.pwa?.screenshots) {
if (!pwa)
pwa = {}

pwa.screenshots = pwa.screenshots ?? {}
Object
.entries(entry.pwa.screenshots)
.forEach(([key, value]) => pwa!.screenshots![key] = value)
}
}

return { action, app_desc_short, app_name, nav, pwa }
}
65 changes: 9 additions & 56 deletions elk/modules/pwa/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,6 @@ import { createI18n, type LocalizedWebManifest, pwaLocales } from './i18n'

export * from './types'

interface PwaDevIcon {
src: string
type: string
}

interface ResolvedPwaDevIcon extends PwaDevIcon {
data: Promise<Buffer>
}

export default defineNuxtModule<VitePWANuxtOptions>({
meta: {
name: 'elk-pwa',
Expand All @@ -38,7 +29,6 @@ export default defineNuxtModule<VitePWANuxtOptions>({
const resolveVitePluginPWAAPI = (): VitePluginPWAAPI | undefined => {
return vitePwaClientPlugin?.api
}
let webmanifests: LocalizedWebManifest | undefined

nuxt.options.appConfig = nuxt.options.appConfig || {}
nuxt.options.appConfig.pwaEnabled = !options.disable
Expand Down Expand Up @@ -80,13 +70,11 @@ export default defineNuxtModule<VitePWANuxtOptions>({
if (plugin)
throw new Error('Remove vite-plugin-pwa plugin from Vite Plugins entry in Nuxt config file!')

webmanifests = await createI18n()
const generateManifest = (entry: string) => {
const manifest = webmanifests![entry]
if (!manifest)
throw new Error(`No webmanifest found for locale/theme ${entry}`)
return JSON.stringify(manifest)
const webmanifest = await createI18n()
const generateManifest = () => {
return JSON.stringify(webmanifest)
}

if (isClient) {
viteInlineConfig.plugins.push({
name: 'elk:pwa:locales:build',
Expand All @@ -95,40 +83,22 @@ export default defineNuxtModule<VitePWANuxtOptions>({
if (options.disable || !bundle)
return
await mkdir(manifestDir, { recursive: true })
for (const wm in webmanifests)
await writeFile(join(manifestDir, `manifest-${wm}.webmanifest`), generateManifest(wm))
await writeFile(join(manifestDir, `manifest.webmanifest`), generateManifest())
},
})
}
viteInlineConfig.plugins.push({
name: 'elk:pwa:dev',
apply: 'serve',
configureServer(server) {
const icons: PwaDevIcon[] = webmanifests?.['en-US']?.icons as any
const mappedIcons: PwaDevIcon[] = [
...icons.map(({ src, type }) => ({ src, type })),
{ src: 'favicon.ico', type: 'image/x-icon' },
{ src: 'apple-touch-icon.png', type: 'image/png' },
{ src: 'logo.svg', type: 'image/svg+xml' },
]

const folder = dirname(fileURLToPath(import.meta.url))
const useIcons = mappedIcons.reduce((acc, icon) => {
icon.src = `${nuxt.options.app.baseURL}${icon.src}`
acc[icon.src] = {
...icon,
data: readFile(resolve(join(folder, '../../public-dev', icon.src))),
}
return acc
}, <Record<string, ResolvedPwaDevIcon>>{})
const localeMatcher = new RegExp(`^${nuxt.options.app.baseURL}manifest-(.*).webmanifest$`)
const localeMatcher = new RegExp(`^${nuxt.options.app.baseURL}manifest.webmanifest$`)
server.middlewares.use(async (req, res, next) => {
const url = req.url
if (!url)
return next()

const match = url.match(localeMatcher)
const entry = match && webmanifests![match[1]]
const entry = match && webmanifest!
if (entry) {
res.statusCode = 200
res.setHeader('Content-Type', 'application/manifest+json')
Expand All @@ -137,15 +107,6 @@ export default defineNuxtModule<VitePWANuxtOptions>({
res.end()
return
}

const icon = useIcons[url]
if (!icon)
return next()

res.statusCode = 200
res.setHeader('Content-Type', icon.type)
res.setHeader('Cache-Control', 'public, max-age=0, must-revalidate')
res.write(await icon.data)
res.end()
})
},
Expand All @@ -159,7 +120,7 @@ export default defineNuxtModule<VitePWANuxtOptions>({
})

if (nuxt.options.dev) {
const webManifest = `${nuxt.options.app.baseURL}${options.devOptions?.webManifestUrl ?? options.manifestFilename ?? 'manifest.webmanifest'}`
const webManifesturl = `${nuxt.options.app.baseURL}${options.devOptions?.webManifestUrl ?? options.manifestFilename ?? 'manifest.webmanifest'}`
const devSw = `${nuxt.options.app.baseURL}dev-sw.js?dev-sw`
const workbox = `${nuxt.options.app.baseURL}workbox-`
// @ts-expect-error just ignore
Expand All @@ -170,15 +131,7 @@ export default defineNuxtModule<VitePWANuxtOptions>({
if (isServer)
return

viteServer.middlewares.stack.push({ route: webManifest, handle: emptyHandle })
if (webmanifests) {
Object.keys(webmanifests).forEach((wm) => {
viteServer.middlewares.stack.push({
route: `${nuxt.options.app.baseURL}manifest-${wm}.webmanifest`,
handle: emptyHandle,
})
})
}
viteServer.middlewares.stack.push({ route: webManifesturl, handle: emptyHandle })
viteServer.middlewares.stack.push({ route: devSw, handle: emptyHandle })
})

Expand Down
1 change: 1 addition & 0 deletions elk/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ export default defineNuxtConfig({
defaultLocale: 'en-US',
vueI18n: './config/i18n.config.ts',
bundle: {
// @ts-expect-error ???
optimizeTranslationDirective: false,
},
},
Expand Down

0 comments on commit 4f10d75

Please sign in to comment.