unhead 2.0.14 → 2.0.15

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/dist/server.d.mts CHANGED
@@ -12,12 +12,41 @@ declare function renderSSRHead(head: Unhead<any>, options?: RenderSSRHeadOptions
12
12
  bodyAttrs: string;
13
13
  }>;
14
14
 
15
+ /**
16
+ * Transform an HTML template string by extracting any head tags and attributes from it, pushing them to Unhead,
17
+ * and injecting the resulting head tags back into the HTML.
18
+ * Uses optimized parsing and index-based HTML construction for best performance.
19
+ */
15
20
  declare function transformHtmlTemplate(head: Unhead<any>, html: string, options?: RenderSSRHeadOptions): Promise<string>;
21
+ /**
22
+ * Transform an HTML template string by injecting head tags managed by Unhead.
23
+ *
24
+ * The differs to `transformHtmlTemplate` in that it does not extract and push any head input from the HTML, resulting
25
+ * in much more performant execution if you don't need that feature.
26
+ *
27
+ * However, this also means that any head tags or attributes already present in the HTML may be duplicated or
28
+ * ordered incorrectly, so use with caution.
29
+ */
30
+ declare function transformHtmlTemplateRaw(head: Unhead<any>, html: string, options?: RenderSSRHeadOptions): Promise<string>;
16
31
 
17
- declare function extractUnheadInputFromHtml(html: string): {
32
+ interface PreparedHtmlTemplate {
18
33
  html: string;
19
34
  input: SerializableHead;
20
- };
35
+ }
36
+ interface PreparedHtmlTemplateWithIndexes {
37
+ html: string;
38
+ input: SerializableHead;
39
+ indexes: {
40
+ htmlTagStart: number;
41
+ htmlTagEnd: number;
42
+ headTagEnd: number;
43
+ bodyTagStart: number;
44
+ bodyTagEnd: number;
45
+ bodyCloseTagStart: number;
46
+ };
47
+ }
48
+
49
+ declare function extractUnheadInputFromHtml(html: string): PreparedHtmlTemplate;
21
50
 
22
51
  declare function propsToString(props: Record<string, any>): string;
23
52
 
