head-metadata 0.0.12 → 0.0.13

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.
Files changed (2) hide show
  1. package/README.md +182 -33
  2. package/package.json +19 -5
package/README.md CHANGED
@@ -10,20 +10,53 @@
10
10
  <img src="https://img.shields.io/bundlephobia/minzip/head-metadata.svg?label=minzipped%20size&style=flat&colorA=293140&colorB=FDE200" alt="NPM bundle minzipped size"/>
11
11
  </a>
12
12
  <a href="https://www.npmjs.com/package/head-metadata">
13
- <img src="https://img.shields.io/npm/dt/featuer-state.svg?label=downloads&style=flat&colorA=293140&colorB=FDE200" alt="NPM total downloads"/>
13
+ <img src="https://img.shields.io/npm/dt/head-metadata.svg?label=downloads&style=flat&colorA=293140&colorB=FDE200" alt="NPM total downloads"/>
14
14
  </a>
15
15
  <a href="https://discord.gg/w4xE3bSjhQ">
16
16
  <img src="https://img.shields.io/discord/795291052897992724.svg?label=&logo=discord&logoColor=000000&color=293140&labelColor=FDE200" alt="Join Discord"/>
17
17
  </a>
18
18
  </p>
19
19
 
20
- > Status: Experimental
20
+ `head-metadata` extracts structured metadata from the `<head>` of an HTML document. It tokenizes the first head element with `xml-tokenizer`, ships extractors for `title`, `meta`, and `link`, and lets you add focused extractors for project-specific tags.
21
21
 
22
- `head-metadata` is a typesafe and straightforward utility for extracting structured metadata (like `<meta>`, `<title>`, and `<link>`) from the `<head>` of an HTML document.
22
+ - Read page title, meta tags, Open Graph tags, charset, and canonical links into typed output
23
+ - Stop after the first `<head>` so full-page HTML does not need a DOM parse
24
+ - Combine built-in extractors with custom `single` and `collection` extractors
25
+ - Keep the extraction shape explicit: output keys come from the extractor config
23
26
 
24
- ## 📖 Usage
27
+ ```ts
28
+ import { extractHeadMetadata, linkExtractor, metaExtractor, titleExtractor } from 'head-metadata';
29
+
30
+ const html = `
31
+ <head>
32
+ <title>Example</title>
33
+ <meta name="description" content="An example page" />
34
+ <meta property="og:title" content="Example OG title" />
35
+ <link rel="canonical" href="https://example.com" />
36
+ </head>
37
+ `;
38
+
39
+ const metadata = extractHeadMetadata(html, {
40
+ title: titleExtractor,
41
+ meta: metaExtractor,
42
+ link: linkExtractor
43
+ });
44
+
45
+ console.log(metadata.title);
46
+ console.log(metadata.meta.description);
47
+ console.log(metadata.meta['og:title']);
48
+ console.log(metadata.link.canonical);
49
+ ```
25
50
 
26
- ### Extract Metadata from `<head>`
51
+ ## Install
52
+
53
+ ```bash
54
+ npm install head-metadata
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ Pass HTML and the extractors you want to run:
27
60
 
28
61
  ```ts
29
62
  import { extractHeadMetadata, linkExtractor, metaExtractor, titleExtractor } from 'head-metadata';
@@ -33,47 +66,163 @@ const html = `
33
66
  <head>
34
67
  <title>Example</title>
35
68
  <meta name="description" content="An example page" />
69
+ <meta property="og:title" content="Example OG title" />
36
70
  <link rel="canonical" href="https://example.com" />
37
71
  </head>
72
+ <body>Hello</body>
38
73
  </html>
39
74
  `;
40
75
 
