Skip to content

Commit

Permalink
feat: added support for inline manifest icons
Browse files Browse the repository at this point in the history
  • Loading branch information
swernerx committed Aug 2, 2024
1 parent ada127e commit 6618030
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 15 deletions.
13 changes: 9 additions & 4 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,34 @@ function splitList(value: string): number[] {
program
.argument("<fileOrFolder>", "File or folder to process")
.option(
"--png-quality <quality>",
"-p, --png-quality <quality>",
"Set PNG quality",
Number,
DEFAULTS.pngQuality
)
.option(
"--manifest-icon-sizes <sizes>",
"-m, --manifest-icon-sizes <sizes>",
"Set manifest icon sizes (comma-separated)",
splitList,
DEFAULTS.manifestIconSizes
)
.option(
"--apple-icon-sizes <sizes>",
"-a, --apple-icon-sizes <sizes>",
"Set Apple icon sizes (comma-separated)",
splitList,
DEFAULTS.appleIconSizes
)
.option(
"--fav-icon-sizes <sizes>",
"-s, --fav-icon-sizes <sizes>",
"Set favicon sizes (comma-separated)",
splitList,
DEFAULTS.favIconSizes
)
.option(
"--no-inline-manifest-icons",
"Disable inlining manifest icons",
DEFAULTS.inlineManifestIcons
)
.parse(process.argv)

// Parse and normalize the options
Expand Down
39 changes: 32 additions & 7 deletions src/factory/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,34 @@
import { promises as fs } from "node:fs"
import { basename } from "node:path"
import { extname } from "node:path"

import { readPackageUp } from "read-package-up"
import sharp from "sharp"

import type { Options } from "../options.js"
import type { WebManifest, WebManifestIcon } from "../types.js"

/**
* Converts an image file to a base64 data URL string.
* @param filePath - The path to the image file.
* @returns A promise that resolves to the base64 data URL string.
*/
async function inlineIcon(filePath: string): Promise<string> {
try {
const data = await fs.readFile(filePath)
const ext = extname(filePath).slice(1) // Get the file extension without the dot
const mimeType = `image/${ext}`
const base64Data = data.toString("base64")
const dataUrl = `data:${mimeType};base64,${base64Data}`
return dataUrl
} catch (error) {
const wrapped =
error instanceof Error
? new Error(`Failed to read file: ${error.message}`)
: new Error("An unknown error occurred")
throw wrapped
}
}

export async function generateWebManifest(
filePrefix: string,
svgContent: string,
Expand All @@ -16,19 +38,14 @@ export async function generateWebManifest(
const icons: WebManifestIcon[] = []
const manifestData: WebManifest = {
name: pkg?.packageJson.description,
start_url: ".",
start_url: "/",
scope: "/",
display: "browser",
icons
}

for (const size of options.manifestIconSizes) {
const pngFilePath = `${filePrefix}-pwa-${size}.png`
icons.push({
src: basename(pngFilePath),
type: "image/png",
sizes: `${size}x${size}`
})

await sharp(Buffer.from(svgContent))
.resize(size, size)
Expand All @@ -39,6 +56,14 @@ export async function generateWebManifest(
quality: options.pngQuality
})
.toFile(pngFilePath)

icons.push({
src: options.inlineManifestIcons
? await inlineIcon(pngFilePath)
: basename(pngFilePath),
type: "image/png",
sizes: `${size}x${size}`
})
}

const manifestPath = `${filePrefix}.webmanifest`
Expand Down
3 changes: 2 additions & 1 deletion src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ export const DEFAULTS = {
pngQuality: 95,
manifestIconSizes: [192, 512],
appleIconSizes: [152, 167, 180],
favIconSizes: [16, 32]
favIconSizes: [16, 32],
inlineManifestIcons: true
}

export type Options = typeof DEFAULTS
6 changes: 3 additions & 3 deletions test/sebastian-software.webmanifest
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"name": "Effective Favicon Generator",
"start_url": ".",
"start_url": "/",
"scope": "/",
"display": "browser",
"icons": [
{
"src": "sebastian-software-pwa-192.png",
"src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAMAAABlApw1AAAAV1BMVEUqGifz8vNFOEPPy87FAEQzJTH8/PwpGicqGyj///9VSVTl4+XIxMeHCzl5cHhwZm97DTc9Lzvb2NpgVV83GCqsBD9LFS65tLm8AUKln6RiETKSipF+DDdi0SGKAAAACHRSTlPm////////7vrFWCYAAAAJcEhZcwAACxMAAAsTAQCanBgAAARLSURBVHja7d1tc6IwFIZhRWGNWhVQEe3//51b123rC8Gc5DwnJzPhqzOd+7JCEkCcLNLeJpMMyIAMyIAMyIAMyIAMyADbVtRpA4pVU6cMKFbGYAVYwLUfLIACbv1YARLw3Q8VAAG//UgBDnDfDxTAAI/9OAEK8Nz/JShTArz2G9MlBBjqN2aZDGC431xSAVj6MZ8hAMDWnwrA2m/6JAD2ftOmABjpn6VwGB3pnxYJAMb6IaMAMyBCPysgRj8nIEo/IyBOPx8gUj8bIFY/FyBaPxMgXj8PIGI/CyBmPwcgaj8DIG5/OCByfzAgdn8oIHp/ICB+fxhAQX8QQEN/CEBFfwBAR78/QEm/N0BLvy9ATb8nQE+/H0BRvxdAU78PgNZfXLqubzUBaP19dbtEqefMHKm/XP+85vJPOJ7P5z0Y4NnvIjgf5l/b6RMK8O5/L9jN/2+HPQ4Q0P9O8NNPFJAAQf3jx9i7fpqAAgjsN2ZVOvWTBARAcL/9Wv1TP0XgDmDot12ofOknCJwBHP1m6trvLnAFlA1D/zBgsN9Z4AroOfpN497vKnAElFOO/qGd+HNu3XaMgCVL/7R2f//n8w/Oj9CWo7/a8ve7AlqO/g2g33kfqJT2Ox+FOqX9zoBiqrPffSRupyr7CXOhJ4GSfsps9EGgpZ+0HrgTqOmnrch+BCs1/dQ1cXcdD6q+VtNPPq1St5v2dWEYr5/nZo+I/SyAmP0cgKj9DIC4/eGAyP3BgNj9oYDo/YGA+P1hAAX9QQAN/SEAFf0BAB39/gAl/d4ALf2+ADX9ngA9/X4ARf1eAE39PgBk/3Jz2RRgALB/Ofv3Z9YFEgDs31aUO0M8ARL9JAERINNPEdAAUv0EAQkg1+8uoAAk+wcvaQYCZPtdv4LvDpDut9xY4Q0Q7zemYL3ZQ77f7VEmrteJSf1Hln5TMwJ6Sv/ig6W/4dwH6obQvzhx9JsN605sEQz271n618wD2aCgGn6XOPpnJfdIPCCw9C8Ocv2UkfhFYOsfPopi+klzoSeBtX+xP4n102ajD4Jq5ChxPEn1E9cDd4Jq9Cj3LID1U1dk9cx+/9uIANdPXxNfrvdLVOu385R7AbDf57zQctu6zFJ+Bch+5NMuj7fh4HRG9mMfmHrcfXwOfKuHsz/GY5tZ+yMAePvlAcz94gDufmkAe78wgL9fFgDoFwUg+iUBkH5BAKZfDgDqFwOg+qUAsH4hAK5fBgDsFwEg+yUA0H4BALYfDwD3wwHofjQA3g8G4PuxAIF+KECiHwkQ6QcCZPpxAKF+GKAV6kcBypVQPwqwkepHATqpfhSgkepHAWZS/ShAL9WPArRS/bBxoBPqhwFeH6g0K9P6Sbvne1tA/cDJ3KMA1Y+cTt8LYP3QBc2vANePXVJ+C4D94EX9TYDsR59WuQqg/fATW3WD7cefWqzL/CPjGZABGZABGZABGZABGWAF/El6m0z+AiP1e02uRAyBAAAAAElFTkSuQmCC",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "sebastian-software-pwa-512.png",
"src": "data:image/png;base64,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",
"type": "image/png",
"sizes": "512x512"
}
Expand Down

0 comments on commit 6618030

Please sign in to comment.