@praxisjs/head 0.3.1 → 0.3.2
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/CHANGELOG.md +30 -0
- package/dist/head-stack.d.ts.map +1 -1
- package/dist/head-stack.js +74 -62
- package/dist/head-stack.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +15 -6
- package/src/head-stack.ts +70 -62
- package/dist/__tests__/head.test.d.ts +0 -2
- package/dist/__tests__/head.test.d.ts.map +0 -1
- package/dist/__tests__/head.test.js +0 -544
- package/dist/__tests__/head.test.js.map +0 -1
- package/dist/__tests__/internal.test.d.ts +0 -2
- package/dist/__tests__/internal.test.d.ts.map +0 -1
- package/dist/__tests__/internal.test.js +0 -32
- package/dist/__tests__/internal.test.js.map +0 -1
- package/src/__tests__/head.test.ts +0 -454
- package/src/__tests__/internal.test.ts +0 -38
- package/tsconfig.json +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @praxisjs/head
|
|
2
2
|
|
|
3
|
+
## 0.3.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 75115a9: Published packages no longer contain their tests: package builds compile `src` without `src/**/__tests__` (through a `tsconfig.build.json`), and each package's `files` leaves test sources and stale compiled tests out of the tarball. `pnpm typecheck` still type-checks the tests.
|
|
8
|
+
- 75115a9: Performance pass and bug fixes across the feature packages.
|
|
9
|
+
- `@praxisjs/router`: static paths are matched through an index and dynamic ones with a single regex run; `RouterView` keeps a shared layout mounted across navigations; new `router.isActive(path)` (used by `Link`, so a navigation re-evaluates only the links whose state changed) and `router.destroy()`. Fixes regex characters in route paths (`.`, `(`, `+`...) being treated as patterns, and a router replaced by `createRouter()` still reacting to `popstate`. `@Route` no longer wraps the class, and the field decorators share one accessor.
|
|
10
|
+
- `@praxisjs/store`: plugin-wrapped actions keep their identity across reads, extensions are bound once, and writing a field inside an effect no longer subscribes the effect to it. `constructor` and inherited `Object.prototype` members are no longer reported as actions. `@Store` caches the singleton.
|
|
11
|
+
- `@praxisjs/motion`: `@Tween` and `@Spring` share one accessor per field and no longer start a frame loop at construction; `tween()`/`spring()` react to target writes without an effect. Fixes `reset()` leaving a tween marked as playing.
|
|
12
|
+
- `@praxisjs/di`: cached singletons and tokens resolve without allocating, `@Injectable` registers the class itself instead of a subclass, and a dependency cycle through `inject()` calls in constructors now reports `[DI] Circular dependency detected` instead of overflowing the stack.
|
|
13
|
+
- `@praxisjs/fsm`: `send()` and `reset()` no longer subscribe the calling effect to the machine state, transitions allocate nothing per send, and `@StateMachine` shares one accessor.
|
|
14
|
+
- `@praxisjs/head`: tag diffing without `JSON.stringify` or a full `<head>` query per update; updating or removing an entry below the top one leaves the document alone, and removing an unknown id no longer resets the title.
|
|
15
|
+
- `@praxisjs/content`: each entry is loaded, parsed and rendered once and shared by `getCollection`, `getPage` and `getEntry`; `getEntry` looks the slug up in an index; schema defaults are built once per class.
|
|
16
|
+
|
|
17
|
+
- 75115a9: Smaller production bundles. The framework and feature packages are marked `"sideEffects": false`, so bundlers leave out the modules an app does not use (`For`/`Index` and `selector()` were kept in every bundle by their shape keepers). `@praxisjs/core`'s graph gates its observer instrumentation on a boolean that minifiers fold away when no observer is ever installed. `@praxisjs/runtime` passes `Index`'s positional update to the list view, so `For`-only apps leave it out, and builds its event prop table from camel-case names. `@praxisjs/devtools` is built one file per module and its decorators check `process.env.NODE_ENV` inline, so `@Trace`/`@Debug` in production code no longer bundle the registry and the panel. `@praxisjs/vite-plugin` serves TypeScript's decorator helpers and the claimed fields' helper from one virtual module (`virtual:praxisjs/decorators`) instead of writing them into every decorated file, and replaces the identical copies `tsc` wrote into ES module libraries.
|
|
18
|
+
- Updated dependencies [75115a9]
|
|
19
|
+
- Updated dependencies [75115a9]
|
|
20
|
+
- Updated dependencies [75115a9]
|
|
21
|
+
- Updated dependencies [75115a9]
|
|
22
|
+
- Updated dependencies [75115a9]
|
|
23
|
+
- Updated dependencies [75115a9]
|
|
24
|
+
- Updated dependencies [75115a9]
|
|
25
|
+
- Updated dependencies [75115a9]
|
|
26
|
+
- Updated dependencies [75115a9]
|
|
27
|
+
- Updated dependencies [75115a9]
|
|
28
|
+
- Updated dependencies [75115a9]
|
|
29
|
+
- @praxisjs/decorators@2.0.0
|
|
30
|
+
- @praxisjs/core@2.3.0
|
|
31
|
+
- @praxisjs/shared@0.3.2
|
|
32
|
+
|
|
3
33
|
## 0.3.1
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/dist/head-stack.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"head-stack.d.ts","sourceRoot":"","sources":["../src/head-stack.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,WAAW,GAAG,iBAAiB,CAAC;CAC/C;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;IACjB,EAAE,CAAC,EAAE;QACH,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;
|
|
1
|
+
{"version":3,"file":"head-stack.d.ts","sourceRoot":"","sources":["../src/head-stack.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,WAAW,GAAG,iBAAiB,CAAC;CAC/C;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;IACjB,EAAE,CAAC,EAAE;QACH,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAgBD,qFAAqF;AACrF,eAAO,MAAM,WAAW,2CAAY,CAAC;AAMrC,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAe7D;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAQ3C;AA8GD;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAQrC"}
|
package/dist/head-stack.js
CHANGED
|
@@ -3,18 +3,29 @@ const ATTR = "data-praxis-head";
|
|
|
3
3
|
const _stack = [];
|
|
4
4
|
let _managed = new Map();
|
|
5
5
|
let _initialTitle;
|
|
6
|
+
// Tags a prerender left in the document are not in `_managed`; they are swept once, on the first
|
|
7
|
+
// sync, instead of querying the whole <head> on every update.
|
|
8
|
+
let _swept = false;
|
|
6
9
|
/** Increments on every head update. Read reactively to subscribe to head changes. */
|
|
7
10
|
export const headVersion = signal(0);
|
|
11
|
+
function _bump() {
|
|
12
|
+
headVersion.update((n) => n + 1);
|
|
13
|
+
}
|
|
8
14
|
export function pushHead(id, config) {
|
|
9
15
|
if (typeof document === "undefined")
|
|
10
16
|
return;
|
|
11
17
|
_initialTitle ??= document.title;
|
|
12
18
|
const idx = _stack.findIndex((e) => e.id === id);
|
|
13
|
-
if (idx
|
|
14
|
-
_stack
|
|
19
|
+
if (idx === -1) {
|
|
20
|
+
_stack.push({ id, config });
|
|
15
21
|
}
|
|
16
22
|
else {
|
|
17
|
-
_stack.
|
|
23
|
+
_stack[idx].config = config;
|
|
24
|
+
// Only the top entry is shown; one below it changes nothing in the document yet.
|
|
25
|
+
if (idx !== _stack.length - 1) {
|
|
26
|
+
_bump();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
18
29
|
}
|
|
19
30
|
_apply();
|
|
20
31
|
}
|
|
@@ -22,75 +33,68 @@ export function removeHead(id) {
|
|
|
22
33
|
if (typeof document === "undefined")
|
|
23
34
|
return;
|
|
24
35
|
const idx = _stack.findIndex((e) => e.id === id);
|
|
25
|
-
if (idx
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
if (idx === -1)
|
|
37
|
+
return;
|
|
38
|
+
const wasTop = idx === _stack.length - 1;
|
|
39
|
+
_stack.splice(idx, 1);
|
|
40
|
+
if (wasTop)
|
|
41
|
+
_apply();
|
|
42
|
+
else
|
|
43
|
+
_bump();
|
|
44
|
+
}
|
|
45
|
+
function _meta(specs, attr, key, content) {
|
|
46
|
+
if (content != null)
|
|
47
|
+
specs.push({ tag: "meta", attrs: [attr, key, "content", content] });
|
|
28
48
|
}
|
|
29
49
|
function _collect(config) {
|
|
30
50
|
const specs = [];
|
|
31
|
-
|
|
32
|
-
specs.push({ tag: "meta", attrs: [["name", key], ["content", content]] });
|
|
33
|
-
};
|
|
34
|
-
const property = (key, content) => {
|
|
35
|
-
specs.push({ tag: "meta", attrs: [["property", key], ["content", content]] });
|
|
36
|
-
};
|
|
37
|
-
if (config.description != null)
|
|
38
|
-
name("description", config.description);
|
|
51
|
+
_meta(specs, "name", "description", config.description);
|
|
39
52
|
if (config.canonical != null) {
|
|
40
|
-
specs.push({ tag: "link", attrs: [
|
|
53
|
+
specs.push({ tag: "link", attrs: ["rel", "canonical", "href", config.canonical] });
|
|
41
54
|
}
|
|
42
55
|
for (const link of config.preload ?? []) {
|
|
43
|
-
const attrs = [
|
|
56
|
+
const attrs = ["rel", "preload", "href", link.href, "as", link.as];
|
|
44
57
|
if (link.type != null)
|
|
45
|
-
attrs.push(
|
|
58
|
+
attrs.push("type", link.type);
|
|
46
59
|
if (link.crossOrigin != null)
|
|
47
|
-
attrs.push(
|
|
60
|
+
attrs.push("crossorigin", link.crossOrigin);
|
|
48
61
|
specs.push({ tag: "link", attrs });
|
|
49
62
|
}
|
|
50
63
|
for (const link of config.prefetch ?? []) {
|
|
51
|
-
const attrs = [
|
|
64
|
+
const attrs = ["rel", "prefetch", "href", link.href];
|
|
52
65
|
if (link.as != null)
|
|
53
|
-
attrs.push(
|
|
66
|
+
attrs.push("as", link.as);
|
|
54
67
|
specs.push({ tag: "link", attrs });
|
|
55
68
|
}
|
|
56
69
|
for (const tag of config.meta ?? []) {
|
|
57
70
|
if (tag.property != null)
|
|
58
|
-
property
|
|
71
|
+
_meta(specs, "property", tag.property, tag.content);
|
|
59
72
|
else if (tag.name != null)
|
|
60
|
-
name
|
|
73
|
+
_meta(specs, "name", tag.name, tag.content);
|
|
61
74
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
"og:site_name": og.siteName,
|
|
71
|
-
});
|
|
75
|
+
const og = config.og;
|
|
76
|
+
if (og != null) {
|
|
77
|
+
_meta(specs, "property", "og:title", og.title);
|
|
78
|
+
_meta(specs, "property", "og:description", og.description);
|
|
79
|
+
_meta(specs, "property", "og:image", og.image);
|
|
80
|
+
_meta(specs, "property", "og:url", og.url);
|
|
81
|
+
_meta(specs, "property", "og:type", og.type);
|
|
82
|
+
_meta(specs, "property", "og:site_name", og.siteName);
|
|
72
83
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"twitter:image": tw.image,
|
|
80
|
-
});
|
|
84
|
+
const tw = config.twitter;
|
|
85
|
+
if (tw != null) {
|
|
86
|
+
_meta(specs, "name", "twitter:card", tw.card);
|
|
87
|
+
_meta(specs, "name", "twitter:title", tw.title);
|
|
88
|
+
_meta(specs, "name", "twitter:description", tw.description);
|
|
89
|
+
_meta(specs, "name", "twitter:image", tw.image);
|
|
81
90
|
}
|
|
82
91
|
return specs;
|
|
83
92
|
}
|
|
84
|
-
function _collectMap(add, map) {
|
|
85
|
-
for (const [key, value] of Object.entries(map)) {
|
|
86
|
-
if (value != null)
|
|
87
|
-
add(key, value);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
93
|
function _create(spec) {
|
|
91
94
|
const el = document.createElement(spec.tag);
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
const { attrs } = spec;
|
|
96
|
+
for (let i = 0; i < attrs.length; i += 2)
|
|
97
|
+
el.setAttribute(attrs[i], attrs[i + 1]);
|
|
94
98
|
el.setAttribute(ATTR, "");
|
|
95
99
|
return el;
|
|
96
100
|
}
|
|
@@ -98,33 +102,40 @@ function _create(spec) {
|
|
|
98
102
|
// removed and recreated on every update; only the difference is added or removed.
|
|
99
103
|
function _syncTags(specs) {
|
|
100
104
|
const next = new Map();
|
|
101
|
-
const occurrences = new Map();
|
|
102
105
|
for (const spec of specs) {
|
|
103
|
-
const base =
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
const base = `${spec.tag}\0${spec.attrs.join("\0")}`;
|
|
107
|
+
let key = base;
|
|
108
|
+
for (let n = 2; next.has(key); n++)
|
|
109
|
+
key = `${base}#${String(n)}`;
|
|
107
110
|
let el = _managed.get(key);
|
|
108
|
-
if (
|
|
111
|
+
if (el?.isConnected) {
|
|
112
|
+
_managed.delete(key);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
109
115
|
el = _create(spec);
|
|
110
116
|
document.head.appendChild(el);
|
|
111
117
|
}
|
|
112
118
|
next.set(key, el);
|
|
113
119
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
for (const stale of _managed.values())
|
|
121
|
+
stale.remove();
|
|
122
|
+
if (!_swept) {
|
|
123
|
+
_swept = true;
|
|
124
|
+
const keep = new Set(next.values());
|
|
125
|
+
document.head.querySelectorAll(`[${ATTR}]`).forEach((el) => {
|
|
126
|
+
if (!keep.has(el))
|
|
127
|
+
el.remove();
|
|
128
|
+
});
|
|
129
|
+
}
|
|
120
130
|
_managed = next;
|
|
121
131
|
}
|
|
122
132
|
function _apply() {
|
|
123
|
-
|
|
133
|
+
_bump();
|
|
124
134
|
const top = _stack.at(-1);
|
|
125
135
|
if (top === undefined) {
|
|
126
136
|
_syncTags([]);
|
|
127
|
-
|
|
137
|
+
// Set by the push that the removal emptying the stack undid.
|
|
138
|
+
document.title = _initialTitle;
|
|
128
139
|
return;
|
|
129
140
|
}
|
|
130
141
|
const { config } = top;
|
|
@@ -142,6 +153,7 @@ export function resetHeadState() {
|
|
|
142
153
|
_stack.length = 0;
|
|
143
154
|
_managed = new Map();
|
|
144
155
|
_initialTitle = undefined;
|
|
156
|
+
_swept = false;
|
|
145
157
|
if (typeof document !== "undefined") {
|
|
146
158
|
document.querySelectorAll(`[${ATTR}]`).forEach((el) => { el.remove(); });
|
|
147
159
|
}
|
package/dist/head-stack.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"head-stack.js","sourceRoot":"","sources":["../src/head-stack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AA2CjD,MAAM,IAAI,GAAG,kBAAkB,CAAC;AAOhC,MAAM,MAAM,GAAgB,EAAE,CAAC;AAC/B,IAAI,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;AAC9C,IAAI,aAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"head-stack.js","sourceRoot":"","sources":["../src/head-stack.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ import { type RootComponent } from "@praxisjs/core/internal";
|
|
|
2
2
|
import { type HeadConfig, type MetaTag, type LinkPreload, type LinkPrefetch, headVersion } from "./head-stack";
|
|
3
3
|
export type { HeadConfig, MetaTag, LinkPreload, LinkPrefetch };
|
|
4
4
|
export { headVersion };
|
|
5
|
-
export declare function Head(config: HeadConfig | ((self: any) => HeadConfig)): <T extends new (...args: any[]) => RootComponent<Record<string, any>>>(constructor: T,
|
|
5
|
+
export declare function Head(config: HeadConfig | ((self: any) => HeadConfig)): <T extends new (...args: any[]) => RootComponent<Record<string, any>>>(constructor: T, context: ClassDecoratorContext) => T;
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,aAAa,EAAG,MAAM,yBAAyB,CAAC;AAOtE,OAAO,EAAwB,KAAK,UAAU,EAAE,KAAK,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAErI,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,CAAC;AA4BvB,wBAAgB,IAAI,CAElB,MAAM,EAAE,UAAU,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,KAAK,UAAU,CAAC
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,aAAa,EAAG,MAAM,yBAAyB,CAAC;AAOtE,OAAO,EAAwB,KAAK,UAAU,EAAE,KAAK,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAErI,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,CAAC;AA4BvB,wBAAgB,IAAI,CAElB,MAAM,EAAE,UAAU,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,KAAK,UAAU,CAAC,+HAKjD"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisjs/head",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
5
6
|
"main": "./dist/index.js",
|
|
6
7
|
"types": "./dist/index.d.ts",
|
|
7
8
|
"exports": {
|
|
@@ -14,16 +15,24 @@
|
|
|
14
15
|
"types": "./dist/internal.d.ts"
|
|
15
16
|
}
|
|
16
17
|
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"!dist/**/__tests__",
|
|
22
|
+
"!src/**/__tests__",
|
|
23
|
+
"CHANGELOG.md"
|
|
24
|
+
],
|
|
17
25
|
"devDependencies": {
|
|
18
26
|
"typescript": "^6.0.3"
|
|
19
27
|
},
|
|
20
28
|
"dependencies": {
|
|
21
|
-
"@praxisjs/
|
|
22
|
-
"@praxisjs/
|
|
23
|
-
"@praxisjs/shared": "0.3.
|
|
29
|
+
"@praxisjs/core": "2.3.0",
|
|
30
|
+
"@praxisjs/decorators": "2.0.0",
|
|
31
|
+
"@praxisjs/shared": "0.3.2"
|
|
24
32
|
},
|
|
25
33
|
"scripts": {
|
|
26
|
-
"build": "tsc",
|
|
27
|
-
"dev": "tsc --watch"
|
|
34
|
+
"build": "tsc -p tsconfig.build.json",
|
|
35
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
36
|
+
"typecheck": "tsc --noEmit"
|
|
28
37
|
}
|
|
29
38
|
}
|
package/src/head-stack.ts
CHANGED
|
@@ -51,18 +51,30 @@ interface HeadEntry {
|
|
|
51
51
|
const _stack: HeadEntry[] = [];
|
|
52
52
|
let _managed = new Map<string, HTMLElement>();
|
|
53
53
|
let _initialTitle: string | undefined;
|
|
54
|
+
// Tags a prerender left in the document are not in `_managed`; they are swept once, on the first
|
|
55
|
+
// sync, instead of querying the whole <head> on every update.
|
|
56
|
+
let _swept = false;
|
|
54
57
|
|
|
55
58
|
/** Increments on every head update. Read reactively to subscribe to head changes. */
|
|
56
59
|
export const headVersion = signal(0);
|
|
57
60
|
|
|
61
|
+
function _bump(): void {
|
|
62
|
+
headVersion.update((n) => n + 1);
|
|
63
|
+
}
|
|
64
|
+
|
|
58
65
|
export function pushHead(id: symbol, config: HeadConfig): void {
|
|
59
66
|
if (typeof document === "undefined") return;
|
|
60
67
|
_initialTitle ??= document.title;
|
|
61
68
|
const idx = _stack.findIndex((e) => e.id === id);
|
|
62
|
-
if (idx
|
|
63
|
-
_stack[idx].config = config;
|
|
64
|
-
} else {
|
|
69
|
+
if (idx === -1) {
|
|
65
70
|
_stack.push({ id, config });
|
|
71
|
+
} else {
|
|
72
|
+
_stack[idx].config = config;
|
|
73
|
+
// Only the top entry is shown; one below it changes nothing in the document yet.
|
|
74
|
+
if (idx !== _stack.length - 1) {
|
|
75
|
+
_bump();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
66
78
|
}
|
|
67
79
|
_apply();
|
|
68
80
|
}
|
|
@@ -70,79 +82,69 @@ export function pushHead(id: symbol, config: HeadConfig): void {
|
|
|
70
82
|
export function removeHead(id: symbol): void {
|
|
71
83
|
if (typeof document === "undefined") return;
|
|
72
84
|
const idx = _stack.findIndex((e) => e.id === id);
|
|
73
|
-
if (idx
|
|
74
|
-
|
|
85
|
+
if (idx === -1) return;
|
|
86
|
+
const wasTop = idx === _stack.length - 1;
|
|
87
|
+
_stack.splice(idx, 1);
|
|
88
|
+
if (wasTop) _apply();
|
|
89
|
+
else _bump();
|
|
75
90
|
}
|
|
76
91
|
|
|
77
92
|
interface TagSpec {
|
|
78
93
|
tag: "meta" | "link";
|
|
79
|
-
|
|
94
|
+
/** Attribute names and values, alternating. */
|
|
95
|
+
attrs: string[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function _meta(specs: TagSpec[], attr: "name" | "property", key: string, content: string | undefined): void {
|
|
99
|
+
if (content != null) specs.push({ tag: "meta", attrs: [attr, key, "content", content] });
|
|
80
100
|
}
|
|
81
101
|
|
|
82
102
|
function _collect(config: HeadConfig): TagSpec[] {
|
|
83
103
|
const specs: TagSpec[] = [];
|
|
84
|
-
const name = (key: string, content: string): void => {
|
|
85
|
-
specs.push({ tag: "meta", attrs: [["name", key], ["content", content]] });
|
|
86
|
-
};
|
|
87
|
-
const property = (key: string, content: string): void => {
|
|
88
|
-
specs.push({ tag: "meta", attrs: [["property", key], ["content", content]] });
|
|
89
|
-
};
|
|
90
104
|
|
|
91
|
-
|
|
105
|
+
_meta(specs, "name", "description", config.description);
|
|
92
106
|
if (config.canonical != null) {
|
|
93
|
-
specs.push({ tag: "link", attrs: [
|
|
107
|
+
specs.push({ tag: "link", attrs: ["rel", "canonical", "href", config.canonical] });
|
|
94
108
|
}
|
|
95
109
|
for (const link of config.preload ?? []) {
|
|
96
|
-
const attrs
|
|
97
|
-
if (link.type != null) attrs.push(
|
|
98
|
-
if (link.crossOrigin != null) attrs.push(
|
|
110
|
+
const attrs = ["rel", "preload", "href", link.href, "as", link.as];
|
|
111
|
+
if (link.type != null) attrs.push("type", link.type);
|
|
112
|
+
if (link.crossOrigin != null) attrs.push("crossorigin", link.crossOrigin);
|
|
99
113
|
specs.push({ tag: "link", attrs });
|
|
100
114
|
}
|
|
101
115
|
for (const link of config.prefetch ?? []) {
|
|
102
|
-
const attrs
|
|
103
|
-
if (link.as != null) attrs.push(
|
|
116
|
+
const attrs = ["rel", "prefetch", "href", link.href];
|
|
117
|
+
if (link.as != null) attrs.push("as", link.as);
|
|
104
118
|
specs.push({ tag: "link", attrs });
|
|
105
119
|
}
|
|
106
120
|
for (const tag of config.meta ?? []) {
|
|
107
|
-
if (tag.property != null) property
|
|
108
|
-
else if (tag.name != null) name
|
|
121
|
+
if (tag.property != null) _meta(specs, "property", tag.property, tag.content);
|
|
122
|
+
else if (tag.name != null) _meta(specs, "name", tag.name, tag.content);
|
|
109
123
|
}
|
|
110
124
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
"og:site_name": og.siteName,
|
|
120
|
-
});
|
|
125
|
+
const og = config.og;
|
|
126
|
+
if (og != null) {
|
|
127
|
+
_meta(specs, "property", "og:title", og.title);
|
|
128
|
+
_meta(specs, "property", "og:description", og.description);
|
|
129
|
+
_meta(specs, "property", "og:image", og.image);
|
|
130
|
+
_meta(specs, "property", "og:url", og.url);
|
|
131
|
+
_meta(specs, "property", "og:type", og.type);
|
|
132
|
+
_meta(specs, "property", "og:site_name", og.siteName);
|
|
121
133
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
"twitter:image": tw.image,
|
|
129
|
-
});
|
|
134
|
+
const tw = config.twitter;
|
|
135
|
+
if (tw != null) {
|
|
136
|
+
_meta(specs, "name", "twitter:card", tw.card);
|
|
137
|
+
_meta(specs, "name", "twitter:title", tw.title);
|
|
138
|
+
_meta(specs, "name", "twitter:description", tw.description);
|
|
139
|
+
_meta(specs, "name", "twitter:image", tw.image);
|
|
130
140
|
}
|
|
131
141
|
return specs;
|
|
132
142
|
}
|
|
133
143
|
|
|
134
|
-
function _collectMap(
|
|
135
|
-
add: (key: string, content: string) => void,
|
|
136
|
-
map: Record<string, string | undefined>,
|
|
137
|
-
): void {
|
|
138
|
-
for (const [key, value] of Object.entries(map)) {
|
|
139
|
-
if (value != null) add(key, value);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
144
|
function _create(spec: TagSpec): HTMLElement {
|
|
144
145
|
const el = document.createElement(spec.tag);
|
|
145
|
-
|
|
146
|
+
const { attrs } = spec;
|
|
147
|
+
for (let i = 0; i < attrs.length; i += 2) el.setAttribute(attrs[i], attrs[i + 1]);
|
|
146
148
|
el.setAttribute(ATTR, "");
|
|
147
149
|
return el;
|
|
148
150
|
}
|
|
@@ -151,37 +153,42 @@ function _create(spec: TagSpec): HTMLElement {
|
|
|
151
153
|
// removed and recreated on every update; only the difference is added or removed.
|
|
152
154
|
function _syncTags(specs: TagSpec[]): void {
|
|
153
155
|
const next = new Map<string, HTMLElement>();
|
|
154
|
-
const occurrences = new Map<string, number>();
|
|
155
156
|
|
|
156
157
|
for (const spec of specs) {
|
|
157
|
-
const base =
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const key = `${base}#${String(n)}`;
|
|
158
|
+
const base = `${spec.tag}\0${spec.attrs.join("\0")}`;
|
|
159
|
+
let key = base;
|
|
160
|
+
for (let n = 2; next.has(key); n++) key = `${base}#${String(n)}`;
|
|
161
161
|
|
|
162
162
|
let el = _managed.get(key);
|
|
163
|
-
if (
|
|
163
|
+
if (el?.isConnected) {
|
|
164
|
+
_managed.delete(key);
|
|
165
|
+
} else {
|
|
164
166
|
el = _create(spec);
|
|
165
167
|
document.head.appendChild(el);
|
|
166
168
|
}
|
|
167
169
|
next.set(key, el);
|
|
168
170
|
}
|
|
169
171
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
for (const stale of _managed.values()) stale.remove();
|
|
173
|
+
|
|
174
|
+
if (!_swept) {
|
|
175
|
+
_swept = true;
|
|
176
|
+
const keep = new Set<Element>(next.values());
|
|
177
|
+
document.head.querySelectorAll(`[${ATTR}]`).forEach((el) => {
|
|
178
|
+
if (!keep.has(el)) el.remove();
|
|
179
|
+
});
|
|
180
|
+
}
|
|
175
181
|
_managed = next;
|
|
176
182
|
}
|
|
177
183
|
|
|
178
184
|
function _apply(): void {
|
|
179
|
-
|
|
185
|
+
_bump();
|
|
180
186
|
|
|
181
187
|
const top = _stack.at(-1);
|
|
182
188
|
if (top === undefined) {
|
|
183
189
|
_syncTags([]);
|
|
184
|
-
|
|
190
|
+
// Set by the push that the removal emptying the stack undid.
|
|
191
|
+
document.title = _initialTitle as string;
|
|
185
192
|
return;
|
|
186
193
|
}
|
|
187
194
|
|
|
@@ -200,6 +207,7 @@ export function resetHeadState(): void {
|
|
|
200
207
|
_stack.length = 0;
|
|
201
208
|
_managed = new Map();
|
|
202
209
|
_initialTitle = undefined;
|
|
210
|
+
_swept = false;
|
|
203
211
|
if (typeof document !== "undefined") {
|
|
204
212
|
document.querySelectorAll(`[${ATTR}]`).forEach((el) => { el.remove(); });
|
|
205
213
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"head.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/head.test.ts"],"names":[],"mappings":""}
|