Skip to content
This repository has been archived by the owner on May 2, 2024. It is now read-only.

Commit

Permalink
feat(icon): cache generated images (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinmarrec committed Jan 5, 2023
1 parent f75f2c9 commit e64e5da
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 54 deletions.
109 changes: 60 additions & 49 deletions lib/generate.cjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* eslint-disable @typescript-eslint/no-var-requires */

const consola = require('consola')
const { remove, mkdirp } = require('fs-extra')
const { join } = require('pathe')
const { remove, mkdirp, existsSync, copy } = require('fs-extra')
const { join, resolve } = require('pathe')
const sharp = require('sharp')

const pkgName = require(join(__dirname, '../package.json')).name

const resizeOptions = {
fit: 'contain',
background: 'transparent',
Expand All @@ -15,57 +17,66 @@ async function ensureDir (dir) {
await mkdirp(dir)
}

async function generate ({ input, distDir, sizes, maskableInput, maskablePadding, splash, hash }) {
await ensureDir(distDir)
async function generate ({ input, buildAssetsDir, targetDir, sizes, maskableInput, maskablePadding, splash, hash }) {
const cacheDir = resolve(`node_modules/.cache/${pkgName}/${hash}`)

if (!existsSync(cacheDir)) {
const iconCacheDir = join(cacheDir, targetDir)
await ensureDir(iconCacheDir)

// Resized icons (purpose: any)
await Promise.all(sizes.map(size =>
sharp(input)
.resize(Math.floor(size), Math.floor(size), resizeOptions)
.toFile(join(distDir, `${size}x${size}${hash}.png`)),
))
// Resized icons (purpose: any)
await Promise.all(sizes.map(size =>
sharp(input)
.resize(Math.floor(size), Math.floor(size), resizeOptions)
.toFile(join(iconCacheDir, `${size}x${size}.${hash}.png`)),
))

// Resized icons (purpose: maskable)
await Promise.all(sizes.map(async (size) => {
await sharp({
create: {
width: size,
height: size,
channels: 4,
background: { r: 0, g: 0, b: 0, alpha: 0 },
},
}).composite([{
input: await sharp(maskableInput)
.resize(Math.floor(size * (1 - maskablePadding / 100)), Math.floor(size * (1 - maskablePadding / 100)), resizeOptions)
.toBuffer(),
}])
.toFile(join(distDir, `${size}x${size}.maskable${hash}.png`))
},
))
// Resized icons (purpose: maskable)
await Promise.all(sizes.map(async (size) => {
await sharp({
create: {
width: size,
height: size,
channels: 4,
background: { r: 0, g: 0, b: 0, alpha: 0 },
},
}).composite([{
input: await sharp(maskableInput)
.resize(Math.floor(size * (1 - maskablePadding / 100)), Math.floor(size * (1 - maskablePadding / 100)), resizeOptions)
.toBuffer(),
}])
.toFile(join(iconCacheDir, `${size}x${size}.maskable.${hash}.png`))
},
))

// Splash
if (splash) {
distDir = join(distDir, `../${splash.targetDir}`)
await ensureDir(distDir)
await Promise.all(splash.devices.map(async (device) => {
try {
await sharp({
create: {
width: device.width,
height: device.height,
channels: 4,
background: splash.backgroundColor,
},
}).composite([{
input: await sharp(input).resize(512, 512, resizeOptions).toBuffer(),
}])
.toFile(join(distDir, `${device.width}x${device.height}${hash}.png`))
}
catch (err) {
consola.warn(`[PWA] Failed to generate splash (${device.width}x${device.height}) because icon input has larger dimensions`)
}
}))
// Splash
if (splash) {
const splashCacheDir = join(cacheDir, splash.targetDir)
await ensureDir(splashCacheDir)

await Promise.all(splash.devices.map(async (device) => {
try {
await sharp({
create: {
width: device.width,
height: device.height,
channels: 4,
background: splash.backgroundColor,
},
}).composite([{
input: await sharp(input).resize(512, 512, resizeOptions).toBuffer(),
}])
.toFile(join(cacheDir, splash.targetDir, `${device.width}x${device.height}.${hash}.png`))
}
catch (err) {
consola.warn(`[PWA] Failed to generate splash (${device.width}x${device.height}) because icon input has larger dimensions`)
}
}))
}
}

await ensureDir(buildAssetsDir)
await copy(cacheDir, buildAssetsDir)
}

generate(JSON.parse(process.argv[2])).then(() => {
Expand Down
7 changes: 4 additions & 3 deletions src/parts/icon/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { existsSync } from 'node:fs'
import { fork } from 'node:child_process'
import consola from 'consola'
import { join, relative, resolve } from 'pathe'
import { relative, resolve } from 'pathe'
import { provider } from 'std-env'
import { joinURL } from 'ufo'
import { addTemplate, useNuxt } from '@nuxt/kit'
Expand Down Expand Up @@ -47,7 +47,7 @@ export default async (pwa: PWAContext) => {
options.sizes = [64, 120, 144, 152, 192, 384, 512]

// Hash as suffix for production
const hash = nuxt.options.dev ? '' : `.${await getFileHash(options.source)}`
const hash = await getFileHash(options.source)

const iconsDir = joinURL(
nuxt.options.app.baseURL,
Expand Down Expand Up @@ -85,7 +85,8 @@ export default async (pwa: PWAContext) => {

const generateOptions = JSON.stringify({
input: options.source,
distDir: join(pwa._buildAssetsDir, options.targetDir),
buildAssetsDir: pwa._buildAssetsDir,
targetDir: options.targetDir,
sizes: options.sizes,
maskableInput: options.maskableSource,
maskablePadding: options.maskablePadding,
Expand Down
2 changes: 1 addition & 1 deletion src/parts/icon/splash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const metaFromDevice = (device: Device, options: { assetsDir: string; has
const { assetsDir, hash } = options

return {
href: join(assetsDir, `${width}x${height}${hash}.png`),
href: join(assetsDir, `${width}x${height}.${hash}.png`),
media: [
`(device-width: ${width / pixelRatio}px)`,
`(device-height: ${height / pixelRatio}px)`,
Expand Down
2 changes: 1 addition & 1 deletion src/parts/icon/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export async function getFileHash (filePath: string): Promise<string> {

export function makeManifestIcon ({ iconsDir, size, purpose, hash }: ManifestIconMakerOptions): ManifestIcon {
return {
src: joinURL(iconsDir, `${size}x${size}${purpose === 'maskable' ? '.maskable' : ''}${hash}.png`),
src: joinURL(iconsDir, `${size}x${size}${purpose === 'maskable' ? '.maskable' : ''}.${hash}.png`),
type: 'image/png',
sizes: `${size}x${size}`,
purpose,
Expand Down

0 comments on commit e64e5da

Please sign in to comment.