Skip to content

Commit 027ee25

Browse files
committed
Make FaviconHtmlTag an interface
1 parent 8e408c9 commit 027ee25

26 files changed

+935
-866
lines changed

src/index.ts

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,10 @@ export const config = {
2020
};
2121

2222
export type FaviconHtmlElement = string;
23-
export class FaviconHtmlTag {
23+
24+
export interface FaviconHtmlTag {
2425
readonly tag: string;
2526
readonly attrs: Record<string, string | boolean>;
26-
27-
constructor(tag: string, attrs: Record<string, string | boolean> = {}) {
28-
this.tag = tag;
29-
this.attrs = attrs;
30-
}
31-
32-
stringify() {
33-
const attrs = Object.entries(this.attrs)
34-
.map(([key, value]) => {
35-
if (value === true) return key;
36-
if (value === false) return "";
37-
return `${key}="${value}"`;
38-
})
39-
.filter(Boolean)
40-
.join(" ");
41-
42-
return `<${this.tag} ${attrs || ""}>`;
43-
}
4427
}
4528

4629
export interface FaviconResponse {

src/platforms/android.ts

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import escapeHtml from "escape-html";
21
import { FaviconHtmlTag, FaviconFile, FaviconImage } from "../index";
32
import {
43
FaviconOptions,
@@ -138,32 +137,37 @@ export class AndroidPlatform extends Platform {
138137

139138
override async createHtml(): Promise<FaviconHtmlTag[]> {
140139
return [
141-
this.options.loadManifestWithCredentials
142-
? new FaviconHtmlTag("link", {
143-
rel: "manifest",
144-
href: this.cacheBusting(this.relative(this.manifestFileName())),
145-
crossOrigin: "use-credentials",
146-
})
147-
: new FaviconHtmlTag("link", {
148-
rel: "manifest",
149-
href: this.cacheBusting(this.relative(this.manifestFileName())),
150-
}),
151-
new FaviconHtmlTag("meta", {
152-
name: "mobile-web-app-capable",
153-
content: "yes",
154-
}),
155-
new FaviconHtmlTag("meta", {
156-
name: "theme-color",
157-
content: this.options.theme_color || this.options.background,
158-
}),
159-
this.options.appName
160-
? new FaviconHtmlTag("meta", {
161-
name: "application-name",
162-
content: escapeHtml(this.options.appName),
163-
})
164-
: new FaviconHtmlTag("meta", {
165-
name: "application-name",
166-
}),
140+
{
141+
tag: "link",
142+
attrs: {
143+
rel: "manifest",
144+
href: this.cacheBusting(this.relative(this.manifestFileName())),
145+
crossOrigin: this.options.loadManifestWithCredentials
146+
? "use-credentials"
147+
: false,
148+
},
149+
},
150+
{
151+
tag: "meta",
152+
attrs: {
153+
name: "mobile-web-app-capable",
154+
content: "yes",
155+
},
156+
},
157+
{
158+
tag: "meta",
159+
attrs: {
160+
name: "theme-color",
161+
content: this.options.theme_color || this.options.background,
162+
},
163+
},
164+
{
165+
tag: "meta",
166+
attrs: {
167+
name: "application-name",
168+
content: this.options.appName || false,
169+
},
170+
},
167171
];
168172
}
169173

src/platforms/appleIcon.ts

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import escapeHtml from "escape-html";
21
import { FaviconHtmlTag } from "../index";
32
import { FaviconOptions, NamedIconOptions } from "../config/defaults";
43
import { opaqueIcon } from "../config/icons";
@@ -33,33 +32,41 @@ export class AppleIconPlatform extends Platform {
3332
.filter(({ name }) => /\d/.test(name)) // with a size in a name
3433
.map((options) => {
3534
const { width, height } = options.sizes[0];
36-
return new FaviconHtmlTag("link", {
37-
rel: "apple-touch-icon",
38-
sizes: `${width}x${height}`,
39-
href: this.cacheBusting(this.relative(options.name)),
40-
});
35+
return {
36+
tag: "link",
37+
attrs: {
38+
rel: "apple-touch-icon",
39+
sizes: `${width}x${height}`,
40+
href: this.cacheBusting(this.relative(options.name)),
41+
},
42+
};
4143
});
4244

4345
const name = this.options.appShortName || this.options.appName;
4446

4547
return [
4648
...icons,
47-
new FaviconHtmlTag("meta", {
48-
name: "apple-mobile-web-app-capable",
49-
content: "yes",
50-
}),
51-
new FaviconHtmlTag("meta", {
52-
name: "apple-mobile-web-app-status-bar-style",
53-
content: this.options.appleStatusBarStyle,
54-
}),
55-
name
56-
? new FaviconHtmlTag("meta", {
57-
name: "apple-mobile-web-app-title",
58-
content: escapeHtml(name),
59-
})
60-
: new FaviconHtmlTag("meta", {
61-
name: "apple-mobile-web-app-title",
62-
}),
49+
{
50+
tag: "meta",
51+
attrs: {
52+
name: "apple-mobile-web-app-capable",
53+
content: "yes",
54+
},
55+
},
56+
{
57+
tag: "meta",
58+
attrs: {
59+
name: "apple-mobile-web-app-status-bar-style",
60+
content: this.options.appleStatusBarStyle,
61+
},
62+
},
63+
{
64+
tag: "meta",
65+
attrs: {
66+
name: "apple-mobile-web-app-title",
67+
content: name || false,
68+
},
69+
},
6370
];
6471
}
6572
}

src/platforms/appleStartup.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,13 @@ export class AppleStartupPlatform extends Platform<AppleStartupImage> {
7171
}
7272

7373
override async createHtml(): Promise<FaviconHtmlTag[]> {
74-
return this.iconOptions.map(
75-
(item) =>
76-
new FaviconHtmlTag("link", {
77-
rel: "apple-touch-startup-image",
78-
media: `(device-width: ${item.deviceWidth}px) and (device-height: ${item.deviceHeight}px) and (-webkit-device-pixel-ratio: ${item.pixelRatio}) and (orientation: ${item.orientation})`,
79-
href: this.cacheBusting(this.relative(item.name)),
80-
}),
81-
);
74+
return this.iconOptions.map((item) => ({
75+
tag: "link",
76+
attrs: {
77+
rel: "apple-touch-startup-image",
78+
media: `(device-width: ${item.deviceWidth}px) and (device-height: ${item.deviceHeight}px) and (-webkit-device-pixel-ratio: ${item.pixelRatio}) and (orientation: ${item.orientation})`,
79+
href: this.cacheBusting(this.relative(item.name)),
80+
},
81+
}));
8282
}
8383
}

src/platforms/base.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import escapeHTML from "escape-html";
12
import {
23
FaviconFile,
34
FaviconHtmlTag,
@@ -60,6 +61,26 @@ export function uniformIconOptions<T extends NamedIconOptions>(
6061
}));
6162
}
6263

