favicon-stealer 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,16 @@
1
1
  # favicon-stealer
2
- favicon stealer npm package
2
+ A react component to get clear and consistent favicon of a website easily.
3
3
 
4
4
 
5
5
  # installation
6
+ ## npm
6
7
  ```shell
7
8
  npm install favicon-stealer
8
9
  ```
10
+ ## pnpm
11
+ ```shell
12
+ pnpm add favicon-stealer
13
+ ```
9
14
 
10
15
  # usage
11
16
  ```typescript
@@ -20,22 +25,27 @@ import { Favicon } from 'favicon-stealer';
20
25
  | `url` | `string` | The URL of the website to fetch the favicon for. |
21
26
  | `size` | `number` | The size of the favicon in pixels. Default is 32. |
22
27
  | `className` | `string` | A class name to apply to the element. |
23
- | `timeout` | `number` | The timeout in milliseconds for fetching the favicon. Default is 1000 (1 second). |
28
+ | `timeout` | `number` | The timeout in milliseconds for fetching the favicon. Default is 3000 (3 seconds). |
24
29
  | `lazy` | `boolean` | Whether to load the favicon lazily. Default is false. |
25
30
  | `border` | `boolean` | Whether to show a border around the favicon. Default is false. |
26
31
  | `padding` | `number` | The padding in pixels.(px) Default is 0. |
27
32
  | `background` | `string` | The background color of the favicon. Default is transparent.(in hex) |
28
33
  | `borderRadius` | `number` | The border radius in pixels.(px) Default is 0. |
34
+ | `preferGoogle` | `boolean` | Whether to prefer Google's favicon service over the website's own favicon. Default is false. |
35
+
29
36
 
30
37
  # npm package
31
38
  https://www.npmjs.com/package/favicon-stealer
32
39
 
40
+
33
41
  # license
34
42
  MIT License
35
43
 
44
+
36
45
  # Changelog
37
46
  - v1.0.0: Initial release (2025.1.21)
38
47
  - v1.0.1: Add README.md (2025.1.21)
39
48
  - v1.0.2: Update license to MIT (2025.1.21)
40
49
  - v1.1.0: Fix show bug(2025.2.1)
41
- - v1.2.0: Add props(lazy, border, padding, background, borderRadius)(2025.2.1)
50
+ - v1.2.0: Add props(lazy, border, padding, background, borderRadius)(2025.2.1)
51
+ - v1.4.0: Improved favicon detection and optimized package size; Added preferGoogle option and improved favicon loading reliability (2025.2.27)
package/dist/Favicon.js CHANGED
@@ -36,25 +36,44 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
37
  const utils_1 = require("./lib/utils");
38
38
  const react_1 = __importStar(require("react"));
39
- const Favicon = ({ url, size = 32, className = "", timeout = 1000, // 1 second
40
- border = false, padding = 0, background = "transparent", borderRadius = 0, lazy = false, }) => {
39
+ const Favicon = ({ url, size = 32, className = "", timeout = 5000, // 增加到5秒,给网站自己的favicon更多加载时间
40
+ border = false, padding = 0, background = "transparent", borderRadius = 0, lazy = false, preferGoogle = false, }) => {
41
41
  const domain = (0, utils_1.getDomain)(url);
42
- const [imgSrc, setImgSrc] = (0, react_1.useState)(`https://${domain}/logo.svg`);
42
+ const [imgSrc, setImgSrc] = (0, react_1.useState)("");
43
43
  const [fallbackIndex, setFallbackIndex] = (0, react_1.useState)(0);
44
44
  const [isLoading, setIsLoading] = (0, react_1.useState)(true);
45
45
  const [hasError, setHasError] = (0, react_1.useState)(false);
