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.
@@ -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)