Merge pull request #27 from rotary-dev-fellowship/feat-faviconGen
Dynamic manifest and favicon generation
|
@ -191,14 +191,6 @@ Static files served directly to the browser are within the `public` directory at
|
||||||
```md
|
```md
|
||||||
|
|
||||||
public/
|
public/
|
||||||
├── apple-touch-icon.png
|
|
||||||
├── favicon.ico
|
|
||||||
├── icon-192.png
|
|
||||||
├── icon-512.png
|
|
||||||
├── icon.svg
|
|
||||||
├── manifest.webmanifest
|
|
||||||
├── maskable_icon.png
|
|
||||||
├── maskable_icon_x512.png
|
|
||||||
├── scripts/
|
├── scripts/
|
||||||
│ └── vendor/
|
│ └── vendor/
|
||||||
│ ├── gsap/ # Animations powered by GSAP (GreenSock Animation Platform)
|
│ ├── gsap/ # Animations powered by GSAP (GreenSock Animation Platform)
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
"gsap": "^3.12.5",
|
"gsap": "^3.12.5",
|
||||||
"html-minifier": "^4.0.0",
|
"html-minifier": "^4.0.0",
|
||||||
"preline": "^2.0.3",
|
"preline": "^2.0.3",
|
||||||
|
"sharp": "^0.33.3",
|
||||||
|
"sharp-ico": "^0.1.5",
|
||||||
"tailwindcss": "^3.4.1"
|
"tailwindcss": "^3.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.5 KiB |
|
@ -1,35 +0,0 @@
|
||||||
{
|
|
||||||
"short_name": "ScrewFast",
|
|
||||||
"name": "ScrewFast",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "/icon-192.png",
|
|
||||||
"sizes": "192x192",
|
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "any"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/icon-512.png",
|
|
||||||
"sizes": "512x512",
|
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "any"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/maskable_icon_x512.png",
|
|
||||||
"sizes": "512x512",
|
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "maskable"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/maskable_icon.png",
|
|
||||||
"sizes": "1000x1000",
|
|
||||||
"type": "image/png",
|
|
||||||
"purpose": "maskable"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"display": "minimal-ui",
|
|
||||||
"id": "/",
|
|
||||||
"start_url": "/",
|
|
||||||
"theme_color": "#FFEDD5",
|
|
||||||
"background_color": "#262626"
|
|
||||||
}
|
|
Before Width: | Height: | Size: 14 KiB |
|
@ -1,4 +1,8 @@
|
||||||
---
|
---
|
||||||
|
import { getImage } from "astro:assets";
|
||||||
|
import faviconSvgSrc from "@/images/icon.svg";
|
||||||
|
import faviconSrc from "@/images/icon.png";
|
||||||
|
|
||||||
// Default properties for the Meta component. These values are used if props are not provided.
|
// Default properties for the Meta component. These values are used if props are not provided.
|
||||||
// 'title' sets the default page title for the website.
|
// 'title' sets the default page title for the website.
|
||||||
// 'meta' sets a default description meta tag to describe the page content.
|
// 'meta' sets a default description meta tag to describe the page content.
|
||||||
|
@ -35,6 +39,20 @@ const ogDescription: string =
|
||||||
"Equip your projects with ScrewFast's top-quality hardware tools and expert construction services. Trusted by industry leaders, ScrewFast offers simplicity, affordability, and reliability. Experience the difference with user-centric design and cutting-edge tools. Start exploring now!"; // Set the Open Graph description
|
"Equip your projects with ScrewFast's top-quality hardware tools and expert construction services. Trusted by industry leaders, ScrewFast offers simplicity, affordability, and reliability. Experience the difference with user-centric design and cutting-edge tools. Start exploring now!"; // Set the Open Graph description
|
||||||
const URL: string = `${Astro.site}`; // Set the website URL in astro.config.mjs
|
const URL: string = `${Astro.site}`; // Set the website URL in astro.config.mjs
|
||||||
const socialImage: string = `${Astro.site}/social.png`; // Set the path for the social media image
|
const socialImage: string = `${Astro.site}/social.png`; // Set the path for the social media image
|
||||||
|
|
||||||
|
// Generate and optimize the favicon images
|
||||||
|
const faviconSvg = await getImage({
|
||||||
|
src: faviconSvgSrc,
|
||||||
|
format: 'svg',
|
||||||
|
});
|
||||||
|
|
||||||
|
const appleTouchIcon = await getImage({
|
||||||
|
src: faviconSrc,
|
||||||
|
width: 180,
|
||||||
|
height: 180,
|
||||||
|
format: 'png',
|
||||||
|
});
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Inject structured data into the page if provided. This data is formatted as JSON-LD, a method recommended by Google for structured data pass:
|
<!-- Inject structured data into the page if provided. This data is formatted as JSON-LD, a method recommended by Google for structured data pass:
|
||||||
|
@ -73,15 +91,15 @@ const socialImage: string = `${Astro.site}/social.png`; // Set the path for the
|
||||||
<meta name="twitter:image" content={socialImage} />
|
<meta name="twitter:image" content={socialImage} />
|
||||||
|
|
||||||
<!-- Links to the webmanifest and sitemap -->
|
<!-- Links to the webmanifest and sitemap -->
|
||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<!-- https://docs.astro.build/en/guides/integrations-guide/sitemap/ -->
|
<!-- https://docs.astro.build/en/guides/integrations-guide/sitemap/ -->
|
||||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||||
|
|
||||||
<!-- Links for favicons -->
|
<!-- Links for favicons -->
|
||||||
<link href="/favicon.ico" rel="icon" sizes="any" type="image/x-icon" />
|
<link href="/favicon.ico" rel="icon" sizes="any" type="image/x-icon" />
|
||||||
<link href="/icon.svg" rel="icon" type="image/svg+xml" sizes="any" />
|
<link href={faviconSvg.src} rel="icon" type="image/svg+xml" sizes="any" />
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<link href="/apple-touch-icon.png" rel="apple-touch-icon" />
|
<link href={appleTouchIcon.src} rel="apple-touch-icon" />
|
||||||
<link href="/apple-touch-icon.png" rel="shortcut icon" />
|
<link href={appleTouchIcon.src} rel="shortcut icon" />
|
||||||
<!-- Set theme color -->
|
<!-- Set theme color -->
|
||||||
<meta name="theme-color" content="#facc15" />
|
<meta name="theme-color" content="#facc15" />
|
||||||
|
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 987 B After Width: | Height: | Size: 987 B |
28
src/pages/favicon.ico.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import type { APIRoute } from "astro";
|
||||||
|
import sharp from "sharp";
|
||||||
|
import ico from "sharp-ico";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
const faviconSrc = path.resolve("src/images/icon.png");
|
||||||
|
|
||||||
|
export const GET: APIRoute = async () => {
|
||||||
|
|
||||||
|
// Resize the image to multiple sizes
|
||||||
|
const sizes = [16, 32, 48, 64, 128, 256];
|
||||||
|
|
||||||
|
const buffers = await Promise.all(
|
||||||
|
sizes.map(async (size) => {
|
||||||
|
return await sharp(faviconSrc)
|
||||||
|
.resize(size)
|
||||||
|
.toFormat("png")
|
||||||
|
.toBuffer();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Convert the image to an ICO file
|
||||||
|
const icoBuffer = ico.encode(buffers);
|
||||||
|
|
||||||
|
return new Response(icoBuffer, {
|
||||||
|
headers: { "Content-Type": "image/x-icon" },
|
||||||
|
});
|
||||||
|
};
|
58
src/pages/manifest.json.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import type { APIRoute, ImageMetadata } from "astro";
|
||||||
|
import { getImage } from "astro:assets";
|
||||||
|
import icon from "@/images/icon.png";
|
||||||
|
import maskableIcon from "@/images/icon-maskable.png";
|
||||||
|
|
||||||
|
interface Favicon {
|
||||||
|
purpose: 'any' | 'maskable' | 'monochrome';
|
||||||
|
src: ImageMetadata;
|
||||||
|
sizes: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizes = [192, 512];
|
||||||
|
const favicons: Favicon[] = [
|
||||||
|
{
|
||||||
|
purpose: 'any',
|
||||||
|
src: icon,
|
||||||
|
sizes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
purpose: 'maskable',
|
||||||
|
src: maskableIcon,
|
||||||
|
sizes,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const GET: APIRoute = async () => {
|
||||||
|
const icons = await Promise.all(
|
||||||
|
favicons.flatMap((favicon) =>
|
||||||
|
favicon.sizes.map(async (size) => {
|
||||||
|
const image = await getImage({
|
||||||
|
src: favicon.src,
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
format: "png",
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
src: image.src,
|
||||||
|
sizes: `${image.options.width}x${image.options.height}`,
|
||||||
|
type: `image/${image.options.format}`,
|
||||||
|
purpose: favicon.purpose,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const manifest = {
|
||||||
|
short_name: "ScrewFast",
|
||||||
|
name: "ScrewFast",
|
||||||
|
icons,
|
||||||
|
display: "minimal-ui",
|
||||||
|
id: "some-unique-id",
|
||||||
|
start_url: "/",
|
||||||
|
theme_color: "#FFEDD5",
|
||||||
|
background_color: "#262626",
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(manifest));
|
||||||
|
};
|