46
- const fallbackSources = [
46
+ const [isInitialized, setIsInitialized] = (0, react_1.useState)(false);
47
+ const standardSources = [
48
+ `https://${domain}/favicon.ico`,
47
49
  `https://${domain}/logo.svg`,
48
50
  `https://${domain}/logo.png`,
49
51
  `https://${domain}/apple-touch-icon.png`,
50
52
  `https://${domain}/apple-touch-icon-precomposed.png`,
51
- `https://www.google.com/s2/favicons?domain=${domain}&sz=64`,
53
+ `https://${domain}/static/img/favicon.ico`,
54
+ `https://${domain}/static/img/favicon.png`,
55
+ `https://${domain}/img/favicon.png`,
56
+ `https://${domain}/img/favicon.ico`,
57
+ `https://${domain}/static/img/logo.svg`,
58
+ `https://${domain}/apple-touch-icon-precomposed.png`,
59
+ ];
60
+ const fallbackServices = [
61
+ `https://www.google.com/s2/favicons?domain=https://${domain}&sz=64`,
62
+ `https://www.google.com/s2/favicons?domain=http://${domain}&sz=64`,
52
63
  `https://icons.duckduckgo.com/ip3/${domain}.ico`,
53
- `https://${domain}/favicon.ico`,
54
64
  ];
65
+ const fallbackSources = preferGoogle
66
+ ? [...fallbackServices, ...standardSources]
67
+ : [...standardSources, ...fallbackServices];
68
+ (0, react_1.useEffect)(() => {
69
+ if (!isInitialized) {
70
+ setImgSrc(fallbackSources[0]);
71
+ setIsInitialized(true);
72
+ }
73
+ }, [isInitialized, fallbackSources]);
55
74
  (0, react_1.useEffect)(() => {
56
75
  let timeoutId;
57
- if (isLoading) {
76
+ if (isLoading && imgSrc) {
58
77
  timeoutId = setTimeout(() => {
59
78
  handleError();
60
79
  }, timeout);
@@ -64,7 +83,7 @@ border = false, padding = 0, background = "transparent", borderRadius = 0, lazy
64
83
  clearTimeout(timeoutId);
65
84
  }
66
85
  };
67
- }, [imgSrc, isLoading]);
86
+ }, [imgSrc, isLoading, timeout]);
68
87
  const handleError = () => {
69
88
  const nextIndex = fallbackIndex + 1;
70
89
  if (nextIndex < fallbackSources.length) {
@@ -96,10 +115,10 @@ border = false, padding = 0, background = "transparent", borderRadius = 0, lazy
96
115
  } },
97
116
  isLoading && (react_1.default.createElement("div", { className: "absolute inset-0 animate-pulse" },
98
117
  react_1.default.createElement("div", { className: "w-full h-full rounded-md bg-gray-200/60" }))),
99
- react_1.default.createElement("img", { src: imgSrc, alt: `${domain} logo`, width: size, height: size, loading: lazy ? "lazy" : "eager", onError: handleError, onLoad: handleLoad, className: `inline-block transition-opacity duration-300 ${isLoading ? "opacity-0" : "opacity-100"}`, style: {
118
+ imgSrc && (react_1.default.createElement("img", { src: imgSrc, alt: `${domain} logo`, width: size, height: size, loading: lazy ? "lazy" : "eager", onError: handleError, onLoad: handleLoad, className: `inline-block transition-opacity duration-300 ${isLoading ? "opacity-0" : "opacity-100"}`, style: {
100
119
  objectFit: "contain",
101
120
  display: hasError ? "none" : "inline-block",
102
- } }),
121
+ } })),
103
122
  hasError && (react_1.default.createElement("div", { className: "w-full h-full flex items-center justify-center bg-gray-100 rounded-md", style: { fontSize: `${size * 0.5}px` } }, domain.charAt(0).toUpperCase()))));
104
123
  };
105
124
  exports.default = Favicon;
@@ -9,6 +9,7 @@ interface IProps {
9
9
  background?: string;
10
10
  borderRadius?: number;
11
11
  lazy?: boolean;
12
+ preferGoogle?: boolean;
12
13
  }
