Skip to content

Commit 4308193

Browse files
committed
favicon: allow multiple input files
1 parent a5bccf3 commit 4308193

File tree

2 files changed

+73
-24
lines changed

2 files changed

+73
-24
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ Go to the `v1` branch to see the changelog of Lume 1.
99
## [3.1.2] - Unreleased
1010
### Added
1111
- `search.file()` to search for a single file.
12+
- `favicon` plugin: The `input` option allows an object with different files per size. Example:
13+
```ts
14+
site.use(favicon({
15+
input: {
16+
16: "favicon.svg",
17+
180: "big-favicon.svg"
18+
}
19+
}))
20+
```
1221

1322
### Changed
1423
- Cache results of `search.files()`.

plugins/favicon.ts

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface Options {
1111
* The input file to generate the favicons
1212
* Accepted formats are SVG, PNG, JPG, GIF, BMP, TIFF, WEBP
1313
*/
14-
input?: string;
14+
input?: string | Record<number, string>;
1515

1616
/**
1717
* The generated favicons
@@ -52,29 +52,44 @@ export interface Favicon {
5252
*/
5353
export function favicon(userOptions?: Options) {
5454
const options = merge(defaults, userOptions);
55+
const input = typeof options.input === "string"
56+
? { 16: options.input }
57+
: options.input;
5558

5659
return (site: Site) => {
57-
async function getContent(): Promise<Uint8Array | string | undefined> {
58-
const content = options.input.endsWith(".svg")
59-
? await site.getContent(options.input, false)
60-
: await site.getContent(options.input, true);
60+
async function getContent(
61+
file: string,
62+
): Promise<Uint8Array | string | undefined> {
63+
const content = file.endsWith(".svg")
64+
? await site.getContent(file, false)
65+
: await site.getContent(file, true);
6166

6267
if (!content) {
63-
log.warn(`[favicon plugin] Input file not found: ${options.input}`);
68+
log.warn(`[favicon plugin] Input file not found: ${file}`);
6469
}
6570

6671
return content;
6772
}
6873

6974
site.process(async function processFaviconImages(_, pages) {
70-
const content = await getContent();
75+
const contents: Record<number, Uint8Array | string> = {};
7176

72-
if (!content) {
77+
for (const [size, file] of Object.entries(input)) {
78+
const fileContent = await getContent(file);
79+
80+
if (fileContent) {
81+
contents[Number(size)] = fileContent;
82+
}
83+
}
84+
85+
if (!Object.keys(contents).length) {
7386
return;
7487
}
7588

7689
const { cache } = site;
7790
for (const favicon of options.favicons) {
91+
const content = getBestContent(contents, favicon.size);
92+
7893
pages.push(
7994
Page.create({
8095
url: favicon.url,
@@ -89,24 +104,31 @@ export function favicon(userOptions?: Options) {
89104
}
90105

91106
// Add the svg favicon
92-
if (
93-
options.input.endsWith(".svg") &&
94-
!site.pages.find((page) => page.data.url === options.input) &&
95-
!site.files.find((file) => file.outputPath === options.input)
96-
) {
97-
site.pages.push(
98-
Page.create({
99-
url: options.input,
100-
content: await site.getContent(
101-
options.input,
102-
false,
103-
),
104-
}),
105-
);
107+
const svg = Object.entries(input)
108+
.find(([, file]) => file.endsWith(".svg"));
109+
if (svg) {
110+
const size = Number(svg[0]);
111+
const url = input[size];
112+
const content = contents[size];
113+
if (
114+
!site.pages.find((page) => page.data.url === url) &&
115+
!site.files.find((file) => file.outputPath === url)
116+
) {
117+
site.pages.push(
118+
Page.create({
119+
url,
120+
content,
121+
}),
122+
);
123+
}
106124
}
107125
});
108126

109127
site.process([".html"], function processFaviconPages(pages) {
128+
const svg = Object.entries(input)
129+
.find(([, file]) => file.endsWith(".svg"));
130+
const svgUrl = svg ? input[Number(svg[0])] : null;
131+
110132
for (const page of pages) {
111133
const { document } = page;
112134

@@ -118,11 +140,11 @@ export function favicon(userOptions?: Options) {
118140
});
119141
}
120142

121-
if (options.input.endsWith(".svg")) {
143+
if (svgUrl) {
122144
addIcon(document, {
123145
rel: "icon",
124146
sizes: "any",
125-
href: site.url(options.input),
147+
href: site.url(svgUrl),
126148
type: "image/svg+xml",
127149
});
128150
}
@@ -176,4 +198,22 @@ async function buildIco(
176198
return image;
177199
}
178200

201+
function getBestContent(
202+
content: Record<number, Uint8Array | string>,
203+
sizes: number[],
204+
): Uint8Array | string {
205+
const size = Math.min(...sizes);
206+
const availableSizes = Object.keys(content).map(Number);
207+
208+
// Find the closest size available
209+
let bestSize = availableSizes[0];
210+
for (const s of availableSizes) {
211+
if (s <= size && s > bestSize) {
212+
bestSize = s;
213+
}
214+
}
215+
216+
return content[bestSize];
217+
}
218+
179219
export default favicon;

0 commit comments

Comments
 (0)