41
76
  const metadata = extractHeadMetadata(html, {
42
- meta: metaExtractor,
43
- title: titleExtractor,
44
- link: linkExtractor
77
+ title: titleExtractor,
78
+ meta: metaExtractor,
79
+ link: linkExtractor
80
+ });
81
+
82
+ metadata.title; // Example
83
+ metadata.meta.description; // An example page
84
+ metadata.meta['og:title']; // Example OG title
85
+ metadata.link.canonical; // https://example.com
86
+ ```
87
+
88
+ The output shape follows the extractor config. Config object keys choose the element names each extractor receives. `key` and `parent` choose metadata fields. Collection extractors write records under `parent`, while single extractors set a string under `key` when the callback returns a value.
89
+
90
+ ## Built-in Extractors
91
+
92
+ ### `titleExtractor`
93
+
94
+ Reads text content from `<title>` and returns it as `metadata.title`.
95
+
96
+ ```ts
97
+ const metadata = extractHeadMetadata(html, {
98
+ title: titleExtractor
45
99
  });
100
+ ```
101
+
102
+ ### `metaExtractor`
103
+
104
+ Reads `<meta>` tags into `metadata.meta`.
105
+
106
+ ```html
107
+ <meta charset="utf-8" />
108
+ <meta name="description" content="An example page" />
109
+ <meta property="og:title" content="Example OG title" />
110
+ ```
111
+
112
+ The extractor uses `charset`, `name`, or `property` as the record key.
113
+
114
+ ### `linkExtractor`
115
+
116
+ Reads `<link>` tags into `metadata.link`.
117
+
118
+ ```html
119
+ <link rel="canonical" href="https://example.com" />
120
+ ```
121
+
122
+ The extractor uses `rel` as the record key and `href` as the value.
123
+
124
+ ## Custom Extractors
125
+
126
+ Use a `single` extractor for one output value:
127
+
128
+ ```ts
129
+ import type { TSingleExtractor } from 'head-metadata';
130
+
131
+ const viewportExtractor = {
132
+ type: 'single',
133
+ key: 'viewport',
134
+ callback: (node) => {
135
+ const name = node.attributes.find((attr) => attr.local === 'name');
136
+ const content = node.attributes.find((attr) => attr.local === 'content');
46
137
 
47
- console.log(metadata);
48
- /*
49
- {
50
- title: 'Example',
51
- meta: {
52
- description: 'An example page'
53
- },
54
- link: {
55
- canonical: 'https://example.com'
138
+ return name?.value === 'viewport' && content != null ? content.value : null;
56
139
  }
57
- }
58
- */
140
+ } satisfies TSingleExtractor;
59
141
  ```
60
142
 
61
- ### Create Custom Extractors
143
+ ```ts
144
+ const metadata = extractHeadMetadata(html, {
145
+ meta: viewportExtractor
146
+ });
147
+
148
+ metadata.viewport;
149
+ ```
62
150
 
63
- You can write your own extractors to handle any `<head>` child element:
151
+ In this example, `meta` means the extractor receives `<meta>` elements. `key: 'viewport'` controls the output field.
152
+
153
+ Use a `collection` extractor when many tags should contribute to one record:
64
154
 
65
155
  ```ts
66
- export const customLinkExtractor = {
67
- type: 'collection' as const,
68
- parent: 'link' as const,
69
- callback: (node) => {
70
- const rel = node.attributes.find((a) => a.local === 'rel');
71
- const href = node.attributes.find((a) => a.local === 'href');
72
- if (rel != null && href != null) {
73
- return { key: rel.value, value: href.value };
74
- }
75
-
76
- return null;
77
- }
156
+ import type { TCollectionExtractor } from 'head-metadata';
157
+
158
+ const iconExtractor = {
159
+ type: 'collection',
160
+ parent: 'link',
161
+ callback: (node) => {
162
+ const rel = node.attributes.find((attr) => attr.local === 'rel');
163
+ const href = node.attributes.find((attr) => attr.local === 'href');
164
+
165
+ if (rel?.value.includes('icon') === true && href != null) {
166
+ return { key: rel.value, value: href.value };
167
+ }
168
+
169
+ return null;
170
+ }
78
171
  } satisfies TCollectionExtractor;
79
172
  ```
173
+
174
+ Install custom extractors under the tag name they should receive:
175
+
176
+ ```ts
177
+ const metadata = extractHeadMetadata(html, {
178
+ link: iconExtractor
179
+ });
180
+ ```
181
+
182
+ ## API
183
+
184
+ ### `extractHeadMetadata(html, extractors)`
185
+
186
+ Tokenizes the first `<head>` element and returns metadata collected by the provided extractors.
187
+
188
+ ```ts
189
+ const metadata = extractHeadMetadata(html, {
190
+ title: titleExtractor,
191
+ meta: metaExtractor
192
+ });
193
+ ```
194
+
195
+ Extractor callbacks receive a `TXmlNode` with this shape:
196
+
197
+ | Field | Description |
198
+ | ------------ | ------------------------------------------------ |
199
+ | `local` | Local element name |
200
+ | `prefix` | Namespace prefix when present |
201
+ | `attributes` | Parsed attributes with local name, prefix, value |
202
+ | `content` | Child nodes and text content |
203
+
204
+ ## FAQ
205
+
206
+ ### Is this a full metadata crawler?
207
+
208
+ No. `head-metadata` extracts metadata from HTML you already have. Fetching pages, following redirects, resolving relative URLs, and crawling links stay in your application code.
209
+
210
+ ### Why does it use extractors instead of returning every head tag?
211
+
212
+ Extractors keep the output shape explicit and typed. You choose which tags matter, how keys are derived, and which tags should be ignored.
213
+
214
+ ### Can I extract Open Graph and Twitter metadata?
215
+
216
+ Yes. `metaExtractor` stores both `name` and `property` attributes as keys, so tags such as `og:title` and `twitter:card` are included in `metadata.meta`.
217
+
218
+ ### What happens with multiple `<head>` elements?
219
+
220
+ Only the first matched `<head>` is read. Later `<head>` elements are ignored.
221
+
222
+ ### What happens when two tags produce the same key?
223
+
224
+ Collection output is a record. The later value replaces the earlier value for the same key.
225
+
226
+ ### Does it validate SEO metadata?
227
+
228
+ No. It extracts selected values only. It does not validate SEO rules, normalize metadata, or resolve relative URLs.
package/package.json CHANGED
@@ -1,9 +1,23 @@
1
1
  {
2
2
  "name": "head-metadata",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "private": false,
5
- "description": "Typesafe and straightforward utility for extracting structured metadata (like `<meta>`, `<title>`, and `<link>`) from the `<head>` of an HTML document",
6
- "keywords": [],
5
+ "description": "Typed HTML head metadata extraction for title, meta, link, and custom extractors",
6
+ "keywords": [
7
+ "html",
8
+ "metadata",
9
+ "head-metadata",
10
+ "head",
11
+ "meta-tags",
12
+ "open-graph",
13
+ "twitter-card",
14
+ "seo",
15
+ "title",
16
+ "link-tags",
17
+ "typescript",
18
+ "extractor",
19
+ "xml-tokenizer"
20
+ ],
7
21
  "homepage": "https://builder.group/?utm_source=package-json",
8
22
  "bugs": {
9
23
  "url": "https://github.com/builder-group/community/issues"
@@ -26,7 +40,7 @@
26
40
  "xml-tokenizer": "0.0.45"
27
41
  },
28
42
  "devDependencies": {
29
- "@types/node": "^25.5.2",
43
+ "@types/node": "^25.9.1",
30
44
  "rollup-presets": "0.0.27"
31
45
  },
32
46
  "size-limit": [
@@ -39,7 +53,7 @@
39
53
  "build:prod": "export NODE_ENV=production && pnpm build",
40
54
  "clean": "shx rm -rf dist && shx rm -rf .turbo && shx rm -rf node_modules",
41
55
  "install:clean": "pnpm run clean && pnpm install",
42
- "lint": "eslint . --fix",
56
+ "lint": "eslint .",
43
57
  "publish:patch": "pnpm build:prod && pnpm version patch && pnpm publish --no-git-checks --access=public",
44
58
  "size": "size-limit --why",
45
59
  "start:dev": "tsc -w",