Skip to content

Commit

Permalink
[gh-452] Add basic PWA manifest (#516)
Browse files Browse the repository at this point in the history
  • Loading branch information
ashmind authored May 19, 2020
1 parent 68e7677 commit 1027289
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 25 deletions.
62 changes: 44 additions & 18 deletions source/WebApp/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,25 @@ const parallel = (...promises: ReadonlyArray<Promise<unknown>>) => 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))!;
Expand Down Expand Up @@ -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<void>;
}, { 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'),
Expand All @@ -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);
}, {
Expand All @@ -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
]
});

Expand All @@ -131,14 +156,15 @@ task('default', () => {
};

return parallel(
favicons(),
icons(),
manifest(),
htmlAll()
) as unknown as Promise<void>;
});

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, '\'')
Expand Down
File renamed without changes
15 changes: 8 additions & 7 deletions source/WebApp/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@
<meta name="description" content="C#/VB/F# compiler playground.">

<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,{build:favicon-svg}">
<link rel="icon" type="image/png" href="favicon-256.png" sizes="256x256">
<link rel="icon" type="image/png" href="favicon-196.png" sizes="196x196">
<link rel="icon" type="image/png" href="favicon-128.png" sizes="128x128">
<link rel="icon" type="image/png" href="favicon-96.png" sizes="96x96">
<link rel="icon" type="image/png" href="favicon-64.png" sizes="64x64">
<link rel="icon" type="image/png" href="favicon-32.png" sizes="32x32">
<link rel="icon" type="image/png" href="favicon-16.png" sizes="16x16">
<link rel="icon" type="image/png" href="icon-256.png" sizes="256x256">
<link rel="icon" type="image/png" href="icon-196.png" sizes="196x196">
<link rel="icon" type="image/png" href="icon-128.png" sizes="128x128">
<link rel="icon" type="image/png" href="icon-96.png" sizes="96x96">
<link rel="icon" type="image/png" href="icon-64.png" sizes="64x64">
<link rel="icon" type="image/png" href="icon-32.png" sizes="32x32">
<link rel="icon" type="image/png" href="icon-16.png" sizes="16x16">

<link rel="stylesheet" href="{build:css}">
<link rel="manifest" href="manifest.json">
</head>
<body data-mobile-codemirror-fullscreen-class="mobile-editor-focus" class="loading" data-cloak="loading">
<main v-bind:class="{
Expand Down
12 changes: 12 additions & 0 deletions source/WebApp/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"short_name": "SharpLab",
"name": "SharpLab",
"icons": [
{ "src": "icon-{build:each-size}.png", "type": "image/png", "sizes": "{size}x{size}", "purpose": "any maskable" },
{ "src": "icon.svg", "type": "image/svg+xml", "sizes": "any", "purpose": "any maskable" }
],
"start_url": ".",
"background_color": "#fff",
"theme_color": "#4684ee",
"display": "minimal-ui"
}

0 comments on commit 1027289

Please sign in to comment.