Skip to content

Commit febd342

Browse files
authored
Release v0.3.0
2 parents 7324db6 + 58a7bb1 commit febd342

38 files changed

Lines changed: 2481 additions & 237 deletions

.changeset/fast-forks-cough.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@devsantara/head': minor
3+
---
4+
5+
feat(title): add templated title support

.changeset/frank-books-grow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@devsantara/head': patch
3+
---
4+
5+
test: add test case to cover all codebase

.changeset/goofy-groups-post.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@devsantara/head': minor
3+
---
4+
5+
feat(script): extract src or inline source as dedicated parameter in addScript

.changeset/itchy-eagles-give.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@devsantara/head': patch
3+
---
4+
5+
refactor(builder): explicitly return this and unify variable names

.changeset/loose-banks-nail.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@devsantara/head': minor
3+
---
4+
5+
feat(builder): implement element deduplication with map
6+
7+
- Replace array-based element storage with Map for O(1) deduplication
8+
- Add getElementKey() method to generate unique keys based on element type and attributes
9+
- Elements with same key now replace previous ones instead of duplicating
10+
- Update build() method to convert Map to array format

.changeset/pink-pumas-judge.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@devsantara/head': minor
3+
---
4+
5+
feat(link): extract href as dedicated parameter in addLink
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@devsantara/head': minor
3+
---
4+
5+
fix(builder): missing manifest key and remove unused try-catch

.changeset/whole-badgers-slide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@devsantara/head': minor
3+
---
4+
5+
feat(style): extract inline css as dedicated parameter in addStyle

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,8 @@ node_modules/
44
# Build
55
dist/
66

7+
# Testing
8+
coverage/
9+
710
# Misc
811
.DS_Store

README.md

Lines changed: 123 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,12 @@ import { HeadBuilder } from '@devsantara/head';
6060
const head = new HeadBuilder()
6161
.addTitle('My Awesome Website')
6262
.addDescription('A comprehensive guide to web development')
63+
.addStyle('body { margin: 0; padding: 0; }')
6364
.addViewport({ width: 'device-width', initialScale: 1 })
65+
.addScript({ code: 'console.log("Hello, world!");' })
66+
.addScript(new URL('https://devsantara.com/assets/scripts/utils.js'), {
67+
async: true,
68+
})
6469
.build();
6570
```
6671

@@ -85,6 +90,28 @@ const head = new HeadBuilder()
8590
content: 'width=device-width, initial-scale=1',
8691
},
8792
},
93+
{
94+
type: 'style',
95+
attributes: {
96+
type: 'text/css',
97+
children: 'body { margin: 0; padding: 0; }',
98+
},
99+
},
100+
{
101+
type: 'script',
102+
attributes: {
103+
type: 'text/javascript',
104+
children: 'console.log("Hello, world!");',
105+
},
106+
},
107+
{
108+
type: 'script',
109+
attributes: {
110+
type: 'text/javascript',
111+
src: 'https://devsantara.com/assets/scripts/utils.js',
112+
async: true,
113+
},
114+
},
88115
];
89116
```
90117

@@ -140,6 +167,95 @@ const head = new HeadBuilder({
140167
];
141168
```
142169

170+
### With Templated Title
171+
172+
Set a title template with a default value, then pass page-specific titles as strings. The builder automatically applies the saved template to subsequent title updates:
173+
174+
```typescript
175+
import { HeadBuilder } from '@devsantara/head';
176+
177+
// Create a builder and set title template with default
178+
// The template stays active for all future addTitle() calls
179+
const sharedHead = new HeadBuilder().addTitle({
180+
template: '%s | My Awesome site', // Store template (%s is the placeholder)
181+
default: 'Home', // Initial title using template
182+
});
183+
// Output: <title>Home | My Awesome site</title>
184+
185+
// Update title for Posts page
186+
// Pass a string, builder applies the saved template automatically
187+
const postHead = sharedHead.addTitle('Posts').build();
188+
// Output: <title>Posts | My Awesome site</title>
189+
190+
// Update title for About page
191+
// Template is still active from the first addTitle() call
192+
const aboutHead = sharedHead.addTitle('About Us').build();
193+
// Output: <title>About Us | My Awesome site</title>
194+
```
195+
196+
**How it works:**
197+
198+
1. First `addTitle()` with template object stores the template internally
199+
2. Subsequent `addTitle()` calls with strings automatically use the stored template
200+
3. The `%s` placeholder gets replaced with your page title
201+
4. Each title replaces the previous one (deduplication)
202+
203+
### With Element Deduplication
204+
205+
HeadBuilder automatically deduplicates elements—when you add an element matching an existing one, the new one replaces the old:
206+
207+
```typescript
208+
import { HeadBuilder } from '@devsantara/head';
209+
210+
const head = new HeadBuilder()
211+
.addTitle('My Site')
212+
.addTitle('Updated Title') // Replaces previous title
213+
214+
.addDescription('First description')
215+
.addDescription('Updated description') // Replaces previous
216+
217+
.addMeta({ name: 'keywords', content: 'web, development' })
218+
.addMeta({ name: 'author', content: 'John Doe' }) // Separate meta tags coexist
219+
220+
.addCanonical('https://devsantara.com/page1')
221+
.addCanonical('https://devsantara.com/page2') // Replaces previous canonical
222+
223+
.build();
224+
```
225+
226+
```typescript
227+
// Output (HeadElement[]):
228+
[
229+
{ type: 'title', attributes: { children: 'Updated Title' } },
230+
{
231+
type: 'meta',
232+
attributes: { name: 'description', content: 'Updated description' },
233+
},
234+
{
235+
type: 'meta',
236+
attributes: { name: 'keywords', content: 'web, development' },
237+
},
238+
{ type: 'meta', attributes: { name: 'author', content: 'John Doe' } },
239+
{
240+
type: 'link',
241+
attributes: { rel: 'canonical', href: 'https://devsantara.com/page2' },
242+
},
243+
];
244+
```
245+
246+
**How it works:**
247+
248+
- **Title**: Only one per document
249+
- **Meta by name**: One per unique `name` attribute (e.g., description, keywords)
250+
- **Meta by property**: One per unique `property` attribute (e.g., `og:title`, `og:description`)
251+
- **Charset**: Only one per document
252+
- **Canonical**: Only one per document
253+
- **Manifest**: Only one per document
254+
- **Alternate locales**: One per unique language code
255+
- **Other tags**: Deduplicated by exact attribute match
256+
257+
This ensures clean metadata without accidental duplicates.
258+
143259
### With React Adapter
144260

145261
```tsx
@@ -222,13 +338,13 @@ export const Route = createRootRoute({
222338

223339
For advanced use cases not covered by the essential methods below, use these basic methods to add any custom element directly.
224340

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 |
341+
| Method | Description |
342+
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
343+
| `addTitle(title: string \| TitleOptions)` | Adds a `<title>` element with optional templating |
344+
| `addMeta(attributes: HeadAttributeTypeMap['meta'])` | Adds a `<meta>` element with custom attributes |
345+
| `addLink(href: string \| URL, attributes?)` | Adds a `<link>` element with a URL and custom attributes |
346+
| `addScript(srcOrCode: string \| URL \| { code: string }, attributes?)` | Adds a `<script>` element (external file with string/URL or inline with `{ code: string }`) |
347+
| `addStyle(css: string, attributes?)` | Adds a `<style>` element with inline CSS |
232348

233349
### Essential Methods
234350

0 commit comments

Comments
 (0)