13
- declare const Favicon: ({ url, size, className, timeout, border, padding, background, borderRadius, lazy, }: IProps) => React.JSX.Element;
14
+ declare const Favicon: ({ url, size, className, timeout, border, padding, background, borderRadius, lazy, preferGoogle, }: IProps) => React.JSX.Element;
14
15
  export default Favicon;
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "favicon-stealer",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/types/index.d.ts",
6
+ "files": [
7
+ "dist",
8
+ "LICENSE",
9
+ "README.md"
10
+ ],
6
11
  "scripts": {
7
12
  "test": "echo \"Error: no test specified\" && exit 1",
8
13
  "build": "tsc",
package/.gitattributes DELETED
@@ -1,2 +0,0 @@
1
- # Auto detect text files and perform LF normalization
2
- * text=auto
package/src/Favicon.tsx DELETED
@@ -1,134 +0,0 @@
1
- "use client";
2
-
3
- import { getDomain } from "./lib/utils";
4
- import React, { useEffect, useState } from "react";
5
-
6
- interface IProps {
7
- url: string;
8
- size?: number;
9
- className?: string;
10
- timeout?: number;
11
- border?: boolean;
12
- padding?: number;
13
- background?: string;
14
- borderRadius?: number;
15
- lazy?: boolean;
16
- }
17
-
18
- const Favicon = ({
19
- url,
20
- size = 32,
21
- className = "",
22
- timeout = 1000, // 1 second
23
- border = false,
24
- padding = 0,
25
- background = "transparent",
26
- borderRadius = 0,
27
- lazy = false,
28
- }: IProps) => {
29
- const domain = getDomain(url);
30
- const [imgSrc, setImgSrc] = useState(`https://${domain}/logo.svg`);
31
- const [fallbackIndex, setFallbackIndex] = useState(0);
32
- const [isLoading, setIsLoading] = useState(true);
33
- const [hasError, setHasError] = useState(false);
34
-
35
- const fallbackSources = [
36
- `https://${domain}/logo.svg`,
37
- `https://${domain}/logo.png`,
38
- `https://${domain}/apple-touch-icon.png`,
39
- `https://${domain}/apple-touch-icon-precomposed.png`,
40
- `https://www.google.com/s2/favicons?domain=${domain}&sz=64`,
41
- `https://icons.duckduckgo.com/ip3/${domain}.ico`,
42
- `https://${domain}/favicon.ico`,
43
- ];
44
-
45
- useEffect(() => {
46
- let timeoutId: any;
47
-
48
- if (isLoading) {
49
- timeoutId = setTimeout(() => {
50
- handleError();
51
- }, timeout);
52
- }
53
-
54
- return () => {
55
- if (timeoutId) {
56
- clearTimeout(timeoutId);
57
- }
58
- };
59
- }, [imgSrc, isLoading]);
60
-
61
- const handleError = () => {
62
- const nextIndex = fallbackIndex + 1;
63
- if (nextIndex < fallbackSources.length) {
64
- setFallbackIndex(nextIndex);
65
- setImgSrc(fallbackSources[nextIndex]);
66
- setIsLoading(true);
67
- } else {
68
- setHasError(true);
69
- setIsLoading(false);
70
- }
71
- };
72
-
73
- const handleLoad = () => {
74
- setIsLoading(false);
75
- setHasError(false);
76
- };
77
-
78
- return (
79
- <div
80
- className={
81
- `relative inline-block
82
- ${className}
83
- ${border ? "border" : ""}
84
- ${hasError ? "opacity-0" : ""}
85
- ${padding ? `p-[${padding}px]` : ""}
86
- ${borderRadius ? `rounded-[${borderRadius}px]` : ""}
87
- `
88
- }
89
- style={{
90
- width: size,
91
- height: size,
92
- background: background,
93
- padding: padding ? `${padding}px` : 0,
94
- borderRadius: borderRadius ? `${borderRadius}px` : 0,
95
- }}
96
- >
97
- {/* placeholder */}
98
- {isLoading && (
99
- <div className="absolute inset-0 animate-pulse">
100
- <div className="w-full h-full rounded-md bg-gray-200/60" />
101
- </div>
102
- )}
103
-
104
- <img
105
- src={imgSrc}
106
- alt={`${domain} logo`}
107
- width={size}
108
- height={size}
109
- loading={lazy ? "lazy" : "eager"}
110
- onError={handleError}
111
- onLoad={handleLoad}
112
- className={`inline-block transition-opacity duration-300 ${
113
- isLoading ? "opacity-0" : "opacity-100"
114
- }`}
115
- style={{
116
- objectFit: "contain",
117
- display: hasError ? "none" : "inline-block",
118
- }}
119
- />
120
-
121
- {/* Fallback: Display first letter of domain when all image sources fail */}
122
- {hasError && (
123
- <div
124
- className="w-full h-full flex items-center justify-center bg-gray-100 rounded-md"
125
- style={{ fontSize: `${size * 0.5}px` }}
126
- >
127
- {domain.charAt(0).toUpperCase()}
128
- </div>
129
- )}
130
- </div>
131
- );
132
- };
133
-
134
- export default Favicon;
package/src/index.tsx DELETED
@@ -1 +0,0 @@
1
- export { default as Favicon } from './Favicon';
@@ -1,12 +0,0 @@
1
- export const getDomain = (url: string) => {
2
- try {
3
- // Add https:// protocol if not present
4
- const urlWithProtocol = url.startsWith('http') ? url : `https://${url}`;
5
- const domain = new URL(urlWithProtocol).hostname;
6
- // Remove 'www.' prefix if exists
7
- return domain.replace(/^www\./, '');
8
- } catch (error) {
9
- // Return original input if URL parsing fails
10
- return url;
11
- }
12
- };
package/tsconfig.json DELETED
@@ -1,25 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "outDir": "dist",
4
- "module": "commonjs",
5
- "target": "es2015",
6
- "jsx": "react",
7
- "declaration": true,
8
- "declarationDir": "dist/types",
9
- "strict": true,
10
- "moduleResolution": "node",
11
- "baseUrl": "./src",
12
- "rootDir": "./src",
13
- "paths": {
14
- "@/*": ["./*"]
15
- },
16
- "esModuleInterop": true
17
- },
18
- "include": [
19
- "src"
20
- ],
21
- "exclude": [
22
- "node_modules",
23
- "dist"
24
- ]
25
- }