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.
- package/README.md +182 -33
- 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/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
143
|
+
```ts
|
|
144
|
+
const metadata = extractHeadMetadata(html, {
|
|
145
|
+
meta: viewportExtractor
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
metadata.viewport;
|
|
149
|
+
```
|
|
62
150
|
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "
|
|
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.
|
|
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 .
|
|
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",
|