unhead 0.0.1

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.
@@ -0,0 +1,11 @@
1
+ import { a as HeadClient } from './types-e30878c0.js';
2
+ import 'hookable';
3
+ import '@unhead/schema';
4
+
5
+ interface RenderDomHeadOptions {
6
+ document?: Document;
7
+ }
8
+ declare const renderDOMHead: <T extends HeadClient<any>>(head: T, options?: RenderDomHeadOptions) => Promise<void>;
9
+ declare const debouncedUpdateDom: <T extends HeadClient<any>>(delayedFn: (fn: () => void) => void, head: T, options?: RenderDomHeadOptions) => Promise<void>;
10
+
11
+ export { RenderDomHeadOptions, debouncedUpdateDom, renderDOMHead };
@@ -0,0 +1,95 @@
1
+ import { createElement } from 'zhead';
2
+
3
+ const setAttributes = ($el, tag) => {
4
+ const sideEffects = {};
5
+ const attrs = tag.props || {};
6
+ for (const k in attrs) {
7
+ if (k === "class") {
8
+ for (const c of attrs[k].split(" ")) {
9
+ if (!$el.classList.contains(c)) {
10
+ $el.classList.add(c);
11
+ sideEffects[`${tag._p}:attr:class:remove:${c}`] = () => {
12
+ $el.classList.remove(c);
13
+ };
14
+ }
15
+ }
16
+ continue;
17
+ }
18
+ $el.setAttribute(k, String(attrs[k]));
19
+ if (!k.startsWith("data-h-")) {
20
+ sideEffects[`${tag._p}:attr:${k}:remove`] = () => {
21
+ $el.removeAttribute(k);
22
+ };
23
+ }
24
+ }
25
+ return sideEffects;
26
+ };
27
+
28
+ let domUpdatePromise = null;
29
+ const renderDOMHead = async (head, options = {}) => {
30
+ const dom = options.document || window.document;
31
+ const tags = await head.resolveTags();
32
+ await head.hooks.callHook("dom:beforeRender", { head, tags, document: dom });
33
+ head._flushDomSideEffects();
34
+ const sideEffectMap = {};
35
+ for (const tag of tags) {
36
+ sideEffectMap[tag._e] = sideEffectMap[tag._e] || {};
37
+ let $el = tag._s ? dom.querySelector(`[${tag._s}]`) : null;
38
+ const renderCtx = { tag, document: dom, $el, head };
39
+ await head.hooks.callHook("dom:renderTag", renderCtx);
40
+ if ($el) {
41
+ if (Object.keys(tag.props).length === 0) {
42
+ $el.remove();
43
+ continue;
44
+ }
45
+ sideEffectMap[tag._e] = {
46
+ ...sideEffectMap[tag._e],
47
+ ...setAttributes($el, tag)
48
+ };
49
+ $el.innerHTML = tag.children || "";
50
+ sideEffectMap[tag._e][`${tag._p}:el:remove`] = () => $el?.remove();
51
+ continue;
52
+ }
53
+ if (tag.tag === "title" && tag.children) {
54
+ dom.title = tag.children;
55
+ continue;
56
+ }
57
+ if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") {
58
+ sideEffectMap[tag._e] = {
59
+ ...sideEffectMap[tag._e],
60
+ ...setAttributes(dom[tag.tag === "htmlAttrs" ? "documentElement" : "body"], tag)
61
+ };
62
+ continue;
63
+ }
64
+ $el = createElement(tag, dom);
65
+ switch (tag.tagPosition) {
66
+ case "bodyClose":
67
+ dom.body.appendChild($el);
68
+ break;
69
+ case "bodyOpen":
70
+ dom.body.insertBefore($el, dom.body.firstChild);
71
+ break;
72
+ case "head":
73
+ default:
74
+ dom.head.appendChild($el);
75
+ break;
76
+ }
77
+ sideEffectMap[tag._e][`${tag._p}:el:remove`] = () => $el?.remove();
78
+ }
79
+ for (const k in sideEffectMap) {
80
+ const entry = head.headEntries().find((e) => e._i === Number(k));
81
+ entry._sde = {
82
+ ...entry._sde,
83
+ ...sideEffectMap[k]
84
+ };
85
+ }
86
+ };
87
+ const debouncedUpdateDom = async (delayedFn, head, options = {}) => {
88
+ function doDomUpdate() {
89
+ domUpdatePromise = null;
90
+ return renderDOMHead(head, options);
91
+ }
92
+ return domUpdatePromise = domUpdatePromise || new Promise((resolve) => delayedFn(() => resolve(doDomUpdate())));
93
+ };
94
+
95
+ export { debouncedUpdateDom, renderDOMHead };
@@ -0,0 +1,37 @@
1
+ import * as _unhead_schema from '@unhead/schema';
2
+ import { Head, Meta, Link, Script, Style, Base, HtmlAttributes, BodyAttributes, Noscript } from '@unhead/schema';
3
+ import { H as HeadPlugin, C as CreateHeadOptions, a as HeadClient, b as HeadEntryOptions } from './types-e30878c0.js';
4
+ export { A as ActiveHeadEntry, C as CreateHeadOptions, D as DomRenderTagContext, a as HeadClient, c as HeadEntry, b as HeadEntryOptions, e as HeadHooks, H as HeadPlugin, R as RuntimeMode, S as SideEffectsRecord, d as defineHeadPlugin } from './types-e30878c0.js';
5
+ import 'hookable';
6
+
7
+ declare const dedupePlugin: HeadPlugin<_unhead_schema.Head<_unhead_schema.SchemaAugmentations>>;
8
+
9
+ declare const sortPlugin: HeadPlugin<_unhead_schema.Head<_unhead_schema.SchemaAugmentations>>;
10
+
11
+ declare const titleTemplatePlugin: HeadPlugin<_unhead_schema.Head<_unhead_schema.SchemaAugmentations>>;
12
+
13
+ declare const hydratesStatePlugin: HeadPlugin<_unhead_schema.Head<_unhead_schema.SchemaAugmentations>>;
14
+
15
+ declare function createHead<T extends {} = Head>(options?: CreateHeadOptions<T>): HeadClient<T>;
16
+
17
+ declare function useHead<T>(input: T, options?: HeadEntryOptions): void;
18
+ declare const useTitle: (title: string) => void;
19
+ declare const useMeta: (meta: Meta) => void;
20
+ declare const useLink: (link: Link) => void;
21
+ declare const useScript: (script: Script) => void;
22
+ declare const useStyle: (style: Style) => void;
23
+ declare const useBase: (base: Base) => void;
24
+ declare const useHtmlAttrs: (attrs: HtmlAttributes) => void;
25
+ declare const useBodyAttrs: (attrs: BodyAttributes) => void;
26
+ declare const useTitleTemplate: (titleTemplate: string) => void;
27
+ declare const useNoscript: (noscript: Noscript) => void;
28
+
29
+ declare let activeHead: HeadClient<any> | undefined;
30
+ declare const setActiveHead: <T>(head: HeadClient<T> | undefined) => HeadClient<T> | undefined;
31
+ declare const getActiveHead: <T>() => HeadClient<T>;
32
+
33
+ declare type Arrayable<T> = T | Array<T>;
34
+ declare function asArray<T>(value: Arrayable<T>): T[];
35
+ declare function hashCode(s: string): string;
36
+
37
+ export { Arrayable, activeHead, asArray, createHead, dedupePlugin, getActiveHead, hashCode, hydratesStatePlugin, setActiveHead, sortPlugin, titleTemplatePlugin, useBase, useBodyAttrs, useHead, useHtmlAttrs, useLink, useMeta, useNoscript, useScript, useStyle, useTitle, useTitleTemplate };
package/dist/index.mjs ADDED
@@ -0,0 +1,250 @@
1
+ import { createHooks } from 'hookable';
2
+ import { tagDedupeKey, sortCriticalTags, resolveTitleTemplateFromTags, HasElementTags, ValidHeadTags, normaliseTag as normaliseTag$1 } from 'zhead';
3
+
4
+ let activeHead;
5
+ const setActiveHead = (head) => activeHead = head;
6
+ const getActiveHead = () => activeHead;
7
+
8
+ function defineHeadPlugin(plugin) {
9
+ return plugin;
10
+ }
11
+
12
+ const dedupePlugin = defineHeadPlugin({
13
+ hooks: {
14
+ "tag:normalise": function({ tag }) {
15
+ ["hid", "vmid", "key"].forEach((key) => {
16
+ if (tag.props[key]) {
17
+ tag.key = tag.props[key];
18
+ delete tag.props[key];
19
+ }
20
+ });
21
+ const dedupe = tag.key ? `${tag.tag}:${tag.key}` : tagDedupeKey(tag);
22
+ if (dedupe)
23
+ tag._d = dedupe;
24
+ },
25
+ "tags:resolve": function(ctx) {
26
+ const deduping = {};
27
+ ctx.tags.forEach((tag, i) => {
28
+ let dedupeKey = tag._d || tag._p || i;
29
+ const dupedTag = deduping[dedupeKey];
30
+ if (dupedTag) {
31
+ if (tag?.tagDuplicateStrategy === "merge") {
32
+ const oldProps = dupedTag.props;
33
+ ["class", "style"].forEach((key) => {
34
+ if (tag.props[key] && oldProps[key])
35
+ tag.props[key] = `${oldProps[key]} ${tag.props[key]}`;
36
+ });
37
+ deduping[dedupeKey].props = {
38
+ ...oldProps,
39
+ ...tag.props
40
+ };
41
+ return;
42
+ } else if (tag._e === dupedTag._e) {
43
+ dedupeKey = `${dedupeKey}:entry(${tag._e}:${tag._p})`;
44
+ tag._d = dedupeKey;
45
+ } else {
46
+ tag._p = dupedTag._p;
47
+ }
48
+ if (Object.keys(tag.props).length === 0 && !tag.children) {
49
+ delete deduping[dedupeKey];
50
+ return;
51
+ }
52
+ }
53
+ deduping[dedupeKey] = tag;
54
+ });
55
+ ctx.tags = Object.values(deduping);
56
+ }
57
+ }
58
+ });
59
+
60
+ const sortPlugin = defineHeadPlugin({
61
+ hooks: {
62
+ "tags:resolve": (ctx) => {
63
+ const tagIndexForKey = (key) => ctx.tags.find((tag) => tag._d === key)?._p;
64
+ for (const tag of ctx.tags) {
65
+ if (!tag?.tagPriority)
66
+ continue;
67
+ if (typeof tag.tagPriority === "number") {
68
+ tag._p = tag.tagPriority;
69
+ continue;
70
+ }
71
+ const modifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
72
+ for (const { prefix, offset } of modifiers) {
73
+ if (tag.tagPriority.startsWith(prefix)) {
74
+ const key = tag.tagPriority.replace(prefix, "");
75
+ const index = tagIndexForKey(key);
76
+ if (typeof index !== "undefined")
77
+ tag._p = index + offset;
78
+ }
79
+ }
80
+ }
81
+ ctx.tags.sort((a, b) => a._p - b._p).sort(sortCriticalTags);
82
+ }
83
+ }
84
+ });
85
+
86
+ const titleTemplatePlugin = defineHeadPlugin({
87
+ hooks: {
88
+ "tags:resolve": (ctx) => {
89
+ ctx.tags = resolveTitleTemplateFromTags(ctx.tags);
90
+ }
91
+ }
92
+ });
93
+
94
+ function asArray(value) {
95
+ return Array.isArray(value) ? value : [value];
96
+ }
97
+ function hashCode(s) {
98
+ let h = 9;
99
+ for (let i = 0; i < s.length; )
100
+ h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9);
101
+ return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 7).toLowerCase();
102
+ }
103
+
104
+ const hydratesStatePlugin = defineHeadPlugin({
105
+ hooks: {
106
+ "tag:normalise": (ctx) => {
107
+ const { tag, entry } = ctx;
108
+ if (!HasElementTags.includes(tag.tag))
109
+ return;
110
+ if (typeof tag._d === "undefined" && entry.mode === "server")
111
+ return;
112
+ tag._s = `data-h-${hashCode(tag._d || tag.tag + JSON.stringify(tag.props))}`;
113
+ tag.props[tag._s] = "";
114
+ }
115
+ }
116
+ });
117
+
118
+ function normaliseTag(tagName, input, entry) {
119
+ const tag = normaliseTag$1(tagName, input, { childrenKeys: ["innerHTML", "textContent"] });
120
+ tag._e = entry._i;
121
+ Object.keys(tag.props).filter((k) => k.startsWith("tag")).forEach((k) => {
122
+ tag[k] = tag.props[k];
123
+ delete tag.props[k];
124
+ });
125
+ if (tag.props.content && Array.isArray(tag.props.content)) {
126
+ return tag.props.content.map((v) => {
127
+ const newTag = { ...tag, props: { ...tag.props } };
128
+ newTag.props.content = v;
129
+ return newTag;
130
+ });
131
+ }
132
+ return tag;
133
+ }
134
+ function normaliseEntryTags(e) {
135
+ return Object.entries(e.input).filter(([k, v]) => typeof v !== "undefined" && ValidHeadTags.includes(k)).map(
136
+ ([k, value]) => asArray(value).map((props) => asArray(normaliseTag(k, props, e)))
137
+ ).flat(3).map((t, i) => {
138
+ t._p = (e._i << 8) + i++;
139
+ return t;
140
+ });
141
+ }
142
+
143
+ function createHead(options = {}) {
144
+ let entries = [];
145
+ let _sde = {};
146
+ let entryId = 0;
147
+ const hooks = createHooks();
148
+ if (options.hooks)
149
+ hooks.addHooks(options.hooks);
150
+ const plugins = [
151
+ dedupePlugin,
152
+ sortPlugin,
153
+ titleTemplatePlugin
154
+ ];
155
+ plugins.push(...options.plugins || []);
156
+ plugins.forEach((plugin) => hooks.addHooks(plugin.hooks || {}));
157
+ const head = {
158
+ entries,
159
+ _flushDomSideEffects() {
160
+ Object.values(_sde).forEach((fn) => fn());
161
+ _sde = {};
162
+ },
163
+ headEntries() {
164
+ return entries;
165
+ },
166
+ get hooks() {
167
+ return hooks;
168
+ },
169
+ push(input, options2) {
170
+ const _i = entryId++;
171
+ entries.push({
172
+ _i,
173
+ input,
174
+ _sde: {},
175
+ ...options2
176
+ });
177
+ return {
178
+ dispose() {
179
+ entries = entries.filter((e) => {
180
+ if (e._i !== _i)
181
+ return true;
182
+ _sde = {
183
+ ..._sde,
184
+ ...e._sde || {}
185
+ };
186
+ return false;
187
+ });
188
+ },
189
+ patch(input2) {
190
+ entries = entries.map((e) => {
191
+ e.input = e._i === _i ? input2 : e.input;
192
+ return e;
193
+ });
194
+ }
195
+ };
196
+ },
197
+ async resolveTags() {
198
+ await hooks.callHook("entries:resolve", head);
199
+ const tags = entries.map((entry) => normaliseEntryTags(entry)).flat();
200
+ for (const k in tags) {
201
+ const tagCtx = { tag: tags[k], entry: entries.find((e) => e._i === tags[k]._e) };
202
+ await hooks.callHook("tag:normalise", tagCtx);
203
+ tags[k] = tagCtx.tag;
204
+ }
205
+ const ctx = { tags };
206
+ await hooks.callHook("tags:beforeResolve", ctx);
207
+ await hooks.callHook("tags:resolve", ctx);
208
+ return ctx.tags;
209
+ }
210
+ };
211
+ setActiveHead(head);
212
+ return head;
213
+ }
214
+
215
+ function useHead(input, options = {}) {
216
+ const head = getActiveHead();
217
+ head.push(input, options);
218
+ }
219
+ const useTitle = (title) => {
220
+ useHead({ title });
221
+ };
222
+ const useMeta = (meta) => {
223
+ useHead({ meta: [meta] });
224
+ };
225
+ const useLink = (link) => {
226
+ useHead({ link: [link] });
227
+ };
228
+ const useScript = (script) => {
229
+ useHead({ script: [script] });
230
+ };
231
+ const useStyle = (style) => {
232
+ useHead({ style: [style] });
233
+ };
234
+ const useBase = (base) => {
235
+ useHead({ base });
236
+ };
237
+ const useHtmlAttrs = (attrs) => {
238
+ useHead({ htmlAttrs: attrs });
239
+ };
240
+ const useBodyAttrs = (attrs) => {
241
+ useHead({ bodyAttrs: attrs });
242
+ };
243
+ const useTitleTemplate = (titleTemplate) => {
244
+ useHead({ titleTemplate });
245
+ };
246
+ const useNoscript = (noscript) => {
247
+ useHead({ noscript: [noscript] });
248
+ };
249
+
250
+ export { activeHead, asArray, createHead, dedupePlugin, defineHeadPlugin, getActiveHead, hashCode, hydratesStatePlugin, setActiveHead, sortPlugin, titleTemplatePlugin, useBase, useBodyAttrs, useHead, useHtmlAttrs, useLink, useMeta, useNoscript, useScript, useStyle, useTitle, useTitleTemplate };
@@ -0,0 +1,3 @@
1
+ export { f as SSRHeadPayload, r as renderSSRHead } from './types-e30878c0.js';
2
+ import 'hookable';
3
+ import '@unhead/schema';
@@ -0,0 +1,13 @@
1
+ import { ssrRenderTags } from 'zhead';
2
+
3
+ const renderSSRHead = async (ctx) => {
4
+ const tags = await ctx.resolveTags();
5
+ const beforeRenderCtx = { tags };
6
+ await ctx.hooks.callHook("ssr:beforeRender", beforeRenderCtx);
7
+ const html = ssrRenderTags(beforeRenderCtx.tags);
8
+ const renderCXx = { tags, html };
9
+ await ctx.hooks.callHook("ssr:render", renderCXx);
10
+ return renderCXx.html;
11
+ };
12
+
13
+ export { renderSSRHead };
@@ -0,0 +1,91 @@
1
+ import { NestedHooks, Hookable } from 'hookable';
2
+ import { Head, HeadTag } from '@unhead/schema';
3
+
4
+ declare type HeadPlugin<O> = Omit<CreateHeadOptions<O>, 'plugins'>;
5
+ declare function defineHeadPlugin<O = Head>(plugin: HeadPlugin<O>): HeadPlugin<O>;
6
+
7
+ interface SSRHeadPayload {
8
+ headTags: string;
9
+ bodyTags: string;
10
+ bodyTagsOpen: string;
11
+ htmlAttrs: string;
12
+ bodyAttrs: string;
13
+ }
14
+ declare const renderSSRHead: <T extends HeadClient<any>>(ctx: T) => Promise<SSRHeadPayload>;
15
+
16
+ interface ActiveHeadEntry<T> {
17
+ patch: (resolvedInput: T) => void;
18
+ dispose: () => void;
19
+ }
20
+ declare type SideEffectsRecord = Record<string, () => void>;
21
+ declare type RuntimeMode = 'server' | 'client' | 'all';
22
+ interface HeadEntry<T> {
23
+ mode?: RuntimeMode;
24
+ input: T;
25
+ /**
26
+ * Head entry index
27
+ *
28
+ * @internal
29
+ */
30
+ _i: number;
31
+ /**
32
+ * Side effects
33
+ *
34
+ * @internal
35
+ */
36
+ _sde?: SideEffectsRecord;
37
+ }
38
+ declare type HookResult = Promise<void> | void;
39
+ interface DomRenderTagContext {
40
+ head: HeadClient;
41
+ tag: HeadTag;
42
+ $el: Element | null;
43
+ document: Document;
44
+ }
45
+ interface HeadHooks<T> {
46
+ 'entries:resolve': (head: HeadClient<T>) => HookResult;
47
+ 'tag:normalise': (ctx: {
48
+ tag: HeadTag;
49
+ entry: HeadEntry<T>;
50
+ }) => HookResult;
51
+ 'tags:beforeResolve': (ctx: {
52
+ tags: HeadTag[];
53
+ }) => HookResult;
54
+ 'tags:resolve': (ctx: {
55
+ tags: HeadTag[];
56
+ }) => HookResult;
57
+ 'render': (ctx: any) => HookResult;
58
+ 'dom:renderTag': (ctx: DomRenderTagContext) => HookResult;
59
+ 'dom:beforeRender': (ctx: {
60
+ head: HeadClient;
61
+ tags: HeadTag[];
62
+ document: Document;
63
+ }) => HookResult;
64
+ 'ssr:beforeRender': (ctx: {
65
+ tags: HeadTag[];
66
+ }) => HookResult;
67
+ 'ssr:render': (ctx: {
68
+ tags: HeadTag[];
69
+ html: SSRHeadPayload;
70
+ }) => HookResult;
71
+ }
72
+ interface CreateHeadOptions<T> {
73
+ plugins?: HeadPlugin<any>[];
74
+ hooks?: NestedHooks<HeadHooks<T>>;
75
+ }
76
+ interface HeadEntryOptions {
77
+ mode?: RuntimeMode;
78
+ }
79
+ interface HeadClient<T = Head> {
80
+ entries: HeadEntry<T>[];
81
+ hooks: Hookable<HeadHooks<T>>;
82
+ headEntries: () => HeadEntry<T>[];
83
+ push: (entry: T, options?: HeadEntryOptions) => ActiveHeadEntry<T>;
84
+ resolveTags: () => Promise<HeadTag[]>;
85
+ /**
86
+ * @internal
87
+ */
88
+ _flushDomSideEffects: () => void;
89
+ }
90
+
91
+ export { ActiveHeadEntry as A, CreateHeadOptions as C, DomRenderTagContext as D, HeadPlugin as H, RuntimeMode as R, SideEffectsRecord as S, HeadClient as a, HeadEntryOptions as b, HeadEntry as c, defineHeadPlugin as d, HeadHooks as e, SSRHeadPayload as f, renderSSRHead as r };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "unhead",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "packageManager": "pnpm@7.14.0",
6
+ "author": "Harlan Wilton <harlan@harlanzw.com>",
7
+ "license": "MIT",
8
+ "funding": "https://github.com/sponsors/harlan-zw",
9
+ "homepage": "https://github.com/harlan-zw/unhead#readme",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/harlan-zw/unhead.git",
13
+ "directory": "packages/unhead"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/harlan-zw/unhead/issues"
17
+ },
18
+ "sideEffects": false,
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.mjs"
23
+ },
24
+ "./server": {
25
+ "types": "./dist/server.d.ts",
26
+ "import": "./dist/server.mjs"
27
+ },
28
+ "./client": {
29
+ "types": "./dist/client.d.ts",
30
+ "import": "./dist/client.mjs"
31
+ }
32
+ },
33
+ "module": "dist/index.mjs",
34
+ "types": "dist/index.d.ts",
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "dependencies": {
39
+ "@unhead/schema": "0.0.1",
40
+ "hookable": "^5.4.1",
41
+ "unctx": "^2.0.2",
42
+ "zhead": "1.0.0-beta.4"
43
+ },
44
+ "scripts": {
45
+ "build": "unbuild .",
46
+ "stub": "unbuild . --stub"
47
+ }
48
+ }