64+
function attrSorkKey(key: string): string {
65+
const attrs = ["name", "rel", "type", "media", "sizes"];
66+
const index = attrs.indexOf(key);
67+
return index >= 0 ? `${index}_${key}` : `z_${key}`;
68+
}
69+
70+
function renderHtmlTag(tag: FaviconHtmlTag): string {
71+
const attrs = Object.entries(tag.attrs)
72+
.toSorted((a, b) => attrSorkKey(a[0]).localeCompare(attrSorkKey(b[0])))
73+
.map(([key, value]) => {
74+
if (value === true) return key;
75+
if (value === false) return "";
76+
return `${key}="${escapeHTML(value)}"`;
77+
})
78+
.filter(Boolean)
79+
.join(" ");
80+
81+
return `<${tag.tag} ${attrs || ""}>`;
82+
}
83+
6384
export class Platform<IO extends NamedIconOptions = NamedIconOptions> {
6485
protected options: FaviconOptions;
6586
protected iconOptions: IO[];
@@ -80,7 +101,7 @@ export class Platform<IO extends NamedIconOptions = NamedIconOptions> {
80101
return {
81102
images,
82103
files,
83-
html: htmlTags.map((element) => element.stringify()),
104+
html: htmlTags.map(renderHtmlTag),
84105
htmlTags,
85106
};
86107
}

src/platforms/favicons.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,23 @@ export class FaviconsPlatform extends Platform {
2121

2222
override async createHtml(): Promise<FaviconHtmlTag[]> {
2323
return this.iconOptions.map(({ name, ...options }) => {
24-
const baseAttrs: Record<string, string> = {
24+
const attrs: Record<string, string> = {
2525
rel: "icon",
2626
type: "image/png",
27+
href: this.cacheBusting(this.relative(name)),
2728
};
2829
if (name.endsWith(".ico")) {
29-
baseAttrs.type = "image/x-icon";
30+
attrs.type = "image/x-icon";
3031
} else if (name.endsWith(".svg")) {
31-
baseAttrs.type = "image/svg+xml";
32+
attrs.type = "image/svg+xml";
3233
} else {
3334
const { width, height } = options.sizes[0];
34-
baseAttrs.sizes = `${width}x${height}`;
35+
attrs.sizes = `${width}x${height}`;
3536
}
36-
return new FaviconHtmlTag("link", {
37-
...baseAttrs,
38-
href: this.cacheBusting(this.relative(name)),
39-
});
37+
return {
38+
tag: "link",
39+
attrs,
40+
};
4041
});
4142
}
4243
}

src/platforms/windows.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,29 @@ export class WindowsPlatform extends Platform {
4747
const tile = "mstile-144x144.png";
4848

4949
return [
50-
new FaviconHtmlTag("meta", {
51-
name: "msapplication-TileColor",
52-
content: this.options.background,
53-
}),
50+
{
51+
tag: "meta",
52+
attrs: {
53+
name: "msapplication-TileColor",
54+
content: this.options.background,
55+
},
56+
},
5457
this.iconOptions.find((iconOption) => iconOption.name === tile)
55-
? new FaviconHtmlTag("meta", {
56-
name: "msapplication-TileImage",
57-
content: this.cacheBusting(this.relative(tile)),
58-
})
58+
? {
59+
tag: "meta",
60+
attrs: {
61+
name: "msapplication-TileImage",
62+
content: this.cacheBusting(this.relative(tile)),
63+
},
64+
}
5965
: undefined,
60-
new FaviconHtmlTag("meta", {
61-
name: "msapplication-config",
62-
content: this.cacheBusting(this.relative(this.manifestFileName())),
63-
}),
66+
{
67+
tag: "meta",
68+
attrs: {
69+
name: "msapplication-config",
70+
content: this.cacheBusting(this.relative(this.manifestFileName())),
71+
},
72+
},
6473
];
6574
}
6675

src/platforms/yandex.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ export class YandexPlatform extends Platform {
2222

2323
override async createHtml(): Promise<FaviconHtmlTag[]> {
2424
return [
25-
new FaviconHtmlTag("link", {
26-
rel: "yandex-tableau-widget",
27-
href: this.cacheBusting(this.relative(this.manifestFileName())),
28-
}),
25+
{
26+
tag: "link",
27+
attrs: {
28+
rel: "yandex-tableau-widget",
29+
href: this.cacheBusting(this.relative(this.manifestFileName())),
30+
},
31+
},
2932
];
3033
}
3134

0 commit comments

Comments
 (0)