favicon-env 0.1.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/LICENSE +21 -0
- package/README.md +314 -0
- package/dist/chunk-NRMMGQRU.js +146 -0
- package/dist/chunk-NRMMGQRU.js.map +1 -0
- package/dist/favicon-env.global.js +2 -0
- package/dist/favicon-env.global.js.map +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +229 -0
- package/dist/index.js.map +1 -0
- package/dist/ssr-CI5GvAY9.d.ts +122 -0
- package/dist/ssr.d.ts +1 -0
- package/dist/ssr.js +3 -0
- package/dist/ssr.js.map +1 -0
- package/package.json +84 -0
- package/skills/core/SKILL.md +246 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: core
|
|
3
|
+
description: >
|
|
4
|
+
favicon-env tints the browser favicon per environment (dev/staging/prod) so
|
|
5
|
+
identical tabs are distinguishable. Load when calling envFavicon, choosing
|
|
6
|
+
runtime (canvas) vs build-time (SSR) mode, adding hue/filter tints or
|
|
7
|
+
badges/PR numbers, wiring it into Next.js/TanStack/Astro/Vite, or configuring
|
|
8
|
+
detect, environments, rules, or auto mode.
|
|
9
|
+
metadata:
|
|
10
|
+
type: core
|
|
11
|
+
library: favicon-env
|
|
12
|
+
library_version: '0.1.0'
|
|
13
|
+
sources:
|
|
14
|
+
- 'Amir-Abushanab/favicon-env:README.md'
|
|
15
|
+
- 'Amir-Abushanab/favicon-env:src/tint.ts'
|
|
16
|
+
- 'Amir-Abushanab/favicon-env:src/ssr.ts'
|
|
17
|
+
- 'Amir-Abushanab/favicon-env:src/detect.ts'
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# favicon-env
|
|
21
|
+
|
|
22
|
+
Two modes ship as separate entry points. `favicon-env` (runtime) redraws the
|
|
23
|
+
page's favicon on a `<canvas>` in the browser — works with any existing favicon
|
|
24
|
+
(svg/png/ico), but briefly shows the untinted icon until JS runs. `favicon-env/ssr`
|
|
25
|
+
(build-time) bakes the tint into an SVG string with no first-paint flash — use it
|
|
26
|
+
when you control the favicon SVG.
|
|
27
|
+
|
|
28
|
+
## Setup
|
|
29
|
+
|
|
30
|
+
Runtime — call once on the client, as early as possible:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import { envFavicon } from 'favicon-env'
|
|
34
|
+
|
|
35
|
+
void envFavicon({
|
|
36
|
+
environments: {
|
|
37
|
+
dev: { hue: 130 }, // hue-rotate degrees
|
|
38
|
+
staging: { badge: '#f59e0b' }, // a corner dot; keeps the logo intact
|
|
39
|
+
// prod omitted → favicon left untouched
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The environment name defaults to a `location.hostname` heuristic (`defaultDetect`):
|
|
45
|
+
`localhost`/`*.local`/raw IPs → `dev`; a `staging`/`preview`/`qa`/`uat`/… segment →
|
|
46
|
+
`staging`; everything else → `prod`.
|
|
47
|
+
|
|
48
|
+
## Core Patterns
|
|
49
|
+
|
|
50
|
+
### Custom environments — any name, but supply a matching `detect`
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
void envFavicon({
|
|
54
|
+
environments: { canary: { hue: 280 }, demo: { badge: '#22c55e' } },
|
|
55
|
+
detect: () => {
|
|
56
|
+
if (location.hostname.startsWith('canary.')) return 'canary'
|
|
57
|
+
if (location.hostname.endsWith('.demo.acme.com')) return 'demo'
|
|
58
|
+
return 'prod' // not in the map → untouched
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`environments` keys are arbitrary strings, but `defaultDetect` only ever returns
|
|
64
|
+
`dev`/`staging`/`prod`, so any other key needs a custom `detect`.
|
|
65
|
+
|
|
66
|
+
### URL rules — stamp the PR number on preview deploys
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
void envFavicon({
|
|
70
|
+
rules: [
|
|
71
|
+
// pr-344.myapp.dev → a "#344" tile; $1 is the regex capture
|
|
72
|
+
{ match: /^pr-(\d+)\./, badge: { text: '#$1', color: '#8b5cf6', shape: 'cover' } },
|
|
73
|
+
{ match: /staging\./, hue: 45 },
|
|
74
|
+
],
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`match` is a `RegExp` tested against `location.host` (includes `:port`) or a
|
|
79
|
+
`(url: URL) => boolean`. Rules are checked first, in order; first match wins, then
|
|
80
|
+
it falls through to `auto`/`environments`.
|
|
81
|
+
|
|
82
|
+
### Auto mode — one stable colour per host, zero config
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
void envFavicon({ auto: true })
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Derives a deterministic hue from `location.host`, so every origin and port gets its
|
|
89
|
+
own colour — handy for telling several dev servers apart.
|
|
90
|
+
|
|
91
|
+
### Build-time SSR — no first-paint flash
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
import { faviconDataUri } from 'favicon-env/ssr'
|
|
95
|
+
import favicon from './favicon.svg?raw'
|
|
96
|
+
|
|
97
|
+
// during an Astro/Vite build; pick the tint for the current env
|
|
98
|
+
const href = faviconDataUri(favicon, { hue: 130 })
|
|
99
|
+
// → render into <link rel="icon" type="image/svg+xml" href={href}>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`favicon-env/ssr` is pure string manipulation (no DOM), safe in Node. It bakes a
|
|
103
|
+
CSS `filter` and/or badge into the SVG using its `viewBox`.
|
|
104
|
+
|
|
105
|
+
## Common Mistakes
|
|
106
|
+
|
|
107
|
+
### HIGH — Custom env name with no matching detect
|
|
108
|
+
|
|
109
|
+
Wrong:
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
void envFavicon({ environments: { canary: { hue: 280 } } })
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Correct:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
void envFavicon({
|
|
119
|
+
environments: { canary: { hue: 280 } },
|
|
120
|
+
detect: () => (location.hostname.startsWith('canary.') ? 'canary' : 'prod'),
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`defaultDetect` only returns `dev`/`staging`/`prod`, so `environments.canary` is
|
|
125
|
+
never looked up and the favicon is left untouched — no error is thrown.
|
|
126
|
+
|
|
127
|
+
Source: src/detect.ts, src/tint.ts (resolveTint)
|
|
128
|
+
|
|
129
|
+
### HIGH — Calling it from a Server Component / during SSR
|
|
130
|
+
|
|
131
|
+
Wrong:
|
|
132
|
+
|
|
133
|
+
```jsx
|
|
134
|
+
// app/page.tsx — a Next.js App Router Server Component
|
|
135
|
+
import { envFavicon } from 'favicon-env'
|
|
136
|
+
envFavicon({ environments: { dev: { hue: 130 } } }) // runs on the server
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Correct:
|
|
140
|
+
|
|
141
|
+
```jsx
|
|
142
|
+
'use client'
|
|
143
|
+
import { useEffect } from 'react'
|
|
144
|
+
import { envFavicon } from 'favicon-env'
|
|
145
|
+
|
|
146
|
+
export function FaviconEnv() {
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
void envFavicon({ environments: { dev: { hue: 130 } } })
|
|
149
|
+
}, [])
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Runtime mode needs `document`; on the server it is a no-op and never tints the
|
|
155
|
+
client. Use `useEffect` in a client component (Next App Router), or call it at a
|
|
156
|
+
client-entry module (Vite/TanStack `src/main.ts`, or an Astro `<script>`).
|
|
157
|
+
|
|
158
|
+
Source: src/tint.ts (`typeof document` guard), README "Runtime mode"
|
|
159
|
+
|
|
160
|
+
### MEDIUM — Multi-digit badge as a corner pill
|
|
161
|
+
|
|
162
|
+
Wrong:
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
void envFavicon({ environments: { preview: { badge: { text: '#344' } } } })
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Correct:
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
void envFavicon({
|
|
172
|
+
environments: { preview: { badge: { text: '#344', shape: 'cover' } } },
|
|
173
|
+
})
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The default `pill` badge sits in a corner at ~half the icon, so a 3–4 digit number
|
|
177
|
+
is illegible at 16px. `shape: 'cover'` fills the whole icon with the number.
|
|
178
|
+
|
|
179
|
+
Source: src/tint.ts (drawBadge), README "Badges"
|
|
180
|
+
|
|
181
|
+
### MEDIUM — Cross-origin favicon source without CORS
|
|
182
|
+
|
|
183
|
+
Wrong:
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
void envFavicon({
|
|
187
|
+
source: 'https://cdn.example.com/favicon.png', // served without CORS headers
|
|
188
|
+
environments: { dev: { hue: 130 } },
|
|
189
|
+
})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Correct:
|
|
193
|
+
|
|
194
|
+
```js
|
|
195
|
+
// serve the favicon same-origin, or with Access-Control-Allow-Origin
|
|
196
|
+
void envFavicon({ source: '/favicon.png', environments: { dev: { hue: 130 } } })
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Tinting draws to a canvas; a cross-origin image without CORS taints it, so
|
|
200
|
+
`envFavicon` catches the error and leaves the favicon untouched — silently.
|
|
201
|
+
|
|
202
|
+
Source: src/tint.ts (`img.crossOrigin`, tainted-canvas catch)
|
|
203
|
+
|
|
204
|
+
### MEDIUM — Expecting runtime mode to avoid the first-paint flash
|
|
205
|
+
|
|
206
|
+
Wrong:
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
// runtime mode always shows the untinted icon until JS runs
|
|
210
|
+
import { envFavicon } from 'favicon-env'
|
|
211
|
+
void envFavicon({ environments: { dev: { hue: 130 } } })
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Correct:
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
// bake the tint into the initial HTML at build time — no flash
|
|
218
|
+
import { faviconDataUri } from 'favicon-env/ssr'
|
|
219
|
+
const href = faviconDataUri(faviconSvg, { hue: 130 })
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`envFavicon` runs after first paint, so the original icon flashes briefly. When
|
|
223
|
+
that matters, render the SSR helper's output into the initial `<link rel="icon">`.
|
|
224
|
+
|
|
225
|
+
Source: README "First-paint flash", src/ssr.ts
|
|
226
|
+
|
|
227
|
+
### MEDIUM — Treating envFavicon as synchronous
|
|
228
|
+
|
|
229
|
+
Wrong:
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
envFavicon({ environments: { dev: { hue: 130 } } })
|
|
233
|
+
const href = document.querySelector('link[rel~="icon"]').href // old icon — not swapped yet
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Correct:
|
|
237
|
+
|
|
238
|
+
```js
|
|
239
|
+
await envFavicon({ environments: { dev: { hue: 130 } } })
|
|
240
|
+
const href = document.querySelector('link[rel~="icon"]').href
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`envFavicon` returns a `Promise` and loads the base image asynchronously before
|
|
244
|
+
redrawing; the `<link>` is not replaced until it resolves.
|
|
245
|
+
|
|
246
|
+
Source: src/tint.ts (returns `Promise`, `img` load listener)
|