@devsantara/head 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Devsantara
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,330 @@
1
+ # @devsantara/head
2
+
3
+ **A lightweight, type-safe HTML `<head>` builder for modern web applications.**
4
+
5
+ Build SEO-friendly metadata with a fluent API, full TypeScript support, and framework adapters for **React**, **TanStack Router/Start**, and more.
6
+
7
+ ---
8
+
9
+ > [!WARNING]
10
+ > **Early Development**: This library is in active development (v0.x.x). Expect breaking changes between minor versions until v1.0. We recommend pinning to exact versions in production.
11
+
12
+ ## ✨ Features
13
+
14
+ ### Developer Experience First
15
+
16
+ - **Fluent Builder API** – Chain methods naturally for readable, maintainable metadata configuration
17
+ - **Full TypeScript Support** – Autocomplete, type checking, and inline documentation in your IDE
18
+ - **Zero Dependencies** – Lightweight core with optional framework adapters
19
+
20
+ ### Comprehensive Head Management
21
+
22
+ - **SEO Essentials** – Title, description, canonical URLs, robots directives.
23
+ - **Social Media** – Open Graph and Twitter Card meta tags for rich previews.
24
+ - **Mobile Optimization** – Viewport configuration, color schemes, PWA icons.
25
+ - **Advanced Tags** – Alternates, manifests, stylesheets, scripts, and custom meta tags.
26
+ - **Simplified URL Management** – Most metadata (Open Graph, canonical, alternates) requires absolute URLs. Set `metadataBase` once and use convenient relative paths everywhere.
27
+ - **Continuously Expanding** – Actively adding more metadata types based on community feedback.
28
+
29
+ ### Framework Integration
30
+
31
+ - **React Adapter** – Generate React components directly from your metadata
32
+ - **TanStack Router** – First-class support for route-level metadata
33
+ - **Framework Agnostic** – Works with vanilla JS, SSR, SSG, or any rendering strategy
34
+
35
+ ## 📦 Installation
36
+
37
+ ```bash
38
+ # npm
39
+ npm install @devsantara/head
40
+
41
+ # pnpm
42
+ pnpm add @devsantara/head
43
+
44
+ # yarn
45
+ yarn add @devsantara/head
46
+
47
+ # bun
48
+ bun add @devsantara/head
49
+ ```
50
+
51
+ > **Note:** Framework adapters are included in the core package. No additional installations needed.
52
+
53
+ ## 🚀 Quick Start
54
+
55
+ ### Basic Usage
56
+
57
+ ```typescript
58
+ import { HeadBuilder } from '@devsantara/head';
59
+
60
+ const head = new HeadBuilder()
61
+ .addTitle('My Awesome Website')
62
+ .addDescription('A comprehensive guide to web development')
63
+ .addViewport({ width: 'device-width', initialScale: 1 })
64
+ .build();
65
+ ```
66
+
67
+ ```typescript
68
+ // Output (HeadElement[]):
69
+ [
70
+ {
71
+ type: 'title',
72
+ attributes: { children: 'My Awesome Website' },
73
+ },
74
+ {
75
+ type: 'meta',
76
+ attributes: {
77
+ name: 'description',
78
+ content: 'A comprehensive guide to web development',
79
+ },
80
+ },
81
+ {
82
+ type: 'meta',
83
+ attributes: {
84
+ name: 'viewport',
85
+ content: 'width=device-width, initial-scale=1',
86
+ },
87
+ },
88
+ ];
89
+ ```
90
+
91
+ ### With Metadata Base URL
92
+
93
+ Use `metadataBase` to automatically resolve relative URLs to absolute URLs:
94
+
95
+ ```typescript
96
+ import { HeadBuilder } from '@devsantara/head';
97
+
98
+ const head = new HeadBuilder({
99
+ metadataBase: new URL('https://devsantara.com'), // <- Add metadata base URL
100
+ })
101
+ .addTitle('My Blog Post')
102
+ .addOpenGraph((helper) => ({
103
+ title: 'My Blog Post',
104
+ url: helper.resolveUrl('/blog/my-post'),
105
+ image: {
106
+ url: helper.resolveUrl('/images/og-image.jpg'),
107
+ },
108
+ }))
109
+ .build();
110
+ ```
111
+
112
+ ```typescript
113
+ // Output (HeadElement[]):
114
+ [
115
+ {
116
+ type: 'title',
117
+ attributes: { children: 'My Blog Post' },
118
+ },
119
+ {
120
+ type: 'meta',
121
+ attributes: {
122
+ property: 'og:title',
123
+ content: 'My Blog Post',
124
+ },
125
+ },
126
+ {
127
+ type: 'meta',
128
+ attributes: {
129
+ property: 'og:url',
130
+ content: 'https://devsantara.com/blog/my-post',
131
+ },
132
+ },
133
+ {
134
+ type: 'meta',
135
+ attributes: {
136
+ property: 'og:image',
137
+ content: 'https://devsantara.com/images/og-image.jpg',
138
+ },
139
+ },
140
+ ];
141
+ ```
142
+
143
+ ### With React Adapter
144
+
145
+ ```tsx
146
+ import { HeadBuilder } from '@devsantara/head';
147
+ import { HeadReactAdapter } from '@devsantara/head/adapters';
148
+
149
+ const head = new HeadBuilder({
150
+ adapter: new HeadReactAdapter(), // <- Add React adapter
151
+ })
152
+ .addTitle('My Awesome Website')
153
+ .addDescription('A comprehensive guide to web development')
154
+ .build();
155
+
156
+ // Use in your document
157
+ export function Document() {
158
+ return (
159
+ <html>
160
+ <head>{head}</head>
161
+ {/* ... */}
162
+ </html>
163
+ );
164
+ }
165
+ ```
166
+
167
+ ```typescript
168
+ // Output (React.ReactNode[]):
169
+ [
170
+ <title>My Awesome Website</title>,
171
+ <meta
172
+ name="description"
173
+ content="A comprehensive guide to web development"
174
+ />,
175
+ ];
176
+ ```
177
+
178
+ ### With TanStack Router/Start Adapter
179
+
180
+ ```tsx
181
+ import { HeadBuilder } from '@devsantara/head';
182
+ import { HeadTanstackRouterAdapter } from '@devsantara/head/adapters';
183
+ import { createRootRoute } from '@tanstack/react-router';
184
+
185
+ export const Route = createRootRoute({
186
+ head: () => {
187
+ return new HeadBuilder({
188
+ adapter: new HeadTanstackRouterAdapter(), // <- Add Tanstack router adapter
189
+ })
190
+ .addTitle('About Us')
191
+ .addDescription('Learn more about our company')
192
+ .build();
193
+ },
194
+ });
195
+ ```
196
+
197
+ ```typescript
198
+ // Output (Tanstack Router Head[]):
199
+ [
200
+ meta: [
201
+ { title: 'About Us' },
202
+ {
203
+ name: 'description',
204
+ content: 'Learn more about our company',
205
+ },
206
+ ],
207
+ links:[],
208
+ scripts:[],
209
+ styles: []
210
+ ]
211
+ ```
212
+
213
+ ## 📚 API Reference
214
+
215
+ ### Core Methods
216
+
217
+ | Method | Description |
218
+ | --------- | -------------------------------------------------------------- |
219
+ | `build()` | Builds and returns the final head elements (or adapted output) |
220
+
221
+ ### Basic Elements
222
+
223
+ For advanced use cases not covered by the essential methods below, use these basic methods to add any custom element directly.
224
+
225
+ | Method | Description |
226
+ | ------------------------------------------------------- | ------------------------------------------------ |
227
+ | `addTitle(title: string)` | Adds a `<title>` element |
228
+ | `addMeta(attributes: HeadAttributeTypeMap['meta'])` | Adds a `<meta>` element with custom attributes |
229
+ | `addLink(attributes: HeadAttributeTypeMap['link'])` | Adds a `<link>` element with custom attributes |
230
+ | `addScript(attributes: HeadAttributeTypeMap['script'])` | Adds a `<script>` element with custom attributes |
231
+ | `addStyle(attributes: HeadAttributeTypeMap['style'])` | Adds a `<style>` element with custom attributes |
232
+
233
+ ### Essential Methods
234
+
235
+ High-level convenience methods for common metadata patterns. These methods handle the complexity of creating properly structured head.
236
+
237
+ | Method | Description |
238
+ | ---------------------------------------------------------------------- | ----------------------------------------------------------- |
239
+ | `addDescription(description: string)` | Adds a meta description tag |
240
+ | `addCanonical(valueOrFn: BuilderOption<string \| URL>)` | Adds a canonical URL link |
241
+ | `addRobots(options: RobotsOptions)` | Adds robots meta tag for search engine directives |
242
+ | `addCharSet(charset: CharSet)` | Adds character encoding declaration |
243
+ | `addViewport(options: ViewportOptions)` | Adds viewport configuration for responsive design |
244
+ | `addColorScheme(colorScheme: ColorScheme)` | Adds color scheme preference (light/dark mode) |
245
+ | `addOpenGraph(valueOrFn: BuilderOption<OpenGraphOptions>)` | Adds Open Graph meta tags for social media previews |
246
+ | `addTwitter(valueOrFn: BuilderOption<TwitterOptions>)` | Adds Twitter Card meta tags |
247
+ | `addIcon(preset: IconPreset, valueOrFn: BuilderOption<IconOptions>)` | Adds favicon or app icons (favicon, apple-touch-icon, etc.) |
248
+ | `addStylesheet(href: string \| URL, options?: StylesheetOptions)` | Adds an external stylesheet link |
249
+ | `addManifest(valueOrFn: BuilderOption<string \| URL>)` | Adds a web app manifest link |
250
+ | `addAlternateLocale(valueOrFn: BuilderOption<AlternateLocaleOptions>)` | Adds alternate language/locale links |
251
+
252
+ > **💡 Tip:** Most methods support either direct values or callback functions that receive a helper object with `resolveUrl()` for dynamic URL resolution.
253
+
254
+ ## 🔌 Adapters
255
+
256
+ Adapters transform the raw `HeadElement[]` output into framework-specific formats. The library includes built-in adapters for popular frameworks, and you can create custom adapters for your specific needs.
257
+
258
+ ### Built-in Adapters
259
+
260
+ | Framework | Adapter | Output Type |
261
+ | --------------------- | --------------------------- | ----------------------------- |
262
+ | React | `HeadReactAdapter` | `React.ReactNode[]` |
263
+ | Tanstack Router/Start | `HeadTanstackRouterAdapter` | TanStack Router `Head` object |
264
+
265
+ ### Creating Custom Adapters
266
+
267
+ You can create your own adapter by implementing the `HeadAdapter<T>` interface:
268
+
269
+ ```typescript
270
+ import type { HeadAdapter, HeadElement } from '@devsantara/head';
271
+
272
+ // Create a custom adapter that outputs HTML strings
273
+ class HeadHTMLAdapter implements HeadAdapter<string> {
274
+ transform(elements: HeadElement[]): string {
275
+ return elements
276
+ .map((element) => {
277
+ const { type, attributes } = element;
278
+ const attrs = Object.entries(attributes)
279
+ .filter(([key]) => key !== 'children')
280
+ .map(([key, value]) => `${key}="${value}"`)
281
+ .join(' ');
282
+
283
+ const children = attributes.children || '';
284
+
285
+ if (type === 'meta' || type === 'link') {
286
+ return `<${type} ${attrs}>`;
287
+ }
288
+
289
+ return `<${type} ${attrs}>${children}</${type}>`;
290
+ })
291
+ .join('\n');
292
+ }
293
+ }
294
+ ```
295
+
296
+ ```typescript
297
+ // Use your custom adapter
298
+ const html = new HeadBuilder({
299
+ adapter: new HeadHTMLAdapter(), // <- Add custom HTML adapter
300
+ })
301
+ .addTitle('My Site')
302
+ .addDescription('My description')
303
+ .build();
304
+ ```
305
+
306
+ ```html
307
+ <!-- Output (HTML string) -->
308
+ <title>My Site</title>
309
+ <meta name="description" content="My description" />
310
+ ```
311
+
312
+ ### Adapter Interface
313
+
314
+ ```typescript
315
+ interface HeadAdapter<T> {
316
+ transform(elements: HeadElement[]): T;
317
+ }
318
+ ```
319
+
320
+ **Parameters:**
321
+
322
+ - `elements` - Array of head elements with `type` and `attributes`
323
+
324
+ **Returns:**
325
+
326
+ - `T` - Your custom output format (string, object, framework components, etc.)
327
+
328
+ ## 📄 License
329
+
330
+ Licensed under the [MIT license](./LICENSE).
@@ -0,0 +1,39 @@
1
+ import { c as HeadMetaAttributes, i as HeadAdapter, l as HeadScriptAttributes, o as HeadElement, s as HeadLinkAttributes, u as HeadStyleAttributes } from "../types-Cvpk_Zha.js";
2
+ import { ReactNode } from "react";
3
+
4
+ //#region src/adapters/react-adapter.d.ts
5
+ type HeadReactAdapterResult = ReactNode[];
6
+ /**
7
+ * Adapter that transforms head elements into React components for rendering in React applications.
8
+ */
9
+ declare class HeadReactAdapter implements HeadAdapter<HeadReactAdapterResult> {
10
+ /**
11
+ * Transforms head elements into React components.
12
+ *
13
+ * @param elements - Array of head elements to transform
14
+ * @returns Array of React components ready for rendering
15
+ */
16
+ transform(elements: HeadElement[]): HeadReactAdapterResult;
17
+ }
18
+ //#endregion
19
+ //#region src/adapters/tanstack-router-adapter.d.ts
20
+ interface HeadTanStackRouterAdapterResult {
21
+ links?: HeadLinkAttributes[];
22
+ scripts?: HeadScriptAttributes[];
23
+ meta?: HeadMetaAttributes[];
24
+ styles?: HeadStyleAttributes[];
25
+ }
26
+ /**
27
+ * Adapter that transforms head elements into TanStack Router head configuration format.
28
+ */
29
+ declare class HeadTanstackRouterAdapter implements HeadAdapter<HeadTanStackRouterAdapterResult> {
30
+ /**
31
+ * Transforms head elements into TanStack Router head configuration with elements organized by type.
32
+ *
33
+ * @param elements - Array of head elements to transform
34
+ * @returns Head configuration object with categorized elements
35
+ */
36
+ transform(elements: HeadElement[]): HeadTanStackRouterAdapterResult;
37
+ }
38
+ //#endregion
39
+ export { HeadReactAdapter, HeadReactAdapterResult, HeadTanStackRouterAdapterResult, HeadTanstackRouterAdapter };
@@ -0,0 +1,72 @@
1
+ import { createElement } from "react";
2
+
3
+ //#region src/adapters/react-adapter.ts
4
+ /**
5
+ * Adapter that transforms head elements into React components for rendering in React applications.
6
+ */
7
+ var HeadReactAdapter = class {
8
+ /**
9
+ * Transforms head elements into React components.
10
+ *
11
+ * @param elements - Array of head elements to transform
12
+ * @returns Array of React components ready for rendering
13
+ */
14
+ transform(elements) {
15
+ return elements.map((element, index) => {
16
+ const { type, attributes } = element;
17
+ return createElement(type, {
18
+ key: `head-${type}-${index}`,
19
+ ...attributes
20
+ });
21
+ });
22
+ }
23
+ };
24
+
25
+ //#endregion
26
+ //#region src/utils.ts
27
+ /**
28
+ * Type guard that checks if a head element matches a specific element type.
29
+ *
30
+ * @param element - The head element to check
31
+ * @param type - The expected element type
32
+ * @returns True if the element matches the specified type
33
+ */
34
+ function isElementOfType(element, type) {
35
+ return element.type === type;
36
+ }
37
+
38
+ //#endregion
39
+ //#region src/adapters/tanstack-router-adapter.ts
40
+ /**
41
+ * Adapter that transforms head elements into TanStack Router head configuration format.
42
+ */
43
+ var HeadTanstackRouterAdapter = class {
44
+ /**
45
+ * Transforms head elements into TanStack Router head configuration with elements organized by type.
46
+ *
47
+ * @param elements - Array of head elements to transform
48
+ * @returns Head configuration object with categorized elements
49
+ */
50
+ transform(elements) {
51
+ const config = {
52
+ meta: [],
53
+ links: [],
54
+ scripts: [],
55
+ styles: []
56
+ };
57
+ for (const element of elements) if (isElementOfType(element, "meta")) config.meta?.push(element.attributes);
58
+ else if (isElementOfType(element, "link")) config.links?.push(element.attributes);
59
+ else if (isElementOfType(element, "script")) config.scripts?.push(element.attributes);
60
+ else if (isElementOfType(element, "style")) config.styles?.push(element.attributes);
61
+ else if (isElementOfType(element, "title"))
62
+ /**
63
+ * TanStack Router automatically dedupes title and meta tags
64
+ * so we only need to push the title as a meta element
65
+ */
66
+ config.meta?.push({ title: String(element.attributes.children) });
67
+ return config;
68
+ }
69
+ };
70
+
71
+ //#endregion
72
+ export { HeadReactAdapter, HeadTanstackRouterAdapter };