From 102728997c23305334eda3a02affdeddd3366219 Mon Sep 17 00:00:00 2001 From: Andrey Shchekin Date: Tue, 19 May 2020 21:07:17 +1200 Subject: [PATCH] [gh-452] Add basic PWA manifest (#516) --- source/WebApp/build.ts | 62 ++++++++++++++++++------- source/WebApp/{favicon.svg => icon.svg} | 0 source/WebApp/index.html | 15 +++--- source/WebApp/manifest.json | 12 +++++ 4 files changed, 64 insertions(+), 25 deletions(-) rename source/WebApp/{favicon.svg => icon.svg} (100%) create mode 100644 source/WebApp/manifest.json diff --git a/source/WebApp/build.ts b/source/WebApp/build.ts index 79fe41d1e..3fbe29d39 100644 --- a/source/WebApp/build.ts +++ b/source/WebApp/build.ts @@ -24,19 +24,25 @@ const parallel = (...promises: ReadonlyArray>) => Promise.all(p const paths = { from: { less: `${dirname}/less/app.less`, - favicon: `${dirname}/favicon.svg`, - html: `${dirname}/index.html` + icon: `${dirname}/icon.svg`, + html: `${dirname}/index.html`, + manifest: `${__dirname}/manifest.json` }, to: { css: `${outputRoot}/app.min.css`, - favicon: { - svg: `${outputRoot}/favicon.svg`, - png: `${outputRoot}/favicon-{size}.png` + icon: { + svg: `${outputRoot}/icon.svg`, + png: `${outputRoot}/icon-{size}.png` }, - html: `${outputRoot}/index.html` + html: `${outputRoot}/index.html`, + manifest: `${outputRoot}/manifest.json` } }; +const iconSizes = [ + 16, 32, 64, 72, 96, 120, 128, 144, 152, 180, 192, 196, 256, 384, 512 +]; + const less = task('less', async () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const content = (await jetpack.readAsync(paths.from.less))!; @@ -81,25 +87,44 @@ const ts = task('ts', async () => { ] }); -const favicons = task('favicons', async () => { +const icons = task('icons', async () => { await jetpack.dirAsync(outputRoot); - const pngGeneration = [16, 32, 64, 96, 128, 196, 256].map(size => { + const pngGeneration = iconSizes.map(size => { // https://github.com/lovell/sharp/issues/729 const density = size > 128 ? Math.round(72 * size / 128) : 72; - return sharp(paths.from.favicon, { density }) + return sharp(paths.from.icon, { density }) .resize(size, size) .png() - .toFile(paths.to.favicon.png.replace('{size}', size.toString())); + .toFile(paths.to.icon.png.replace('{size}', size.toString())); }); return parallel( - jetpack.copyAsync(paths.from.favicon, paths.to.favicon.svg, { overwrite: true }), + jetpack.copyAsync(paths.from.icon, paths.to.icon.svg, { overwrite: true }), ...pngGeneration ) as unknown as Promise; -}, { inputs: [paths.from.favicon] }); +}, { inputs: [paths.from.icon] }); + +const manifest = task('manifest', async () => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const content = JSON.parse((await jetpack.readAsync(paths.from.manifest))!) as { + icons: ReadonlyArray<{ src: string }>; + }; + + content.icons = content.icons.flatMap(icon => { + if (!icon.src.includes('{build:each-size}')) + return [icon]; + + const template = JSON.stringify(icon); // simpler than Object.entries + return iconSizes.map(size => JSON.parse( + template.replace(/\{(?:build:each-)?size\}/g, size.toString()) + ) as typeof icon); + }); + + await jetpack.writeAsync(paths.to.manifest, JSON.stringify(content)); +}, { inputs: [paths.from.manifest] }); const html = task('html', async () => { - const faviconDataUrl = await getFaviconDataUrl(); + const iconDataUrl = await getIconDataUrl(); const templates = await getCombinedTemplates(); const [jsHash, cssHash] = await parallel( md5File('wwwroot/app.min.js'), @@ -111,7 +136,7 @@ const html = task('html', async () => { .replace('{build:js}', 'app.min.js?' + jsHash) .replace('{build:css}', 'app.min.css?' + cssHash) .replace('{build:templates}', templates) - .replace('{build:favicon-svg}', faviconDataUrl); + .replace('{build:favicon-svg}', iconDataUrl); html = htmlMinifier.minify(html, { collapseWhitespace: true }); await jetpack.writeAsync(paths.to.html, html); }, { @@ -120,7 +145,7 @@ const html = task('html', async () => { paths.to.css, `${outputRoot}/app.min.js`, paths.from.html, - paths.from.favicon + paths.from.icon ] }); @@ -131,14 +156,15 @@ task('default', () => { }; return parallel( - favicons(), + icons(), + manifest(), htmlAll() ) as unknown as Promise; }); -async function getFaviconDataUrl() { +async function getIconDataUrl() { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const faviconSvg = (await jetpack.readAsync(paths.from.favicon))!; + const faviconSvg = (await jetpack.readAsync(paths.from.icon))!; // http://codepen.io/jakob-e/pen/doMoML return faviconSvg .replace(/"/g, '\'') diff --git a/source/WebApp/favicon.svg b/source/WebApp/icon.svg similarity index 100% rename from source/WebApp/favicon.svg rename to source/WebApp/icon.svg diff --git a/source/WebApp/index.html b/source/WebApp/index.html index e4137d88c..a6611e8a7 100644 --- a/source/WebApp/index.html +++ b/source/WebApp/index.html @@ -9,15 +9,16 @@ - - - - - - - + + + + + + + +