@mannisto/astro-metadata 1.0.0-alpha.2 → 1.0.0-alpha.4

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,6 +1,6 @@
1
1
  # Astro Metadata
2
2
 
3
- ![banner](./assets/default/banner.png)
3
+ ![banner](./assets/banner.png)
4
4
 
5
5
  ![npm version](https://img.shields.io/npm/v/@mannisto/astro-metadata)
6
6
  ![license](https://img.shields.io/badge/license-MIT-green)
@@ -8,15 +8,13 @@
8
8
 
9
9
  Astro components for managing your page head — metadata, social sharing, favicons, and SEO.
10
10
 
11
- ---
12
-
13
11
  ## Table of contents
14
12
 
15
13
  - [Installation](#installation)
16
- - [Patterns](#patterns)
17
- - [1. Head component](#1-head-component)
18
- - [2. Individual components](#2-individual-components)
19
- - [3. Metadata utility](#3-metadata-utility)
14
+ - [Usage](#usage)
15
+ - [Head component](#1-head-component)
16
+ - [Individual components](#2-individual-components)
17
+ - [Metadata utility](#3-metadata-utility)
20
18
  - [Components](#components)
21
19
  - [Canonical](#canonical)
22
20
  - [Description](#description)
@@ -31,16 +29,19 @@ Astro components for managing your page head — metadata, social sharing, favic
31
29
  - [Twitter](#twitter)
32
30
  - [License](#license)
33
31
 
34
- ---
35
-
36
32
  ## Installation
37
33
  ```bash
34
+ # pnpm
38
35
  pnpm add @mannisto/astro-metadata
39
- ```
40
36
 
41
- ---
37
+ # npm
38
+ npm install @mannisto/astro-metadata
39
+
40
+ # yarn
41
+ yarn add @mannisto/astro-metadata
42
+ ```
42
43
 
43
- ## Patterns
44
+ ## Usage
44
45
 
45
46
  There are three ways to use this package. Pick what suits your project, or combine them freely.
46
47
 
@@ -110,12 +111,10 @@ import { Title, Description, OpenGraph, Favicon } from "@mannisto/astro-metadata
110
111
  }}
111
112
  />
112
113
  <Favicon
113
- icons={{
114
- default: {
115
- ico: { path: "/favicon.ico" },
116
- svg: { path: "/favicon.svg" },
117
- },
118
- }}
114
+ icons={[
115
+ { path: "/favicon.ico" },
116
+ { path: "/favicon.svg" },
117
+ ]}
119
118
  />
120
119
  </head>
121
120
  <body>
@@ -181,7 +180,8 @@ Best for sites with deeply nested layouts, or when you want to keep metadata co-
181
180
 
182
181
  ## Components
183
182
 
184
- ### Canonical
183
+ <details>
184
+ <summary><strong>Canonical</strong></summary>
185
185
 
186
186
  Renders a canonical link tag. Falls back to `Astro.url.href` when no value is provided, so every page gets a canonical tag with zero configuration.
187
187
  ```astro
@@ -192,9 +192,12 @@ Renders a canonical link tag. Falls back to `Astro.url.href` when no value is pr
192
192
  |------|------|-------------|
193
193
  | `value` | `string` | Canonical URL. Defaults to `Astro.url.href`. |
194
194
 
195
+ </details>
196
+
195
197
  ---
196
198
 
197
- ### Description
199
+ <details>
200
+ <summary><strong>Description</strong></summary>
198
201
  ```astro
199
202
  <Description value="Welcome to my site" />
200
203
  ```
@@ -203,59 +206,48 @@ Renders a canonical link tag. Falls back to `Astro.url.href` when no value is pr
203
206
  |------|------|-------------|
204
207
  | `value` | `string` | Page description |
205
208
 
209
+ </details>
210
+
206
211
  ---
207
212
 
208
- ### Favicon
213
+ <details>
214
+ <summary><strong>Favicon</strong></summary>
209
215
 
210
- Favicon support with dark and light mode variants, multiple formats, and optional cache busting.
216
+ Favicon support with light and dark mode variants and automatic MIME type detection.
211
217
  ```astro
212
218
  <Favicon
213
- icons={{
214
- default: {
215
- ico: { path: "/favicon.ico" },
216
- svg: { path: "/favicon.svg" },
217
- png: [{ path: "/favicon-96x96.png", size: 96 }],
218
- apple: { path: "/apple-touch-icon.png", size: 180 },
219
- },
220
- lightMode: {
221
- svg: { path: "/favicon-light.svg" },
222
- },
223
- darkMode: {
224
- svg: { path: "/favicon-dark.svg" },
225
- },
226
- }}
219
+ icons={[
220
+ { path: "/favicon.ico" },
221
+ { path: "/favicon.svg" },
222
+ { path: "/favicon-96x96.png", size: 96 },
223
+ { path: "/apple-touch-icon.png", size: 180, apple: true },
224
+ { path: "/favicon-dark.svg", theme: "dark" },
225
+ { path: "/favicon-light.svg", theme: "light" },
226
+ ]}
227
227
  manifest="/site.webmanifest"
228
- cacheBust
229
228
  />
230
229
  ```
231
230
 
232
231
  | Prop | Type | Description |
233
232
  |------|------|-------------|
234
- | `icons.default` | `FaviconIcons` | Default favicon set |
235
- | `icons.lightMode` | `FaviconIcons` | Favicons shown in light color scheme |
236
- | `icons.darkMode` | `FaviconIcons` | Favicons shown in dark color scheme |
233
+ | `icons` | `FaviconFile[]` | List of favicon files |
237
234
  | `manifest` | `string` | Path to web app manifest |
238
- | `cacheBust` | `boolean` | Append `?v={timestamp}` to favicon URLs |
239
-
240
- #### FaviconIcons
241
-
242
- | Prop | Type | Description |
243
- |------|------|-------------|
244
- | `ico` | `FaviconFile` | `.ico` favicon |
245
- | `svg` | `FaviconFile` | `.svg` favicon |
246
- | `png` | `FaviconFile \| FaviconFile[]` | One or more `.png` favicons |
247
- | `apple` | `FaviconFile` | Apple touch icon |
248
235
 
249
236
  #### FaviconFile
250
237
 
251
238
  | Prop | Type | Description |
252
239
  |------|------|-------------|
253
- | `path` | `string` | Path to the file |
240
+ | `path` | `string` | Path to the file. MIME type is detected automatically. |
254
241
  | `size` | `number` | Size in pixels. Rendered as `NxN` in the `sizes` attribute. |
242
+ | `theme` | `"light" \| "dark"` | Adds a `prefers-color-scheme` media query |
243
+ | `apple` | `boolean` | Renders as `<link rel="apple-touch-icon">` |
244
+
245
+ </details>
255
246
 
256
247
  ---
257
248
 
258
- ### Head
249
+ <details>
250
+ <summary><strong>Head</strong></summary>
259
251
 
260
252
  Wraps the entire page head and composes all sub-components internally. Charset and viewport are always included and can be overridden if needed.
261
253
  ```astro
@@ -272,11 +264,10 @@ Wraps the entire page head and composes all sub-components internally. Charset a
272
264
  },
273
265
  }}
274
266
  favicon={{
275
- icons: {
276
- default: {
277
- ico: { path: "/favicon.ico" },
278
- },
279
- },
267
+ icons: [
268
+ { path: "/favicon.ico" },
269
+ { path: "/favicon.svg" },
270
+ ],
280
271
  }}
281
272
  />
282
273
  ```
@@ -308,9 +299,12 @@ Wraps the entire page head and composes all sub-components internally. Charset a
308
299
  </Head>
309
300
  ```
310
301
 
302
+ </details>
303
+
311
304
  ---
312
305
 
313
- ### Keywords
306
+ <details>
307
+ <summary><strong>Keywords</strong></summary>
314
308
  ```astro
315
309
  <Keywords value={["astro", "seo", "metadata"]} />
316
310
  ```
@@ -319,9 +313,12 @@ Wraps the entire page head and composes all sub-components internally. Charset a
319
313
  |------|------|-------------|
320
314
  | `value` | `string[]` | List of keywords |
321
315
 
316
+ </details>
317
+
322
318
  ---
323
319
 
324
- ### LanguageAlternates
320
+ <details>
321
+ <summary><strong>LanguageAlternates</strong></summary>
325
322
 
326
323
  Renders `<link rel="alternate" hreflang>` tags for multilingual sites. Tells search engines which language version to serve for a given region.
327
324
  ```astro
@@ -340,9 +337,12 @@ Renders `<link rel="alternate" hreflang>` tags for multilingual sites. Tells sea
340
337
  | `alternates[].href` | `string` | Full URL of the alternate page |
341
338
  | `alternates[].hreflang` | `string` | Language or region code, e.g. `en`, `fi`, `en-US`, `x-default` |
342
339
 
340
+ </details>
341
+
343
342
  ---
344
343
 
345
- ### OpenGraph
344
+ <details>
345
+ <summary><strong>OpenGraph</strong></summary>
346
346
 
347
347
  Renders Open Graph meta tags for rich previews when your pages are shared on social platforms. When used inside `Head`, `title`, `description` and `url` fall back to the page values automatically.
348
348
  ```astro
@@ -374,9 +374,12 @@ Renders Open Graph meta tags for rich previews when your pages are shared on soc
374
374
  | `siteName` | `string` | — | Name of the site |
375
375
  | `locale` | `string` | — | Locale, e.g. `en_US` |
376
376
 
377
+ </details>
378
+
377
379
  ---
378
380
 
379
- ### Robots
381
+ <details>
382
+ <summary><strong>Robots</strong></summary>
380
383
 
381
384
  Controls how search engines crawl and index your page. Defaults to `index, follow`.
382
385
  ```astro
@@ -394,9 +397,12 @@ Controls how search engines crawl and index your page. Defaults to `index, follo
394
397
  | `noSnippet` | `boolean` | — | Prevent text snippets in search results |
395
398
  | `extra` | `string` | — | Additional directives, e.g. `"max-snippet:-1, max-image-preview:large"` |
396
399
 
400
+ </details>
401
+
397
402
  ---
398
403
 
399
- ### Schema
404
+ <details>
405
+ <summary><strong>Schema</strong></summary>
400
406
 
401
407
  Outputs a `<script type="application/ld+json">` tag for structured data. Use it to help search engines understand your content and qualify for rich results.
402
408
  ```astro
@@ -414,14 +420,16 @@ Outputs a `<script type="application/ld+json">` tag for structured data. Use it
414
420
  |------|------|-------------|
415
421
  | `schema` | `Record<string, unknown>` | JSON-LD object |
416
422
 
423
+ </details>
424
+
417
425
  ---
418
426
 
419
- ### Title
427
+ <details>
428
+ <summary><strong>Title</strong></summary>
420
429
 
421
430
  Renders the `<title>` tag. The template must contain `%s`, which is replaced with the page title — TypeScript enforces this at the type level.
422
431
  ```astro
423
432
  <Title value="My Page" template="%s | My Site" />
424
- <!-- <title>My Page | My Site</title> -->
425
433
  ```
426
434
 
427
435
  | Prop | Type | Description |
@@ -429,9 +437,12 @@ Renders the `<title>` tag. The template must contain `%s`, which is replaced wit
429
437
  | `value` | `string` | Page title. Required. |
430
438
  | `template` | `` `${string}%s${string}` `` | Template string. Must contain `%s`. |
431
439
 
440
+ </details>
441
+
432
442
  ---
433
443
 
434
- ### Twitter
444
+ <details>
445
+ <summary><strong>Twitter</strong></summary>
435
446
 
436
447
  Renders Twitter card meta tags for rich previews on X. When used inside `Head`, `title` and `description` fall back to the page values automatically.
437
448
  ```astro
@@ -456,6 +467,8 @@ Renders Twitter card meta tags for rich previews on X. When used inside `Head`,
456
467
  | `site` | `string` | — | Twitter handle of the site, e.g. `@mysite` |
457
468
  | `creator` | `string` | — | Twitter handle of the content author |
458
469
 
470
+ </details>
471
+
459
472
  ---
460
473
 
461
474
  ## License
package/index.ts CHANGED
@@ -20,8 +20,7 @@ export type { Props as RobotsProps } from "./src/components/Robots.a
20
20
  export type { Props as OpenGraphProps } from "./src/components/OpenGraph.astro"
21
21
  export type { Props as TwitterProps } from "./src/components/Twitter.astro"
22
22
  export type { Props as FaviconProps,
23
- FaviconFile,
24
- FaviconIcons } from "./src/components/Favicon.astro"
23
+ FaviconFile } from "./src/components/Favicon.astro"
25
24
  export type { Props as SchemaProps } from "./src/components/Schema.astro"
26
25
  export type { Props as LanguageAlternatesProps,
27
26
  LanguageAlternate } from "./src/components/LanguageAlternates.astro"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mannisto/astro-metadata",
3
- "version": "1.0.0-alpha.2",
3
+ "version": "1.0.0-alpha.4",
4
4
  "type": "module",
5
5
  "description": "Astro components for managing your page head — metadata, social sharing, favicons, and SEO.",
6
6
  "license": "MIT",
@@ -1,177 +1,80 @@
1
1
  ---
2
2
 
3
3
  export type FaviconFile = {
4
- path : string
5
- size? : number
6
- }
7
-
8
- export type FaviconIcons = {
9
- ico? : FaviconFile
10
- png? : FaviconFile | FaviconFile[]
11
- svg? : FaviconFile
12
- apple? : FaviconFile
13
- }
14
-
15
- type PreparedFile = {
16
- path : string
17
- size? : string
18
- }
19
-
20
- type PreparedIcons = {
21
- ico? : PreparedFile
22
- png? : PreparedFile[]
23
- svg? : PreparedFile
24
- apple? : PreparedFile
4
+ path : string
5
+ size? : number
6
+ theme? : "light" | "dark"
7
+ apple? : boolean
25
8
  }
26
9
 
27
10
  export type Props = {
28
- icons: {
29
- default? : FaviconIcons
30
- lightMode? : FaviconIcons
31
- darkMode? : FaviconIcons
32
- }
33
- manifest? : string
34
- cacheBust? : boolean
11
+ icons : FaviconFile[]
12
+ manifest? : string
35
13
  }
36
14
 
37
- const {
38
- icons,
39
- manifest,
40
- cacheBust = false
15
+ const {
16
+ icons,
17
+ manifest
41
18
  } = Astro.props
42
19
 
43
- const cacheBustString = cacheBust
44
- ? `?v=${Date.now()}`
45
- : ""
46
-
47
20
  /**
48
- * Prepares a single favicon file by appending the cache bust string
49
- * and converting the numeric size to the "NxN" format browsers expect.
21
+ * Detects the MIME type of a favicon file from its extension.
50
22
  *
51
- * @param file - The favicon file to prepare.
52
- * @returns The prepared favicon file, or undefined if no file was provided.
23
+ * @param path - The path to the favicon file.
24
+ * @returns The MIME type of the file.
53
25
  */
54
- function prepareFile(file?: FaviconFile): PreparedFile | undefined {
55
- if (!file) return undefined
56
- return {
57
- path: `${file.path}${cacheBustString}`,
58
- size: file.size ? `${file.size}x${file.size}` : undefined,
59
- }
26
+ function getMimeType(path: string): string {
27
+ if (path.endsWith(".svg")) return "image/svg+xml"
28
+ if (path.endsWith(".png")) return "image/png"
29
+ if (path.endsWith(".webp")) return "image/webp"
30
+ if (path.endsWith(".jpg") ||
31
+ path.endsWith(".jpeg")) return "image/jpeg"
32
+ if (path.endsWith(".ico")) return "image/x-icon"
33
+ return "image/x-icon"
60
34
  }
61
35
 
62
36
  /**
63
- * Prepares a full set of favicon icons by running each format through prepareFile.
64
- * Normalizes the png field to always be an array for consistent rendering.
37
+ * Builds the media query string for a given theme.
65
38
  *
66
- * @param set - The favicon icon set to prepare.
67
- * @returns The prepared favicon icon set, or undefined if no set was provided.
39
+ * @param theme - The theme to build the media query for.
40
+ * @returns The media query string, or undefined if no theme was provided.
68
41
  */
69
- function prepareIcons(set?: FaviconIcons): PreparedIcons | undefined {
70
- if (!set) return undefined
71
- return {
72
- ico: prepareFile(set.ico),
73
- svg: prepareFile(set.svg),
74
- apple: prepareFile(set.apple),
75
- png: set.png
76
- ? (Array.isArray(set.png) ? set.png : [set.png]).map((file) => prepareFile(file)!)
77
- : undefined,
78
- }
42
+ function getMedia(theme?: "light" | "dark"): string | undefined {
43
+ if (theme === "light") return "(prefers-color-scheme: light)"
44
+ if (theme === "dark") return "(prefers-color-scheme: dark)"
45
+ return undefined
79
46
  }
80
47
 
81
- const prepared = {
82
- default: prepareIcons(icons.default),
83
- lightMode: prepareIcons(icons.lightMode),
84
- darkMode: prepareIcons(icons.darkMode),
85
- }
48
+ const prepared = icons.map((file) => ({
49
+ path : file.path,
50
+ size : file.size ? `${file.size}x${file.size}` : undefined,
51
+ type : getMimeType(file.path),
52
+ media : getMedia(file.theme),
53
+ apple : file.apple ?? false,
54
+ }))
86
55
 
87
56
  ---
88
57
 
89
58
  {manifest && <link rel="manifest" href={manifest} />}
90
59
 
91
- {prepared.default?.ico && (
92
- <link
93
- rel="icon"
94
- type="image/x-icon"
95
- href={prepared.default.ico.path}
96
- sizes={prepared.default.ico.size}
97
- />
98
- )}
99
- {prepared.default?.svg && (
100
- <link
101
- rel="icon"
102
- type="image/svg+xml"
103
- href={prepared.default.svg.path}
104
- sizes={prepared.default.svg.size}
105
- />
106
- )}
107
- {prepared.default?.apple && (
108
- <link
109
- rel="apple-touch-icon"
110
- href={prepared.default.apple.path}
111
- sizes={prepared.default.apple.size}
112
- />
113
- )}
114
- {prepared.default?.png?.map((png) => (
115
- <link
116
- rel="icon"
117
- type="image/png"
118
- href={png.path}
119
- sizes={png.size}
120
- />
121
- ))}
122
-
123
- {prepared.lightMode?.ico && (
124
- <link
125
- rel="icon"
126
- type="image/x-icon"
127
- href={prepared.lightMode.ico.path}
128
- sizes={prepared.lightMode.ico.size}
129
- media="(prefers-color-scheme: light)"
130
- />
131
- )}
132
- {prepared.lightMode?.svg && (
133
- <link
134
- rel="icon"
135
- type="image/svg+xml"
136
- href={prepared.lightMode.svg.path}
137
- sizes={prepared.lightMode.svg.size}
138
- media="(prefers-color-scheme: light)"
139
- />
140
- )}
141
- {prepared.lightMode?.png?.map((png) => (
142
- <link
143
- rel="icon"
144
- type="image/png"
145
- href={png.path}
146
- sizes={png.size}
147
- media="(prefers-color-scheme: light)"
148
- />
149
- ))}
60
+ {prepared.map((icon) => {
61
+ if (icon.apple) {
62
+ return (
63
+ <link
64
+ rel="apple-touch-icon"
65
+ href={icon.path}
66
+ sizes={icon.size}
67
+ />
68
+ )
69
+ }
150
70
 
151
- {prepared.darkMode?.ico && (
152
- <link
153
- rel="icon"
154
- type="image/x-icon"
155
- href={prepared.darkMode.ico.path}
156
- sizes={prepared.darkMode.ico.size}
157
- media="(prefers-color-scheme: dark)"
158
- />
159
- )}
160
- {prepared.darkMode?.svg && (
161
- <link
162
- rel="icon"
163
- type="image/svg+xml"
164
- href={prepared.darkMode.svg.path}
165
- sizes={prepared.darkMode.svg.size}
166
- media="(prefers-color-scheme: dark)"
167
- />
168
- )}
169
- {prepared.darkMode?.png?.map((png) => (
170
- <link
171
- rel="icon"
172
- type="image/png"
173
- href={png.path}
174
- sizes={png.size}
175
- media="(prefers-color-scheme: dark)"
176
- />
177
- ))}
71
+ return (
72
+ <link
73
+ rel="icon"
74
+ type={icon.type}
75
+ href={icon.path}
76
+ sizes={icon.size}
77
+ media={icon.media}
78
+ />
79
+ )
80
+ })}
@@ -93,7 +93,6 @@ const {
93
93
  <Favicon
94
94
  icons={favicon.icons}
95
95
  manifest={favicon.manifest}
96
- cacheBust={favicon.cacheBust}
97
96
  />
98
97
  )}
99
98