favicon-env 0.3.1 → 0.3.2
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 +33 -0
- package/package.json +5 -5
- package/skills/core/SKILL.md +63 -3
package/README.md
CHANGED
|
@@ -545,6 +545,37 @@ For a zero-byte prod page, have the HTML build/template omit these scripts when
|
|
|
545
545
|
|
|
546
546
|
</details>
|
|
547
547
|
|
|
548
|
+
<details>
|
|
549
|
+
<summary><b>Non-JS backends</b> — Rails, Django, Laravel, Phoenix, Go, Rust</summary>
|
|
550
|
+
|
|
551
|
+
Runtime mode asks nothing of the server: it reads the page's `<link rel="icon">` (or falls back to `/favicon.ico`), redraws it on a canvas, and swaps the `href`. No bundler and no JavaScript framework are involved, so serve the global build from your static assets and let the template supply the environment:
|
|
552
|
+
|
|
553
|
+
```html
|
|
554
|
+
<link rel="icon" href="/favicon.svg" />
|
|
555
|
+
|
|
556
|
+
<!-- render these two tags only when the app environment is not `prod` -->
|
|
557
|
+
<script src="/static/favicon-env.global.js"></script>
|
|
558
|
+
<script>
|
|
559
|
+
void faviconEnv.envFavicon({
|
|
560
|
+
environments: {
|
|
561
|
+
dev: { tint: '#22c55e' },
|
|
562
|
+
staging: { badge: { text: 'S', color: '#f59e0b', shape: 'cover' } },
|
|
563
|
+
},
|
|
564
|
+
detect: () => 'staging', // interpolate the server's env value here
|
|
565
|
+
});
|
|
566
|
+
</script>
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Pass whatever the stack already knows — `Rails.env`, `settings.DEBUG`, `config('app.env')`, `Application.get_env(:my_app, :env)`, `os.Getenv("APP_ENV")` — through your template's normal escaping. Without it the hostname heuristic still applies, which is enough when your hosts follow the usual conventions.
|
|
570
|
+
|
|
571
|
+
Having the template omit those two tags in `prod` is the whole [zero-bytes-in-prod](#zero-bytes-in-prod) story here: nothing is served, so there is no bundler dead-code elimination to arrange. The `Plain HTML (global)` entry in the framework matrix exercises this exact shape — a server that renders the script conditionally and injects `detect` — so only the template language differs.
|
|
572
|
+
|
|
573
|
+
Add the head-observer pattern from the examples above only if something re-renders `<head>` after load: Turbo Drive, Inertia's head manager, `leptos_meta`. Plain server templates, htmx body swaps, and LiveView leave `<head>` alone, so a single call is enough. For a Rust/Wasm SPA (Leptos, Dioxus, Yew), the script tag in the `index.html` shell is all you need — no `wasm-bindgen` interop.
|
|
574
|
+
|
|
575
|
+
[Build-time mode](#build-time--ssr-mode) is the one piece that needs a JavaScript runtime; see the note there.
|
|
576
|
+
|
|
577
|
+
</details>
|
|
578
|
+
|
|
548
579
|
By default the environment is guessed from the hostname (`localhost` / `*.local` / raw IPs → `dev`; a `staging`/`preview`/`qa`/… segment → `staging`; everything else → `prod`). Override it:
|
|
549
580
|
|
|
550
581
|
```js
|
|
@@ -679,6 +710,8 @@ const pr = process.env.VERCEL_GIT_PULL_REQUEST_ID;
|
|
|
679
710
|
faviconDataUri(favicon, pr ? { badge: { text: `#${pr}` } } : { hue: 45 });
|
|
680
711
|
```
|
|
681
712
|
|
|
713
|
+
`favicon-env/ssr` is JavaScript, so a non-JS server can't call it in-process. Run it as a Node build step that writes one SVG per environment (`favicon.dev.svg`, `favicon.staging.svg`) and have the template pick one, or stay on runtime mode and accept its brief flash. `tintSvg` itself is plain string manipulation — a wrapping filtered `<g>` plus an optional badge `<g>` — if you would rather port it to the host language.
|
|
714
|
+
|
|
682
715
|
### Vite
|
|
683
716
|
|
|
684
717
|
This Vite plugin rewrites an SVG favicon using the current mode:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "favicon-env",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Tint your favicon per environment (dev/staging/prod) so you can tell instances apart at a glance. Runtime canvas mode + build-time SVG mode. Zero dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"canvas",
|
|
@@ -52,13 +52,13 @@
|
|
|
52
52
|
"@changesets/cli": "^3.0.1",
|
|
53
53
|
"@playwright/test": "^1.62.1",
|
|
54
54
|
"@swc/core": "^1.16.1",
|
|
55
|
-
"@tanstack/intent": "^0.3.
|
|
55
|
+
"@tanstack/intent": "^0.3.8",
|
|
56
56
|
"dependency-cruiser": "^18.2.0",
|
|
57
|
-
"knip": "^6.
|
|
57
|
+
"knip": "^6.33.0",
|
|
58
58
|
"oxfmt": "^0.65.0",
|
|
59
59
|
"oxlint": "^1.80.0",
|
|
60
60
|
"publint": "^0.3.24",
|
|
61
|
-
"simple-git-hooks": "^2.
|
|
61
|
+
"simple-git-hooks": "^2.14.0",
|
|
62
62
|
"tsup": "^8.5.1",
|
|
63
63
|
"typescript": "^7.0.2"
|
|
64
64
|
},
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"format": "oxfmt src",
|
|
75
75
|
"knip": "knip",
|
|
76
76
|
"depcruise": "depcruise src",
|
|
77
|
-
"check": "oxfmt --check src && oxlint src && knip && depcruise src",
|
|
77
|
+
"check": "oxfmt --check src && oxlint src && knip && depcruise src && pnpm run skill:stale && pnpm run skill:check",
|
|
78
78
|
"publint": "publint --strict",
|
|
79
79
|
"intent:validate": "intent validate skills",
|
|
80
80
|
"intent:stale": "intent stale skills",
|
package/skills/core/SKILL.md
CHANGED
|
@@ -5,12 +5,13 @@ description: >
|
|
|
5
5
|
identical tabs are distinguishable. Load when calling envFavicon, choosing
|
|
6
6
|
runtime (canvas) vs build-time (SSR) mode, adding hue/invert/filter tints or
|
|
7
7
|
badges/PR numbers, wiring it into Next.js/TanStack/Astro/SvelteKit/SolidStart/
|
|
8
|
-
Angular/Nuxt/Vite/plain HTML
|
|
9
|
-
|
|
8
|
+
Angular/Nuxt/Vite/plain HTML or a server-rendered non-JS backend
|
|
9
|
+
(Rails/Django/Laravel/Phoenix/Go/Rust), or configuring detect, environments,
|
|
10
|
+
rules, or auto mode.
|
|
10
11
|
metadata:
|
|
11
12
|
type: core
|
|
12
13
|
library: favicon-env
|
|
13
|
-
library_version: '0.3.
|
|
14
|
+
library_version: '0.3.2'
|
|
14
15
|
sources:
|
|
15
16
|
- 'Amir-Abushanab/favicon-env:README.md'
|
|
16
17
|
- 'Amir-Abushanab/favicon-env:src/tint.ts'
|
|
@@ -66,6 +67,7 @@ The environment name defaults to a `location.hostname` heuristic (`defaultDetect
|
|
|
66
67
|
| Nuxt | `app/plugins/favicon-env.client.ts`, using an explicit Vite build constant |
|
|
67
68
|
| Vite SPA | Client entry module (`src/main.ts`, etc.) |
|
|
68
69
|
| Plain HTML | Native module or the global build (`window.faviconEnv`) |
|
|
70
|
+
| Non-JS backend | Global build `<script>` in the server template; `detect` from the injected env |
|
|
69
71
|
|
|
70
72
|
For any SSR router/head manager that declares the favicon itself, wrap the call in
|
|
71
73
|
the head-observer pattern below. Hydration or navigation can otherwise restore its
|
|
@@ -81,6 +83,9 @@ are folded to literals. SvelteKit and Nuxt may preserve their normal public runt
|
|
|
81
83
|
configuration, so define a dedicated constant through Vite's `define` option and use
|
|
82
84
|
that in the guard.
|
|
83
85
|
|
|
86
|
+
In a server-rendered template there is no bundler step to arrange: render the
|
|
87
|
+
`<script>` tags only when the environment is not `prod` and prod ships nothing.
|
|
88
|
+
|
|
84
89
|
Angular's application builder can emit a lazy chunk even behind a false `define`
|
|
85
90
|
guard. Put the dynamic import in a local loader module and replace that module with a
|
|
86
91
|
typed no-op in the prod build using Angular `fileReplacements`. For plain HTML, omit
|
|
@@ -130,6 +135,32 @@ void envFavicon({ auto: true });
|
|
|
130
135
|
Derives a deterministic hue from `location.host`, so every origin and port gets its
|
|
131
136
|
own colour — handy for telling several dev servers apart.
|
|
132
137
|
|
|
138
|
+
### Server-rendered templates — the global build, no bundler
|
|
139
|
+
|
|
140
|
+
```html
|
|
141
|
+
<link rel="icon" href="/favicon.svg" />
|
|
142
|
+
|
|
143
|
+
<!-- render these two tags only when the app environment is not prod -->
|
|
144
|
+
<script src="/static/favicon-env.global.js"></script>
|
|
145
|
+
<script>
|
|
146
|
+
void faviconEnv.envFavicon({
|
|
147
|
+
environments: { dev: { tint: '#22c55e' }, staging: { badge: '#f59e0b' } },
|
|
148
|
+
detect: () => 'staging', // interpolate Rails.env / config('app.env') / os.Getenv("APP_ENV")
|
|
149
|
+
});
|
|
150
|
+
</script>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Runtime mode needs only a browser — it reads `link[rel~="icon"]` (falling back to
|
|
154
|
+
`/favicon.ico`), redraws on canvas, and swaps the `href` — so Rails, Django, Laravel,
|
|
155
|
+
Phoenix, Go, and Rust/Wasm apps serve `dist/favicon-env.global.js` from their static
|
|
156
|
+
assets and let the template inject `detect`. Omitting the two tags in prod is the
|
|
157
|
+
whole zero-bytes story there. Add the head observer only where something re-renders
|
|
158
|
+
`<head>` (Turbo Drive, Inertia, `leptos_meta`); plain templates, htmx body swaps, and
|
|
159
|
+
LiveView leave it alone. For a Rust/Wasm SPA the tag in the `index.html` shell is
|
|
160
|
+
enough — no `wasm-bindgen` interop. `favicon-env/ssr` is the one JS-only entry point:
|
|
161
|
+
run it as a Node build step emitting one SVG per environment if the no-flash path
|
|
162
|
+
matters.
|
|
163
|
+
|
|
133
164
|
### Build-time SSR — no first-paint flash
|
|
134
165
|
|
|
135
166
|
```js
|
|
@@ -355,3 +386,32 @@ const href = document.querySelector('link[rel~="icon"]').href;
|
|
|
355
386
|
redrawing; the `<link>` is not replaced until it resolves.
|
|
356
387
|
|
|
357
388
|
Source: src/tint.ts (returns `Promise`, `img` load listener)
|
|
389
|
+
|
|
390
|
+
### MEDIUM — Expecting the global build's `data-*` attributes to carry the environment
|
|
391
|
+
|
|
392
|
+
Wrong:
|
|
393
|
+
|
|
394
|
+
```html
|
|
395
|
+
<!-- beta.acme.internal — no `staging`/`qa`/… segment, so the heuristic says `prod` -->
|
|
396
|
+
<script src="/static/favicon-env.global.js" data-staging="45"></script>
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Correct:
|
|
400
|
+
|
|
401
|
+
```html
|
|
402
|
+
<script src="/static/favicon-env.global.js"></script>
|
|
403
|
+
<script>
|
|
404
|
+
void faviconEnv.envFavicon({
|
|
405
|
+
environments: { staging: { hue: 45 } },
|
|
406
|
+
detect: () => 'staging', // filled in by the server template
|
|
407
|
+
});
|
|
408
|
+
</script>
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
`data-<env>="<degrees>"` only declares hues (non-numeric values are dropped, and any
|
|
412
|
+
other numeric `data-*` becomes an environment); the current env is still resolved by
|
|
413
|
+
`defaultDetect`. When the host doesn't match that heuristic, call `envFavicon`
|
|
414
|
+
explicitly with a `detect` the server fills in. `data-auto` is the exception: it
|
|
415
|
+
ignores `environments` and hashes `location.host`.
|
|
416
|
+
|
|
417
|
+
Source: src/global.ts (boot), src/detect.ts
|