favicon-env 0.3.0 → 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 +35 -0
- package/package.json +16 -13
- package/skills/core/SKILL.md +63 -3
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ Tint your favicon per environment so you can tell instances apart at a glance
|
|
|
16
16
|
pnpm add favicon-env
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
> **`dependencies` or `devDependencies`?** Either works — every mode below resolves the import during your build, so nothing looks `favicon-env` up at runtime on the deployed host, staging included. `dependencies` is the safer default: npm reads `NODE_ENV=production` as `--omit=dev`, so a CI or PaaS builder with that set skips a devDependency and fails the build on `Cannot find module 'favicon-env'` (pnpm doesn't couple the two). `-D` is defensible if you only use [build-time mode](#build-time--ssr-mode) and want a clean production audit. Neither costs you bundle size — [it tree-shakes to zero bytes in prod](#zero-bytes-in-prod).
|
|
20
|
+
|
|
19
21
|
> **Using an AI coding agent?** Install the bundled [Agent Skill](https://tanstack.com/intent) with `pnpm dlx @tanstack/intent@latest install`.
|
|
20
22
|
|
|
21
23
|
## Runtime mode
|
|
@@ -543,6 +545,37 @@ For a zero-byte prod page, have the HTML build/template omit these scripts when
|
|
|
543
545
|
|
|
544
546
|
</details>
|
|
545
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
|
+
|
|
546
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:
|
|
547
580
|
|
|
548
581
|
```js
|
|
@@ -677,6 +710,8 @@ const pr = process.env.VERCEL_GIT_PULL_REQUEST_ID;
|
|
|
677
710
|
faviconDataUri(favicon, pr ? { badge: { text: `#${pr}` } } : { hue: 45 });
|
|
678
711
|
```
|
|
679
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
|
+
|
|
680
715
|
### Vite
|
|
681
716
|
|
|
682
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",
|
|
@@ -49,16 +49,16 @@
|
|
|
49
49
|
"access": "public"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@changesets/cli": "^
|
|
53
|
-
"@playwright/test": "^1.
|
|
54
|
-
"@swc/core": "^1.
|
|
55
|
-
"@tanstack/intent": "^0.3.
|
|
56
|
-
"dependency-cruiser": "^18.
|
|
57
|
-
"knip": "^6.
|
|
58
|
-
"oxfmt": "^0.
|
|
59
|
-
"oxlint": "^1.
|
|
60
|
-
"publint": "^0.3.
|
|
61
|
-
"simple-git-hooks": "^2.
|
|
52
|
+
"@changesets/cli": "^3.0.1",
|
|
53
|
+
"@playwright/test": "^1.62.1",
|
|
54
|
+
"@swc/core": "^1.16.1",
|
|
55
|
+
"@tanstack/intent": "^0.3.8",
|
|
56
|
+
"dependency-cruiser": "^18.2.0",
|
|
57
|
+
"knip": "^6.33.0",
|
|
58
|
+
"oxfmt": "^0.65.0",
|
|
59
|
+
"oxlint": "^1.80.0",
|
|
60
|
+
"publint": "^0.3.24",
|
|
61
|
+
"simple-git-hooks": "^2.14.0",
|
|
62
62
|
"tsup": "^8.5.1",
|
|
63
63
|
"typescript": "^7.0.2"
|
|
64
64
|
},
|
|
@@ -74,10 +74,13 @@
|
|
|
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",
|
|
81
|
+
"skill:sync": "node scripts/skill-version.mjs",
|
|
82
|
+
"skill:check": "node scripts/skill-version.mjs --check",
|
|
83
|
+
"skill:stale": "node scripts/skill-stale.mjs",
|
|
81
84
|
"pretest": "pnpm build",
|
|
82
85
|
"test": "node --test test/pure.test.mjs test/skill.test.mjs",
|
|
83
86
|
"test:skill": "node --test test/skill.test.mjs",
|
|
@@ -89,7 +92,7 @@
|
|
|
89
92
|
"test:all": "pnpm test && pnpm test:frameworks",
|
|
90
93
|
"ncu": "pnpm dlx npm-check-updates",
|
|
91
94
|
"changeset": "changeset",
|
|
92
|
-
"version": "changeset version",
|
|
95
|
+
"version": "changeset version && pnpm run skill:sync",
|
|
93
96
|
"release": "changeset publish"
|
|
94
97
|
}
|
|
95
98
|
}
|
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.2
|
|
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
|