@@ -32,4 +61,5 @@ declare function ssrRenderTags<T extends HeadTag>(tags: T[], options?: RenderSSR
32
61
  declare function escapeHtml(str: string): string;
33
62
  declare function tagToString<T extends HeadTag>(tag: T): string;
34
63
 
35
- export { CreateServerHeadOptions, Unhead, createHead, escapeHtml, extractUnheadInputFromHtml, propsToString, renderSSRHead, ssrRenderTags, tagToString, transformHtmlTemplate };
64
+ export { CreateServerHeadOptions, Unhead, createHead, escapeHtml, extractUnheadInputFromHtml, propsToString, renderSSRHead, ssrRenderTags, tagToString, transformHtmlTemplate, transformHtmlTemplateRaw };
65
+ export type { PreparedHtmlTemplate, PreparedHtmlTemplateWithIndexes };
package/dist/server.d.ts CHANGED
@@ -12,12 +12,41 @@ declare function renderSSRHead(head: Unhead<any>, options?: RenderSSRHeadOptions
12
12
  bodyAttrs: string;
13
13
  }>;
14
14
 
15
+ /**
16
+ * Transform an HTML template string by extracting any head tags and attributes from it, pushing them to Unhead,
17
+ * and injecting the resulting head tags back into the HTML.
18
+ * Uses optimized parsing and index-based HTML construction for best performance.
19
+ */
15
20
  declare function transformHtmlTemplate(head: Unhead<any>, html: string, options?: RenderSSRHeadOptions): Promise<string>;
21
+ /**
22
+ * Transform an HTML template string by injecting head tags managed by Unhead.
23
+ *
24
+ * The differs to `transformHtmlTemplate` in that it does not extract and push any head input from the HTML, resulting
25
+ * in much more performant execution if you don't need that feature.
26
+ *
27
+ * However, this also means that any head tags or attributes already present in the HTML may be duplicated or
28
+ * ordered incorrectly, so use with caution.
29
+ */
30
+ declare function transformHtmlTemplateRaw(head: Unhead<any>, html: string, options?: RenderSSRHeadOptions): Promise<string>;
16
31
 
17
- declare function extractUnheadInputFromHtml(html: string): {
32
+ interface PreparedHtmlTemplate {
18
33
  html: string;
19
34
  input: SerializableHead;
20
- };
35
+ }
36
+ interface PreparedHtmlTemplateWithIndexes {
37
+ html: string;
38
+ input: SerializableHead;
39
+ indexes: {
40
+ htmlTagStart: number;
41
+ htmlTagEnd: number;
42
+ headTagEnd: number;
43
+ bodyTagStart: number;
44
+ bodyTagEnd: number;
45
+ bodyCloseTagStart: number;
46
+ };
47
+ }
48
+
49
+ declare function extractUnheadInputFromHtml(html: string): PreparedHtmlTemplate;
21
50
 
22
51
  declare function propsToString(props: Record<string, any>): string;
23
52
 
@@ -32,4 +61,5 @@ declare function ssrRenderTags<T extends HeadTag>(tags: T[], options?: RenderSSR
32
61
  declare function escapeHtml(str: string): string;
33
62
  declare function tagToString<T extends HeadTag>(tag: T): string;
34
63
 
35
- export { CreateServerHeadOptions, Unhead, createHead, escapeHtml, extractUnheadInputFromHtml, propsToString, renderSSRHead, ssrRenderTags, tagToString, transformHtmlTemplate };
64
+ export { CreateServerHeadOptions, Unhead, createHead, escapeHtml, extractUnheadInputFromHtml, propsToString, renderSSRHead, ssrRenderTags, tagToString, transformHtmlTemplate, transformHtmlTemplateRaw };
65
+ export type { PreparedHtmlTemplate, PreparedHtmlTemplateWithIndexes };
package/dist/server.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as createUnhead } from './shared/unhead.DH45uomy.mjs';
2
- import { T as TagsWithInnerContent, S as SelfClosingTags$1 } from './shared/unhead.yem5I2v_.mjs';
2
+ import { T as TagsWithInnerContent, S as SelfClosingTags } from './shared/unhead.yem5I2v_.mjs';
3
3
  import 'hookable';
4
4
  import './shared/unhead.BpRRHAhY.mjs';
5
5
  import './shared/unhead.DZbvapt-.mjs';
@@ -67,55 +67,521 @@ function createHead(options = {}) {
67
67
  return unhead;
68
68
  }
69
69
 
70
- const Attrs = /(\w+-?\w+)(?:=["']([^"']*)["'])?/g;
71
- const HtmlTag = /<html[^>]*>/;
72
- const BodyTag = /<body[^>]*>/;
73
- const HeadContent = /<head[^>]*>(.*?)<\/head>/s;
74
- const SelfClosingTags = /<(meta|link|base)[^>]*>/g;
75
- const ClosingTags = /<(title|script|style)[^>]*>[\s\S]*?<\/\1>/g;
76
- const NewLines = /(\n\s*)+/g;
77
- function extractAttributes(tag) {
78
- const inner = tag.match(/<([^>]*)>/)?.[1].split(" ").slice(1).join(" ");
79
- if (!inner)
80
- return {};
81
- const attrs = inner.match(Attrs);
82
- return attrs?.reduce((acc, attr) => {
83
- const sep = attr.indexOf("=");
84
- const key = sep > 0 ? attr.slice(0, sep) : attr;
85
- const val = sep > 0 ? attr.slice(sep + 1).slice(1, -1) : true;
86
- return { ...acc, [key]: val };
87
- }, {}) || {};
70
+ const TAG_HTML = 0;
71
+ const TAG_HEAD = 1;
72
+ const TAG_TITLE = 4;
73
+ const TAG_META = 5;
74
+ const TAG_BODY = 44;
75
+ const TAG_SCRIPT = 52;
76
+ const TAG_STYLE = 53;
77
+ const TAG_LINK = 54;
78
+ const TAG_BASE = 56;
79
+ const TagIdMap = {
80
+ html: TAG_HTML,
81
+ head: TAG_HEAD,
82
+ title: TAG_TITLE,
83
+ meta: TAG_META,
84
+ body: TAG_BODY,
85
+ script: TAG_SCRIPT,
86
+ style: TAG_STYLE,
87
+ link: TAG_LINK,
88
+ base: TAG_BASE
89
+ };
90
+
91
+ const LT_CHAR = 60;
92
+ const GT_CHAR = 62;
93
+ const SLASH_CHAR = 47;
94
+ const EQUALS_CHAR = 61;
95
+ const QUOTE_CHAR = 34;
96
+ const APOS_CHAR = 39;
97
+ const EXCLAMATION_CHAR = 33;
98
+ const BACKSLASH_CHAR = 92;
99
+ const DASH_CHAR = 45;
100
+ const SPACE_CHAR = 32;
101
+ const TAB_CHAR = 9;
102
+ const NEWLINE_CHAR = 10;
103
+ const CARRIAGE_RETURN_CHAR = 13;
104
+ const EMPTY_ATTRIBUTES = Object.freeze({});
105
+ function isWhitespace(charCode) {
106
+ return charCode === SPACE_CHAR || charCode === TAB_CHAR || charCode === NEWLINE_CHAR || charCode === CARRIAGE_RETURN_CHAR;
88
107
  }
89
- function extractUnheadInputFromHtml(html) {
108
+ function processCommentOrDoctype(htmlChunk, position) {
109
+ let i = position;
110
+ const chunkLength = htmlChunk.length;
111
+ if (i + 3 < chunkLength && htmlChunk.charCodeAt(i + 2) === DASH_CHAR && htmlChunk.charCodeAt(i + 3) === DASH_CHAR) {
112
+ i += 4;
113
+ while (i < chunkLength - 2) {
114
+ if (htmlChunk.charCodeAt(i) === DASH_CHAR && htmlChunk.charCodeAt(i + 1) === DASH_CHAR && htmlChunk.charCodeAt(i + 2) === GT_CHAR) {
115
+ i += 3;
116
+ return {
117
+ complete: true,
118
+ newPosition: i,
119
+ remainingText: ""
120
+ };
121
+ }
122
+ i++;
123
+ }
124
+ return {
125
+ complete: false,
126
+ newPosition: position,
127
+ remainingText: htmlChunk.substring(position)
128
+ };
129
+ } else {
130
+ i += 2;
131
+ while (i < chunkLength) {
132
+ if (htmlChunk.charCodeAt(i) === GT_CHAR) {
133
+ i++;
134
+ return {
135
+ complete: true,
136
+ newPosition: i,
137
+ remainingText: ""
138
+ };
139
+ }
140
+ i++;
141
+ }
142
+ return {
143
+ complete: false,
144
+ newPosition: i,
145
+ remainingText: htmlChunk.substring(position, i)
146
+ };
147
+ }
148
+ }
149
+ function parseAttributes(attrStr) {
150
+ if (!attrStr)
151
+ return EMPTY_ATTRIBUTES;
152
+ const result = {};
153
+ const len = attrStr.length;
154
+ let i = 0;
155
+ const WHITESPACE = 0;
156
+ const NAME = 1;
157
+ const AFTER_NAME = 2;
158
+ const BEFORE_VALUE = 3;
159
+ const QUOTED_VALUE = 4;
160
+ const UNQUOTED_VALUE = 5;
161
+ let state = WHITESPACE;
162
+ let nameStart = 0;
163
+ let nameEnd = 0;
164
+ let valueStart = 0;
165
+ let quoteChar = 0;
166
+ let name = "";
167
+ while (i < len) {
168
+ const charCode = attrStr.charCodeAt(i);
169
+ const isSpace = isWhitespace(charCode);
170
+ switch (state) {
171
+ case WHITESPACE:
172
+ if (!isSpace) {
173
+ state = NAME;
174
+ nameStart = i;
175
+ nameEnd = 0;
176
+ }
177
+ break;
178
+ case NAME:
179
+ if (charCode === EQUALS_CHAR || isSpace) {
180
+ nameEnd = i;
181
+ name = attrStr.substring(nameStart, nameEnd).toLowerCase();
182
+ state = charCode === EQUALS_CHAR ? BEFORE_VALUE : AFTER_NAME;
183
+ }
184
+ break;
185
+ case AFTER_NAME:
186
+ if (charCode === EQUALS_CHAR) {
187
+ state = BEFORE_VALUE;
188
+ } else if (!isSpace) {
189
+ result[name] = "";
190
+ state = NAME;
191
+ nameStart = i;
192
+ nameEnd = 0;
193
+ }
194
+ break;
195
+ case BEFORE_VALUE:
196
+ if (charCode === QUOTE_CHAR || charCode === APOS_CHAR) {
197
+ quoteChar = charCode;
198
+ state = QUOTED_VALUE;
199
+ valueStart = i + 1;
200
+ } else if (!isSpace) {
201
+ state = UNQUOTED_VALUE;
202
+ valueStart = i;
203
+ }
204
+ break;
205
+ case QUOTED_VALUE:
206
+ if (charCode === BACKSLASH_CHAR && i + 1 < len) {
207
+ i++;
208
+ } else if (charCode === quoteChar) {
209
+ result[name] = attrStr.substring(valueStart, i);
210
+ state = WHITESPACE;
211
+ }
212
+ break;
213
+ case UNQUOTED_VALUE:
214
+ if (isSpace || charCode === GT_CHAR) {
215
+ result[name] = attrStr.substring(valueStart, i);
216
+ state = WHITESPACE;
217
+ }
218
+ break;
219
+ }
220
+ i++;
221
+ }
222
+ if (state === QUOTED_VALUE || state === UNQUOTED_VALUE) {
223
+ if (name) {
224
+ result[name] = attrStr.substring(valueStart, i);
225
+ }
226
+ } else if (state === NAME || state === AFTER_NAME || state === BEFORE_VALUE) {
227
+ nameEnd = nameEnd || i;
228
+ const currentName = attrStr.substring(nameStart, nameEnd).toLowerCase();
229
+ if (currentName) {
230
+ result[currentName] = "";
231
+ }
232
+ }
233
+ return result;
234
+ }
235
+ function parseHtmlForIndexes(html) {
236
+ const indexes = {
237
+ htmlTagStart: html.indexOf("<html"),
238
+ htmlTagEnd: -1,
239
+ headTagEnd: html.indexOf("</head>"),
240
+ bodyTagStart: html.indexOf("<body"),
241
+ bodyTagEnd: -1,
242
+ bodyCloseTagStart: html.indexOf("</body>")
243
+ };
244
+ if (indexes.htmlTagStart >= 0) {
245
+ const htmlTagEndPos = html.indexOf(">", indexes.htmlTagStart);
246
+ if (htmlTagEndPos >= 0) {
247
+ indexes.htmlTagEnd = htmlTagEndPos + 1;
248
+ }
249
+ }
250
+ if (indexes.bodyTagStart >= 0) {
251
+ const bodyTagEndPos = html.indexOf(">", indexes.bodyTagStart);
252
+ if (bodyTagEndPos >= 0) {
253
+ indexes.bodyTagEnd = bodyTagEndPos + 1;
254
+ }
255
+ }
256
+ return { html, input: {}, indexes };
257
+ }
258
+ function parseHtmlForUnheadExtraction(html) {
90
259
  const input = {};
91
- input.htmlAttrs = extractAttributes(html.match(HtmlTag)?.[0] || "");
92
- html = html.replace(HtmlTag, "<html>");
93
- input.bodyAttrs = extractAttributes(html.match(BodyTag)?.[0] || "");
94
- html = html.replace(BodyTag, "<body>");
95
- const innerHead = html.match(HeadContent)?.[1] || "";
96
- innerHead.match(SelfClosingTags)?.forEach((s) => {
97
- html = html.replace(s, "");
98
- const tag = s.split(" ")[0].slice(1);
99
- input[tag] = input[tag] || [];
100
- input[tag].push(extractAttributes(s));
101
- });
102
- innerHead.match(ClosingTags)?.map((tag) => tag.trim()).filter(Boolean).forEach((tag) => {
103
- html = html.replace(tag, "");
104
- const type = tag.match(/<([a-z-]+)/)?.[1];
105
- const res = extractAttributes(tag);
106
- const innerContent = tag.match(/>([\s\S]*)</)?.[1];
107
- if (innerContent) {
108
- res[type !== "script" ? "textContent" : "innerHTML"] = innerContent;
109
- }
110
- if (type === "title") {
111
- input.title = res;
260
+ const htmlParts = [];
261
+ let position = 0;
262
+ let inHead = false;
263
+ let foundBodyStart = false;
264
+ let lastCopyPosition = 0;
265
+ let currentOutputLength = 0;
266
+ const indexes = {
267
+ htmlTagStart: -1,
268
+ htmlTagEnd: -1,
269
+ headTagEnd: -1,
270
+ bodyTagStart: -1,
271
+ bodyTagEnd: -1,
272
+ bodyCloseTagStart: -1
273
+ };
274
+ const headElementsToExtract = /* @__PURE__ */ new Set([TAG_TITLE, TAG_META, TAG_LINK, TAG_SCRIPT, TAG_STYLE, TAG_BASE]);
275
+ function copyAccumulatedText() {
276
+ if (lastCopyPosition < position) {
277
+ const textToAdd = html.substring(lastCopyPosition, position);
278
+ htmlParts.push(textToAdd);
279
+ currentOutputLength += textToAdd.length;
280
+ lastCopyPosition = position;
281
+ }
282
+ }
283
+ function addText(text) {
284
+ htmlParts.push(text);
285
+ currentOutputLength += text.length;
286
+ }
287
+ while (position < html.length && !foundBodyStart) {
288
+ const currentCharCode = html.charCodeAt(position);
289
+ if (currentCharCode !== LT_CHAR) {
290
+ position++;
291
+ continue;
292
+ }
293
+ if (position + 1 >= html.length) {
294
+ copyAccumulatedText();
295
+ addText(html[position]);
296
+ break;
297
+ }
298
+ const nextCharCode = html.charCodeAt(position + 1);
299
+ if (nextCharCode === EXCLAMATION_CHAR) {
300
+ const result = processCommentOrDoctype(html, position);
301
+ if (result.complete) {
302
+ copyAccumulatedText();
303
+ addText(html.substring(position, result.newPosition));
304
+ position = result.newPosition;
305
+ lastCopyPosition = position;
306
+ } else {
307
+ copyAccumulatedText();
308
+ addText(html.substring(position));
309
+ break;
310
+ }
311
+ continue;
312
+ }
313
+ if (nextCharCode === SLASH_CHAR) {
314
+ let tagEnd2 = position + 2;
315
+ while (tagEnd2 < html.length && html.charCodeAt(tagEnd2) !== GT_CHAR) {
316
+ tagEnd2++;
317
+ }
318
+ if (tagEnd2 >= html.length) {
319
+ copyAccumulatedText();
320
+ addText(html.substring(position));
321
+ break;
322
+ }
323
+ const tagName2 = html.substring(position + 2, tagEnd2).toLowerCase().trim();
324
+ const tagId2 = TagIdMap[tagName2] ?? -1;
325
+ if (tagId2 === TAG_HEAD) {
326
+ inHead = false;
327
+ copyAccumulatedText();
328
+ const headCloseStart = currentOutputLength;
329
+ addText(html.substring(position, tagEnd2 + 1));
330
+ indexes.headTagEnd = headCloseStart;
331
+ } else {
332
+ copyAccumulatedText();
333
+ addText(html.substring(position, tagEnd2 + 1));
334
+ }
335
+ position = tagEnd2 + 1;
336
+ lastCopyPosition = position;
337
+ continue;
338
+ }
339
+ const tagStart = position + 1;
340
+ let tagNameEnd = tagStart;
341
+ while (tagNameEnd < html.length) {
342
+ const c = html.charCodeAt(tagNameEnd);
343
+ if (isWhitespace(c) || c === SLASH_CHAR || c === GT_CHAR) {
344
+ break;
345
+ }
346
+ tagNameEnd++;
347
+ }
348
+ if (tagNameEnd >= html.length) {
349
+ copyAccumulatedText();
350
+ addText(html.substring(position));
351
+ break;
352
+ }
353
+ const tagName = html.substring(tagStart, tagNameEnd).toLowerCase();
354
+ const tagId = TagIdMap[tagName] ?? -1;
355
+ let tagEnd = tagNameEnd;
356
+ let inQuote = false;
357
+ let quoteChar = 0;
358
+ let foundEnd = false;
359
+ let isSelfClosing = false;
360
+ while (tagEnd < html.length && !foundEnd) {
361
+ const c = html.charCodeAt(tagEnd);
362
+ if (inQuote) {
363
+ if (c === quoteChar) {
364
+ inQuote = false;
365
+ }
366
+ } else if (c === QUOTE_CHAR || c === APOS_CHAR) {
367
+ inQuote = true;
368
+ quoteChar = c;
369
+ } else if (c === SLASH_CHAR && tagEnd + 1 < html.length && html.charCodeAt(tagEnd + 1) === GT_CHAR) {
370
+ isSelfClosing = true;
371
+ tagEnd += 2;
372
+ foundEnd = true;
373
+ continue;
374
+ } else if (c === GT_CHAR) {
375
+ tagEnd++;
376
+ foundEnd = true;
377
+ continue;
378
+ }
379
+ tagEnd++;
380
+ }
381
+ if (!foundEnd) {
382
+ copyAccumulatedText();
383
+ addText(html.substring(position));
384
+ break;
385
+ }
386
+ const attributesStr = html.substring(tagNameEnd, tagEnd - (isSelfClosing ? 2 : 1)).trim();
387
+ const attributes = parseAttributes(attributesStr);
388
+ if (tagId === TAG_HTML) {
389
+ copyAccumulatedText();
390
+ indexes.htmlTagStart = currentOutputLength;
391
+ if (Object.keys(attributes).length > 0) {
392
+ input.htmlAttrs = attributes;
393
+ addText(`<${tagName}>`);
394
+ } else {
395
+ addText(html.substring(position, tagEnd));
396
+ }
397
+ indexes.htmlTagEnd = currentOutputLength;
398
+ } else if (tagId === TAG_BODY) {
399
+ copyAccumulatedText();
400
+ indexes.bodyTagStart = currentOutputLength;
401
+ if (Object.keys(attributes).length > 0) {
402
+ input.bodyAttrs = attributes;
403
+ addText(`<${tagName}>`);
404
+ } else {
405
+ addText(html.substring(position, tagEnd));
406
+ }
407
+ indexes.bodyTagEnd = currentOutputLength;
408
+ foundBodyStart = true;
409
+ position = tagEnd;
410
+ lastCopyPosition = position;
411
+ break;
412
+ } else if (tagId === TAG_HEAD) {
413
+ inHead = true;
414
+ copyAccumulatedText();
415
+ addText(html.substring(position, tagEnd));
416
+ } else if (inHead && headElementsToExtract.has(tagId)) {
417
+ if (tagId === TAG_TITLE) {
418
+ if (!isSelfClosing) {
419
+ const titleEnd = findClosingTag(html, tagEnd, tagName);
420
+ if (titleEnd !== -1) {
421
+ const titleContent = html.substring(tagEnd, titleEnd).trim();
422
+ if (titleContent && !input.title) {
423
+ input.title = titleContent;
424
+ }
425
+ position = findTagEnd(html, titleEnd, tagName);
426
+ lastCopyPosition = position;
427
+ continue;
428
+ }
429
+ }
430
+ } else if (tagId === TAG_SCRIPT) {
431
+ const scriptAttrs = { ...attributes };
432
+ if (!isSelfClosing) {
433
+ const scriptEnd = findClosingTag(html, tagEnd, tagName);
434
+ if (scriptEnd !== -1) {
435
+ const scriptContent = html.substring(tagEnd, scriptEnd);
436
+ scriptAttrs.innerHTML = scriptContent || "";
437
+ position = findTagEnd(html, scriptEnd, tagName);
438
+ } else {
439
+ scriptAttrs.innerHTML = "";
440
+ position = tagEnd;
441
+ }
442
+ } else {
443
+ scriptAttrs.innerHTML = "";
444
+ position = tagEnd;
445
+ }
446
+ lastCopyPosition = position;
447
+ (input.script ||= []).push(scriptAttrs);
448
+ continue;
449
+ } else if (tagId === TAG_STYLE) {
450
+ const styleAttrs = { ...attributes };
451
+ if (!isSelfClosing) {
452
+ const styleEnd = findClosingTag(html, tagEnd, tagName);
453
+ if (styleEnd !== -1) {
454
+ const styleContent = html.substring(tagEnd, styleEnd);
455
+ styleAttrs.textContent = styleContent || "";
456
+ position = findTagEnd(html, styleEnd, tagName);
457
+ } else {
458
+ styleAttrs.textContent = "";
459
+ position = tagEnd;
460
+ }
461
+ } else {
462
+ styleAttrs.textContent = "";
463
+ position = tagEnd;
464
+ }
465
+ lastCopyPosition = position;
466
+ (input.style ||= []).push(styleAttrs);
467
+ continue;
468
+ } else if (tagId === TAG_META) {
469
+ (input.meta ||= []).push(attributes);
470
+ position = tagEnd;
471
+ lastCopyPosition = position;
472
+ continue;
473
+ } else if (tagId === TAG_LINK) {
474
+ (input.link ||= []).push(attributes);
475
+ position = tagEnd;
476
+ lastCopyPosition = position;
477
+ continue;
478
+ } else if (tagId === TAG_BASE && !input.base) {
479
+ input.base = attributes;
480
+ position = tagEnd;
481
+ lastCopyPosition = position;
482
+ continue;
483
+ }
112
484
  } else {
113
- input[type] = input[type] || [];
114
- input[type].push(res);
485
+ copyAccumulatedText();
486
+ addText(html.substring(position, tagEnd));
115
487
  }
116
- });
117
- html = html.replace(NewLines, "\n");
118
- return { html, input };
488
+ position = tagEnd;
489
+ lastCopyPosition = position;
490
+ }
491
+ const remainingHtml = html.substring(position);
492
+ const bodyCloseIndex = remainingHtml.indexOf("</body>");
493
+ if (bodyCloseIndex !== -1) {
494
+ indexes.bodyCloseTagStart = currentOutputLength + bodyCloseIndex;
495
+ }
496
+ copyAccumulatedText();
497
+ addText(remainingHtml);
498
+ return { html: htmlParts.join(""), input, indexes };
499
+ }
500
+ function findClosingTag(html, startPos, tagName) {
501
+ const tagId = TagIdMap[tagName];
502
+ const isScriptOrStyle = tagId === TAG_SCRIPT || tagId === TAG_STYLE;
503
+ if (!isScriptOrStyle) {
504
+ const closingTag2 = `</${tagName}`;
505
+ const index = html.indexOf(closingTag2, startPos);
506
+ return index === -1 ? -1 : index;
507
+ }
508
+ const closingTag = `</${tagName}`;
509
+ let pos = startPos;
510
+ let inSingleQuote = false;
511
+ let inDoubleQuote = false;
512
+ let inBacktick = false;
513
+ let lastCharWasBackslash = false;
514
+ while (pos < html.length) {
515
+ const currentCharCode = html.charCodeAt(pos);
516
+ if (!lastCharWasBackslash) {
517
+ if (currentCharCode === APOS_CHAR && !inDoubleQuote && !inBacktick) {
518
+ inSingleQuote = !inSingleQuote;
519
+ } else if (currentCharCode === QUOTE_CHAR && !inSingleQuote && !inBacktick) {
520
+ inDoubleQuote = !inDoubleQuote;
521
+ } else if (currentCharCode === 96 && !inSingleQuote && !inDoubleQuote) {
522
+ inBacktick = !inBacktick;
523
+ }
524
+ }
525
+ lastCharWasBackslash = currentCharCode === BACKSLASH_CHAR && !lastCharWasBackslash;
526
+ const inQuotes = inSingleQuote || inDoubleQuote || inBacktick;
527
+ if (!inQuotes && html.startsWith(closingTag, pos)) {
528
+ const afterTagPos = pos + closingTag.length;
529
+ if (afterTagPos >= html.length) {
530
+ return pos;
531
+ }
532
+ const nextChar = html.charCodeAt(afterTagPos);
533
+ if (nextChar === GT_CHAR || isWhitespace(nextChar)) {
534
+ return pos;
535
+ }
536
+ }
537
+ pos++;
538
+ }
539
+ return -1;
540
+ }
541
+ function findTagEnd(html, closingTagStart, tagName) {
542
+ let pos = closingTagStart + tagName.length + 2;
543
+ while (pos < html.length && html.charCodeAt(pos) !== GT_CHAR) {
544
+ pos++;
545
+ }
546
+ return pos < html.length ? pos + 1 : pos;
547
+ }
548
+ function applyHeadToHtml(template, headHtml) {
549
+ const { html, indexes } = template;
550
+ const parts = [];
551
+ let lastIndex = 0;
552
+ if (indexes.htmlTagStart >= 0) {
553
+ parts.push(html.substring(lastIndex, indexes.htmlTagStart));
554
+ parts.push(`<html${headHtml.htmlAttrs}>`);
555
+ lastIndex = indexes.htmlTagEnd;
556
+ }
557
+ if (indexes.headTagEnd >= 0) {
558
+ parts.push(html.substring(lastIndex, indexes.headTagEnd));
559
+ parts.push(headHtml.headTags);
560
+ parts.push("</head>");
561
+ lastIndex = indexes.headTagEnd + 7;
562
+ }
563
+ if (indexes.bodyTagStart >= 0) {
564
+ parts.push(html.substring(lastIndex, indexes.bodyTagStart));
565
+ if (headHtml.bodyTagsOpen) {
566
+ parts.push(`<body${headHtml.bodyAttrs}>
567
+ ${headHtml.bodyTagsOpen}`);
568
+ } else {
569
+ parts.push(`<body${headHtml.bodyAttrs}>`);
570
+ }
571
+ lastIndex = indexes.bodyTagEnd;
572
+ }
573
+ if (indexes.bodyCloseTagStart >= 0) {
574
+ parts.push(html.substring(lastIndex, indexes.bodyCloseTagStart));
575
+ parts.push(headHtml.bodyTags);
576
+ parts.push(html.substring(indexes.bodyCloseTagStart));
577
+ } else {
578
+ parts.push(html.substring(lastIndex));
579
+ }
580
+ return parts.join("");
581
+ }
582
+
583
+ function extractUnheadInputFromHtml(html) {
584
+ return parseHtmlForUnheadExtraction(html);
119
585
  }
120
586
 
121
587
  function encodeAttribute(value) {
@@ -161,10 +627,10 @@ function tagToString(tag) {
161
627
  const attrs = propsToString(tag.props);
162
628
  const openTag = `<${tag.tag}${attrs}>`;
163
629
  if (!TagsWithInnerContent.has(tag.tag))
164
- return SelfClosingTags$1.has(tag.tag) ? openTag : `${openTag}</${tag.tag}>`;
630
+ return SelfClosingTags.has(tag.tag) ? openTag : `${openTag}</${tag.tag}>`;
165
631
  let content = String(tag.textContent || tag.innerHTML || "");
166
632
  content = tag.tag === "title" ? escapeHtml(content) : content.replace(new RegExp(`</${tag.tag}`, "gi"), `<\\/${tag.tag}`);
167
- return SelfClosingTags$1.has(tag.tag) ? openTag : `${openTag}${content}</${tag.tag}>`;
633
+ return SelfClosingTags.has(tag.tag) ? openTag : `${openTag}${content}</${tag.tag}>`;
168
634
  }
169
635
 
170
636
  function ssrRenderTags(tags, options) {
@@ -209,11 +675,15 @@ async function renderSSRHead(head, options) {
209
675
  }
210
676
 
211
677
  async function transformHtmlTemplate(head, html, options) {
212
- const { html: parsedHtml, input } = extractUnheadInputFromHtml(html);
213
- head.push(input, { _index: 0 });
678
+ const template = parseHtmlForUnheadExtraction(html);
679
+ head.push(template.input, { _index: 0 });
680
+ const headHtml = await renderSSRHead(head, options);
681
+ return applyHeadToHtml(template, headHtml);
682
+ }
683
+ async function transformHtmlTemplateRaw(head, html, options) {
214
684
  const headHtml = await renderSSRHead(head, options);
215
- return parsedHtml.replace("<html>", `<html${headHtml.htmlAttrs}>`).replace("<body>", `<body>${headHtml.bodyTagsOpen ? `
216
- ${headHtml.bodyTagsOpen}` : ``}`).replace("<body>", `<body${headHtml.bodyAttrs}>`).replace("</head>", `${headHtml.headTags}</head>`).replace("</body>", `${headHtml.bodyTags}</body>`);
685
+ const template = parseHtmlForIndexes(html);
686
+ return applyHeadToHtml(template, headHtml);
217
687
  }
218
688
 
219
- export { createHead, escapeHtml, extractUnheadInputFromHtml, propsToString, renderSSRHead, ssrRenderTags, tagToString, transformHtmlTemplate };
689
+ export { createHead, escapeHtml, extractUnheadInputFromHtml, propsToString, renderSSRHead, ssrRenderTags, tagToString, transformHtmlTemplate, transformHtmlTemplateRaw };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "unhead",
3
3
  "type": "module",
4
- "version": "2.0.14",
4
+ "version": "2.0.15",
5
5
  "description": "Full-stack <head> manager built for any framework.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",