@praxisjs/runtime 0.7.1 → 0.8.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.
- package/CHANGELOG.md +39 -0
- package/dist/children.d.ts.map +1 -1
- package/dist/children.js +2 -1
- package/dist/children.js.map +1 -1
- package/dist/component.d.ts +2 -3
- package/dist/component.d.ts.map +1 -1
- package/dist/component.js +133 -60
- package/dist/component.js.map +1 -1
- package/dist/context.d.ts +13 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +24 -2
- package/dist/context.js.map +1 -1
- package/dist/dom/constants.d.ts +1 -1
- package/dist/dom/constants.d.ts.map +1 -1
- package/dist/dom/constants.js +24 -83
- package/dist/dom/constants.js.map +1 -1
- package/dist/dom/create.d.ts +1 -1
- package/dist/dom/create.d.ts.map +1 -1
- package/dist/dom/create.js +3 -3
- package/dist/dom/create.js.map +1 -1
- package/dist/dom/events.d.ts +28 -0
- package/dist/dom/events.d.ts.map +1 -1
- package/dist/dom/events.js +163 -17
- package/dist/dom/events.js.map +1 -1
- package/dist/dom/props.d.ts +4 -0
- package/dist/dom/props.d.ts.map +1 -1
- package/dist/dom/props.js +61 -8
- package/dist/dom/props.js.map +1 -1
- package/dist/dom/range.d.ts +4 -0
- package/dist/dom/range.d.ts.map +1 -0
- package/dist/dom/range.js +43 -0
- package/dist/dom/range.js.map +1 -0
- package/dist/dom/remove.d.ts.map +1 -1
- package/dist/dom/remove.js +2 -8
- package/dist/dom/remove.js.map +1 -1
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +7 -5
- package/dist/element.js.map +1 -1
- package/dist/hmr.d.ts +35 -0
- package/dist/hmr.d.ts.map +1 -0
- package/dist/hmr.js +85 -0
- package/dist/hmr.js.map +1 -0
- package/dist/hydration-context.d.ts +9 -1
- package/dist/hydration-context.d.ts.map +1 -1
- package/dist/hydration-context.js +34 -13
- package/dist/hydration-context.js.map +1 -1
- package/dist/hydration.d.ts.map +1 -1
- package/dist/hydration.js +4 -11
- package/dist/hydration.js.map +1 -1
- package/dist/index.d.ts +16 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -7
- package/dist/index.js.map +1 -1
- package/dist/list.d.ts +72 -0
- package/dist/list.d.ts.map +1 -0
- package/dist/list.js +786 -0
- package/dist/list.js.map +1 -0
- package/dist/portal.d.ts.map +1 -1
- package/dist/portal.js +3 -0
- package/dist/portal.js.map +1 -1
- package/dist/reactive.d.ts +2 -2
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +109 -34
- package/dist/reactive.js.map +1 -1
- package/dist/scope.d.ts +14 -4
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +55 -15
- package/dist/scope.js.map +1 -1
- package/dist/template.d.ts +48 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +270 -0
- package/dist/template.js.map +1 -0
- package/package.json +15 -6
- package/src/children.ts +2 -1
- package/src/component.ts +135 -63
- package/src/context.ts +35 -4
- package/src/dom/constants.ts +25 -82
- package/src/dom/create.ts +3 -3
- package/src/dom/events.ts +184 -16
- package/src/dom/props.ts +70 -9
- package/src/dom/range.ts +45 -0
- package/src/dom/remove.ts +3 -7
- package/src/element.ts +8 -6
- package/src/hmr.ts +110 -0
- package/src/hydration-context.ts +34 -13
- package/src/hydration.ts +4 -9
- package/src/index.ts +54 -7
- package/src/list.ts +886 -0
- package/src/portal.ts +3 -0
- package/src/reactive.ts +147 -40
- package/src/scope.ts +63 -16
- package/src/template.ts +271 -0
- package/dist/__tests__/children.test.d.ts +0 -2
- package/dist/__tests__/children.test.d.ts.map +0 -1
- package/dist/__tests__/children.test.js +0 -241
- package/dist/__tests__/children.test.js.map +0 -1
- package/dist/__tests__/component.test.d.ts +0 -2
- package/dist/__tests__/component.test.d.ts.map +0 -1
- package/dist/__tests__/component.test.js +0 -347
- package/dist/__tests__/component.test.js.map +0 -1
- package/dist/__tests__/context.test.d.ts +0 -2
- package/dist/__tests__/context.test.d.ts.map +0 -1
- package/dist/__tests__/context.test.js +0 -111
- package/dist/__tests__/context.test.js.map +0 -1
- package/dist/__tests__/dom.test.d.ts +0 -2
- package/dist/__tests__/dom.test.d.ts.map +0 -1
- package/dist/__tests__/dom.test.js +0 -429
- package/dist/__tests__/dom.test.js.map +0 -1
- package/dist/__tests__/hydration.test.d.ts +0 -2
- package/dist/__tests__/hydration.test.d.ts.map +0 -1
- package/dist/__tests__/hydration.test.js +0 -299
- package/dist/__tests__/hydration.test.js.map +0 -1
- package/dist/__tests__/portal.test.d.ts +0 -2
- package/dist/__tests__/portal.test.d.ts.map +0 -1
- package/dist/__tests__/portal.test.js +0 -157
- package/dist/__tests__/portal.test.js.map +0 -1
- package/dist/__tests__/remove.test.d.ts +0 -2
- package/dist/__tests__/remove.test.d.ts.map +0 -1
- package/dist/__tests__/remove.test.js +0 -45
- package/dist/__tests__/remove.test.js.map +0 -1
- package/dist/__tests__/render.test.d.ts +0 -2
- package/dist/__tests__/render.test.d.ts.map +0 -1
- package/dist/__tests__/render.test.js +0 -97
- package/dist/__tests__/render.test.js.map +0 -1
- package/dist/__tests__/scope.test.d.ts +0 -2
- package/dist/__tests__/scope.test.d.ts.map +0 -1
- package/dist/__tests__/scope.test.js +0 -110
- package/dist/__tests__/scope.test.js.map +0 -1
- package/src/__tests__/children.test.ts +0 -276
- package/src/__tests__/component.test.ts +0 -381
- package/src/__tests__/context.test.ts +0 -128
- package/src/__tests__/dom.test.ts +0 -483
- package/src/__tests__/hydration.test.ts +0 -360
- package/src/__tests__/portal.test.ts +0 -177
- package/src/__tests__/remove.test.ts +0 -51
- package/src/__tests__/render.test.ts +0 -119
- package/src/__tests__/scope.test.ts +0 -120
- package/tsconfig.json +0 -8
package/dist/template.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { mountChildren } from "./children";
|
|
2
|
+
import { getCurrentScope } from "./context";
|
|
3
|
+
import { createElement } from "./dom/create";
|
|
4
|
+
import { addDelegatedEvent, delegatedProp } from "./dom/events";
|
|
5
|
+
import { applyProp, bindClass, hasWritableProperty, setProp } from "./dom/props";
|
|
6
|
+
import { recording, recordProp } from "./hydration-context";
|
|
7
|
+
import { mountReactive } from "./reactive";
|
|
8
|
+
/** A reactive child's position: the comment it inserts its nodes before. */
|
|
9
|
+
export const MARKER = 0;
|
|
10
|
+
let pendingPatches = null;
|
|
11
|
+
// Templates are built in the inert document that <template> contents live in: cloning there and
|
|
12
|
+
// letting the insertion adopt the clone is cheaper than cloning inside the live document. Custom
|
|
13
|
+
// elements are the exception, since only the live document upgrades them, so a template that
|
|
14
|
+
// contains one is built (and cloned) there instead.
|
|
15
|
+
let inertDocument = null;
|
|
16
|
+
// null is the live document, looked up when used: the module is imported before any DOM exists
|
|
17
|
+
// when a server renderer installs one afterwards.
|
|
18
|
+
let buildDocument = null;
|
|
19
|
+
let sawCustomElement = false;
|
|
20
|
+
// The global document the cached templates were built for, and a counter that tells every template
|
|
21
|
+
// to build again. A server renderer may install a new window (a new global document) for each run:
|
|
22
|
+
// nodes cloned from another window's templates fail every `instanceof Node` check and are dropped.
|
|
23
|
+
let templatesDocument = null;
|
|
24
|
+
let generation = 0;
|
|
25
|
+
function inert() {
|
|
26
|
+
templatesDocument ??= document;
|
|
27
|
+
return (inertDocument ??= document.createElement("template").content.ownerDocument);
|
|
28
|
+
}
|
|
29
|
+
/** Called by render(): templates built for another global document are built again on next use. */
|
|
30
|
+
export function syncTemplateDocument() {
|
|
31
|
+
if (templatesDocument === null || templatesDocument === document)
|
|
32
|
+
return;
|
|
33
|
+
templatesDocument = null;
|
|
34
|
+
inertDocument = null;
|
|
35
|
+
generation++;
|
|
36
|
+
}
|
|
37
|
+
// Attribute-level props survive cloneNode, so they are applied to the template itself. A prop that
|
|
38
|
+
// lives only in a property (an expando, a custom element's field) would be lost by cloning, so it is
|
|
39
|
+
// replayed on every clone instead. Which is which is checked on a probe element, not guessed.
|
|
40
|
+
function bakeProp(el, key, value) {
|
|
41
|
+
const name = key === "htmlFor" ? "for" : key;
|
|
42
|
+
if (name === "class" || name === "className" || name === "style" || value === null || value === undefined) {
|
|
43
|
+
setProp(el, name, value);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Deep, so state held by the children (a <select>'s chosen option) is part of the check.
|
|
47
|
+
const probe = el.cloneNode(true);
|
|
48
|
+
setProp(probe, name, value);
|
|
49
|
+
const survives = !hasWritableProperty(probe, name) ||
|
|
50
|
+
Object.is(probe.cloneNode(true)[name], probe[name]);
|
|
51
|
+
if (survives)
|
|
52
|
+
setProp(el, name, value);
|
|
53
|
+
else
|
|
54
|
+
pendingPatches.push([el, name, value]);
|
|
55
|
+
}
|
|
56
|
+
function appendStatic(parent, child) {
|
|
57
|
+
if (child === MARKER) {
|
|
58
|
+
parent.appendChild((buildDocument ?? document).createComment(""));
|
|
59
|
+
}
|
|
60
|
+
else if (typeof child === "string") {
|
|
61
|
+
parent.appendChild((buildDocument ?? document).createTextNode(child));
|
|
62
|
+
}
|
|
63
|
+
else if (Array.isArray(child)) {
|
|
64
|
+
for (const c of child)
|
|
65
|
+
appendStatic(parent, c);
|
|
66
|
+
}
|
|
67
|
+
else if (child instanceof Node) {
|
|
68
|
+
parent.appendChild(child);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Builds one static element of a template. */
|
|
72
|
+
export function element(tag, props, children) {
|
|
73
|
+
if (tag.includes("-"))
|
|
74
|
+
sawCustomElement = true;
|
|
75
|
+
const el = createElement(tag, buildDocument ?? document);
|
|
76
|
+
// Children first, as mountElement does, so a prop that depends on them (a <select> value) sees them.
|
|
77
|
+
if (children !== null)
|
|
78
|
+
for (const child of children)
|
|
79
|
+
appendStatic(el, child);
|
|
80
|
+
if (props !== null)
|
|
81
|
+
for (const key in props)
|
|
82
|
+
bakeProp(el, key, props[key]);
|
|
83
|
+
return el;
|
|
84
|
+
}
|
|
85
|
+
function pathOf(root, node) {
|
|
86
|
+
const path = [];
|
|
87
|
+
let current = node;
|
|
88
|
+
while (current !== root) {
|
|
89
|
+
let index = 0;
|
|
90
|
+
let sibling = current.previousSibling;
|
|
91
|
+
while (sibling !== null) {
|
|
92
|
+
index++;
|
|
93
|
+
sibling = sibling.previousSibling;
|
|
94
|
+
}
|
|
95
|
+
path.unshift(index);
|
|
96
|
+
current = current.parentNode;
|
|
97
|
+
}
|
|
98
|
+
return path;
|
|
99
|
+
}
|
|
100
|
+
function nodeAt(root, path) {
|
|
101
|
+
let node = root;
|
|
102
|
+
for (const index of path)
|
|
103
|
+
node = node.childNodes[index];
|
|
104
|
+
return node;
|
|
105
|
+
}
|
|
106
|
+
// Builds the template root in `doc` (null: the live document) and lists the props to replay on
|
|
107
|
+
// every clone.
|
|
108
|
+
function buildIn(doc, build) {
|
|
109
|
+
const pending = [];
|
|
110
|
+
pendingPatches = pending;
|
|
111
|
+
buildDocument = doc;
|
|
112
|
+
sawCustomElement = false;
|
|
113
|
+
const built = build();
|
|
114
|
+
return [built, pending.length > 0 ? pending.map(([el, key, value]) => [pathOf(built, el), key, value]) : null];
|
|
115
|
+
}
|
|
116
|
+
/** Returns a function that clones the template `build` creates (built on first use). */
|
|
117
|
+
export function template(build) {
|
|
118
|
+
let root = null;
|
|
119
|
+
let patches = null;
|
|
120
|
+
let live = false;
|
|
121
|
+
let built = -1;
|
|
122
|
+
return () => {
|
|
123
|
+
if (built !== generation) {
|
|
124
|
+
const outerPatches = pendingPatches;
|
|
125
|
+
const outerDocument = buildDocument;
|
|
126
|
+
const outerSaw = sawCustomElement;
|
|
127
|
+
try {
|
|
128
|
+
let result = buildIn(inert(), build);
|
|
129
|
+
live = sawCustomElement;
|
|
130
|
+
if (live)
|
|
131
|
+
result = buildIn(null, build);
|
|
132
|
+
[root, patches] = result;
|
|
133
|
+
built = generation;
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
pendingPatches = outerPatches;
|
|
137
|
+
buildDocument = outerDocument;
|
|
138
|
+
sawCustomElement = outerSaw;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// A clone of a live-document template taken while another template builds forces that one
|
|
142
|
+
// into the live document too, or its own clones would hold never-upgraded custom elements.
|
|
143
|
+
if (live)
|
|
144
|
+
sawCustomElement = true;
|
|
145
|
+
const node = root.cloneNode(true);
|
|
146
|
+
if (patches !== null) {
|
|
147
|
+
for (const [path, key, value] of patches) {
|
|
148
|
+
const el = nodeAt(node, path);
|
|
149
|
+
setProp(el, key, value);
|
|
150
|
+
// Cloned while another template is being built: that template's clones need it too.
|
|
151
|
+
if (pendingPatches !== null)
|
|
152
|
+
pendingPatches.push([el, key, value]);
|
|
153
|
+
if (recording)
|
|
154
|
+
recordProp(el, key, value, getCurrentScope());
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return node;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Returns a function that clones the tree `markup` parses to (parsed on first use). The compiler
|
|
162
|
+
* emits it only for markup the parser reads back as the tree element() would build, with no custom
|
|
163
|
+
* element in it, so it is parsed and cloned in the inert document like a built template.
|
|
164
|
+
*/
|
|
165
|
+
export function html(markup) {
|
|
166
|
+
let root = null;
|
|
167
|
+
let built = -1;
|
|
168
|
+
return () => {
|
|
169
|
+
if (built !== generation) {
|
|
170
|
+
const parser = inert().createElement("template");
|
|
171
|
+
parser.innerHTML = markup;
|
|
172
|
+
root = parser.content.firstChild;
|
|
173
|
+
built = generation;
|
|
174
|
+
}
|
|
175
|
+
return root.cloneNode(true);
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/** Applies a dynamic prop to a cloned element. */
|
|
179
|
+
export function prop(el, key, value, scope) {
|
|
180
|
+
if (recording)
|
|
181
|
+
recordProp(el, key, value, scope);
|
|
182
|
+
applyProp(el, key, value, scope);
|
|
183
|
+
}
|
|
184
|
+
/** Binds an event prop of a compiled template element through the delegated path (dom/events). */
|
|
185
|
+
export function on(el, key, value, scope) {
|
|
186
|
+
if (recording)
|
|
187
|
+
recordProp(el, key, value, scope);
|
|
188
|
+
const entry = delegatedProp(key);
|
|
189
|
+
if (entry !== null)
|
|
190
|
+
addDelegatedEvent(el, entry, value, scope);
|
|
191
|
+
else
|
|
192
|
+
applyProp(el, key, value, scope);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The binding target of an event prop, resolved once per module by the compiler: the delegated
|
|
196
|
+
* entry, or the key itself when `key` is not a known event (it is then bound as a plain prop).
|
|
197
|
+
*/
|
|
198
|
+
export function event(key) {
|
|
199
|
+
return delegatedProp(key) ?? key;
|
|
200
|
+
}
|
|
201
|
+
/** Binds an event prop through the target `event()` resolved, with no lookup per binding. */
|
|
202
|
+
export function onEvent(el, target, value, scope) {
|
|
203
|
+
if (typeof target === "string") {
|
|
204
|
+
prop(el, target, value, scope);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (recording)
|
|
208
|
+
recordProp(el, target.key, value, scope);
|
|
209
|
+
addDelegatedEvent(el, target, value, scope);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Binds a reactive `class` (or `className`) prop: the compiler emits it for a function literal
|
|
213
|
+
* value, which applyProp would bind the same way after its key checks.
|
|
214
|
+
*/
|
|
215
|
+
export function className(el, read, scope) {
|
|
216
|
+
if (recording)
|
|
217
|
+
recordProp(el, "class", read, scope);
|
|
218
|
+
bindClass(el, read, scope);
|
|
219
|
+
}
|
|
220
|
+
/** Mounts a reactive child before its marker comment from the template. */
|
|
221
|
+
export function reactive(marker, fn, scope) {
|
|
222
|
+
mountReactive(marker.parentNode, fn, scope, marker);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Binds a reactive child that is the only content of `parent`, which the template leaves empty: no
|
|
226
|
+
* anchor comment is needed. Templates compiled by earlier versions hold an empty text node there,
|
|
227
|
+
* which a text result takes over; a hydration build pass gets one too, as mountReactive registers
|
|
228
|
+
* fresh nodes of other results only.
|
|
229
|
+
*/
|
|
230
|
+
export function reactiveOnly(parent, fn, scope) {
|
|
231
|
+
let placeholder = null;
|
|
232
|
+
if (parent.hasChildNodes())
|
|
233
|
+
placeholder = parent.firstChild;
|
|
234
|
+
else if (recording)
|
|
235
|
+
placeholder = parent.appendChild(document.createTextNode(""));
|
|
236
|
+
mountReactive(parent, fn, scope, null, placeholder);
|
|
237
|
+
}
|
|
238
|
+
/** Inserts the only child of `parent`, which the template leaves empty. */
|
|
239
|
+
export function insertOnly(parent, value, scope) {
|
|
240
|
+
// textContent builds the text node without a JS wrapper for it. An empty string still gets its
|
|
241
|
+
// (empty) text node, as mountChildren gives it one.
|
|
242
|
+
if ((typeof value === "string" && value !== "") || typeof value === "number") {
|
|
243
|
+
parent.textContent = value;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
mountChildren(parent, value, scope);
|
|
247
|
+
}
|
|
248
|
+
/** Inserts a dynamic child before `before`, or at the end of `parent` when it is null. */
|
|
249
|
+
export function insert(parent, value, before, scope) {
|
|
250
|
+
if (before === null) {
|
|
251
|
+
mountChildren(parent, value, scope);
|
|
252
|
+
}
|
|
253
|
+
else if (typeof value === "function") {
|
|
254
|
+
const end = parent.insertBefore(document.createComment(""), before);
|
|
255
|
+
mountReactive(parent, value, scope, end);
|
|
256
|
+
}
|
|
257
|
+
else if (value instanceof Node) {
|
|
258
|
+
parent.insertBefore(value, before);
|
|
259
|
+
}
|
|
260
|
+
else if (typeof value === "string" || typeof value === "number") {
|
|
261
|
+
before.before(String(value));
|
|
262
|
+
}
|
|
263
|
+
else if (Array.isArray(value)) {
|
|
264
|
+
// Each item goes straight in front of `before`, in order: no fragment to fill and empty again.
|
|
265
|
+
for (const item of value)
|
|
266
|
+
insert(parent, item, before, scope);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
export { getCurrentScope as scope };
|
|
270
|
+
//# sourceMappingURL=template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisjs/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
5
6
|
"main": "./dist/index.js",
|
|
6
7
|
"types": "./dist/index.d.ts",
|
|
7
8
|
"exports": {
|
|
@@ -10,16 +11,24 @@
|
|
|
10
11
|
"types": "./dist/index.d.ts"
|
|
11
12
|
}
|
|
12
13
|
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src",
|
|
17
|
+
"!dist/**/__tests__",
|
|
18
|
+
"!src/**/__tests__",
|
|
19
|
+
"CHANGELOG.md"
|
|
20
|
+
],
|
|
13
21
|
"devDependencies": {
|
|
14
22
|
"typescript": "^6.0.3"
|
|
15
23
|
},
|
|
16
24
|
"dependencies": {
|
|
17
|
-
"@praxisjs/
|
|
18
|
-
"@praxisjs/
|
|
19
|
-
"@praxisjs/
|
|
25
|
+
"@praxisjs/core": "2.3.0",
|
|
26
|
+
"@praxisjs/decorators": "2.0.0",
|
|
27
|
+
"@praxisjs/shared": "0.3.2"
|
|
20
28
|
},
|
|
21
29
|
"scripts": {
|
|
22
|
-
"build": "tsc",
|
|
23
|
-
"dev": "tsc --watch"
|
|
30
|
+
"build": "tsc -p tsconfig.build.json",
|
|
31
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
32
|
+
"typecheck": "tsc --noEmit"
|
|
24
33
|
}
|
|
25
34
|
}
|
package/src/children.ts
CHANGED
|
@@ -10,7 +10,8 @@ export function mountChildren(
|
|
|
10
10
|
if (children === null || children === undefined || children === false) return;
|
|
11
11
|
|
|
12
12
|
if (typeof children === "string" || typeof children === "number") {
|
|
13
|
-
|
|
13
|
+
// append() builds the text node natively: no JS wrapper is created for it.
|
|
14
|
+
(parent as ParentNode).append(String(children));
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
17
|
|
package/src/component.ts
CHANGED
|
@@ -1,100 +1,172 @@
|
|
|
1
1
|
import {
|
|
2
|
+
getComponentStart,
|
|
3
|
+
pauseTracking,
|
|
4
|
+
resumeTracking,
|
|
5
|
+
setCurrentOwner,
|
|
2
6
|
setComponentAnchor,
|
|
3
7
|
setComponentMounted,
|
|
4
|
-
untrack,
|
|
5
8
|
} from "@praxisjs/core/internal";
|
|
6
9
|
import { initSlots } from "@praxisjs/decorators";
|
|
7
|
-
import {
|
|
10
|
+
import type { ComponentConstructor } from "@praxisjs/shared/internal";
|
|
8
11
|
|
|
9
12
|
import { mountChildren } from "./children";
|
|
10
|
-
import {
|
|
13
|
+
import { swapScope } from "./context";
|
|
14
|
+
import { hotEnabled, isHotClass, latestClass, setHotRemount, trackHotInstance, type HotInstance } from "./hmr";
|
|
15
|
+
import { recording, onHydrationReplace } from "./hydration-context";
|
|
11
16
|
|
|
12
17
|
import type { Scope } from "./scope";
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
type Instance = InstanceType<ComponentConstructor>;
|
|
20
|
+
|
|
21
|
+
// One object per component carries both lifecycle steps: mounting (drained in a shared microtask)
|
|
22
|
+
// and disposal (registered in the component's scope), instead of a closure for each.
|
|
23
|
+
class ComponentLife {
|
|
24
|
+
declare disposed: boolean;
|
|
25
|
+
declare readonly instance: Instance;
|
|
26
|
+
declare readonly ref: ((instance: object | null) => void) | undefined;
|
|
27
|
+
|
|
28
|
+
constructor(instance: Instance, ref: ((instance: object | null) => void) | undefined) {
|
|
29
|
+
this.disposed = false;
|
|
30
|
+
this.instance = instance;
|
|
31
|
+
this.ref = ref;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
mount(): void {
|
|
35
|
+
if (this.disposed) return;
|
|
36
|
+
setComponentMounted(this.instance, true);
|
|
37
|
+
this.instance.onMount?.();
|
|
38
|
+
this.ref?.(this.instance);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
dispose(): void {
|
|
42
|
+
this.disposed = true;
|
|
43
|
+
this.instance.onUnmount?.();
|
|
44
|
+
setComponentMounted(this.instance, false);
|
|
45
|
+
this.ref?.(null);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let pendingMounts: ComponentLife[] = [];
|
|
15
50
|
|
|
16
51
|
// One shared microtask drains every component mounted in the same synchronous pass, in mount
|
|
17
52
|
// order (children before parents). A throwing onMount is rethrown on its own microtask so it
|
|
18
53
|
// can't stop the others, as it couldn't when each component had a microtask of its own.
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
queueMicrotask(() => { throw e; });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
});
|
|
54
|
+
function drainMounts(): void {
|
|
55
|
+
const queue = pendingMounts;
|
|
56
|
+
pendingMounts = [];
|
|
57
|
+
for (const life of queue) {
|
|
58
|
+
try {
|
|
59
|
+
life.mount();
|
|
60
|
+
} catch (e) {
|
|
61
|
+
queueMicrotask(() => { throw e; });
|
|
62
|
+
}
|
|
32
63
|
}
|
|
33
|
-
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function scheduleMount(life: ComponentLife): void {
|
|
67
|
+
if (pendingMounts.length === 0) queueMicrotask(drainMounts);
|
|
68
|
+
pendingMounts.push(life);
|
|
34
69
|
}
|
|
35
70
|
|
|
36
71
|
export function mountComponent(
|
|
37
72
|
ctor: ComponentConstructor,
|
|
38
73
|
props: Record<string, unknown>,
|
|
39
74
|
parentScope: Scope,
|
|
75
|
+
// Hot remount only: the comments of the instance being replaced, kept as the new one's bounds.
|
|
76
|
+
bounds: [Comment, Comment] | null = null,
|
|
40
77
|
): Node[] {
|
|
41
|
-
|
|
78
|
+
const prevSub = pauseTracking();
|
|
79
|
+
try {
|
|
80
|
+
// A registered class (development, see hmr.ts) always mounts its newest version, and every
|
|
81
|
+
// instance gets comment bounds, which stay in place when it is mounted again.
|
|
82
|
+
let hot = false;
|
|
83
|
+
if (hotEnabled) {
|
|
84
|
+
ctor = latestClass(ctor);
|
|
85
|
+
hot = isHotClass(ctor);
|
|
86
|
+
}
|
|
42
87
|
const scope = parentScope.fork();
|
|
43
88
|
|
|
44
|
-
// Strip ref — managed by the runtime, not forwarded to the component.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
89
|
+
// Strip ref — managed by the runtime, not forwarded to the component. The component copies its
|
|
90
|
+
// props itself, so they are only copied here when there is a ref to leave out.
|
|
91
|
+
let instanceProps = props;
|
|
92
|
+
let ref: ((instance: object | null) => void) | undefined;
|
|
93
|
+
if ("ref" in props) {
|
|
94
|
+
const { ref: refFn, ...rest } = props;
|
|
95
|
+
instanceProps = rest;
|
|
96
|
+
if (typeof refFn === "function") ref = refFn as (instance: object | null) => void;
|
|
97
|
+
}
|
|
49
98
|
|
|
50
|
-
const instance = new ctor(
|
|
99
|
+
const instance = new ctor(instanceProps);
|
|
51
100
|
|
|
52
101
|
const rawChildren = instanceProps.children;
|
|
53
102
|
if (rawChildren != null) {
|
|
54
103
|
initSlots(instance, rawChildren);
|
|
55
104
|
}
|
|
56
105
|
|
|
57
|
-
const start = document.createComment(`[${ctor.name}]`);
|
|
58
|
-
const end = document.createComment(`[/${ctor.name}]`);
|
|
59
|
-
let disposed = false;
|
|
60
|
-
|
|
61
|
-
setComponentAnchor(instance, end);
|
|
62
|
-
|
|
63
106
|
instance.onBeforeMount?.();
|
|
64
107
|
|
|
65
|
-
const container = document.createDocumentFragment();
|
|
66
|
-
container.appendChild(start);
|
|
67
|
-
|
|
68
108
|
let dom: Node | Node[] | null = null;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
109
|
+
const prevScope = swapScope(scope);
|
|
110
|
+
const prevOwner = setCurrentOwner(instance);
|
|
111
|
+
try {
|
|
112
|
+
dom = instance.render();
|
|
113
|
+
} catch (e) {
|
|
114
|
+
const fallback = instance.onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
115
|
+
if (fallback != null) dom = fallback;
|
|
116
|
+
} finally {
|
|
117
|
+
setCurrentOwner(prevOwner);
|
|
118
|
+
swapScope(prevScope);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let nodes: Node[];
|
|
122
|
+
if (hot) {
|
|
123
|
+
const container = document.createDocumentFragment();
|
|
124
|
+
const start = bounds === null ? container.appendChild(document.createComment(`[${ctor.name}]`)) : bounds[0];
|
|
125
|
+
mountChildren(container, dom, scope);
|
|
126
|
+
const end = bounds === null ? container.appendChild(document.createComment(`[/${ctor.name}]`)) : bounds[1];
|
|
127
|
+
setComponentAnchor(instance, end);
|
|
128
|
+
nodes = Array.from(container.childNodes);
|
|
129
|
+
trackHotInstance({ ctor, props, parentScope, scope, start, end });
|
|
130
|
+
} else if (dom instanceof Node && dom.nodeType !== 11) {
|
|
131
|
+
// A single root node can't change, so it bounds the component by itself: no comments.
|
|
132
|
+
nodes = [dom];
|
|
133
|
+
setComponentAnchor(instance, dom);
|
|
134
|
+
if (recording) onHydrationReplace(dom, (real) => { setComponentAnchor(instance, real); });
|
|
135
|
+
} else {
|
|
136
|
+
// Several nodes, or ones a reactive child may replace: comments mark where the component
|
|
137
|
+
// starts and ends, unless its render already brought them (see getComponentStart).
|
|
138
|
+
const bounded = getComponentStart(instance) !== undefined;
|
|
139
|
+
const container = document.createDocumentFragment();
|
|
140
|
+
if (!bounded) container.appendChild(document.createComment(`[${ctor.name}]`));
|
|
141
|
+
mountChildren(container, dom, scope);
|
|
142
|
+
if (!bounded) {
|
|
143
|
+
const end = document.createComment(`[/${ctor.name}]`);
|
|
144
|
+
container.appendChild(end);
|
|
145
|
+
setComponentAnchor(instance, end);
|
|
75
146
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
147
|
+
nodes = Array.from(container.childNodes);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const life = new ComponentLife(instance, ref);
|
|
151
|
+
scheduleMount(life);
|
|
152
|
+
scope.add(life);
|
|
153
|
+
return nodes;
|
|
154
|
+
} finally {
|
|
155
|
+
resumeTracking(prevSub);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Mounts `hot` again with `ctor` between the comments it already has: the reactive children and
|
|
160
|
+
// lists around it keep their references to those comments.
|
|
161
|
+
function remountHot(hot: HotInstance, ctor: ComponentConstructor): void {
|
|
162
|
+
const { start, end } = hot;
|
|
163
|
+
const parent = end.parentNode;
|
|
164
|
+
// Detached (kept aside by its owner): there is no place to render into, so it is left as it is.
|
|
165
|
+
if (parent === null || start.parentNode !== parent) return;
|
|
166
|
+
hot.scope.dispose();
|
|
167
|
+
for (let node = start.nextSibling; node !== null && node !== end; node = start.nextSibling) parent.removeChild(node);
|
|
168
|
+
const nodes = mountComponent(ctor, hot.props, hot.parentScope, [start, end]);
|
|
169
|
+
for (const node of nodes) parent.insertBefore(node, end);
|
|
98
170
|
}
|
|
99
171
|
|
|
100
|
-
|
|
172
|
+
setHotRemount(remountHot);
|
package/src/context.ts
CHANGED
|
@@ -2,15 +2,38 @@ import type { Scope } from "./scope";
|
|
|
2
2
|
|
|
3
3
|
let _currentScope: Scope | null = null;
|
|
4
4
|
|
|
5
|
+
/** Owner of a child scope that is created only once something asks for it (see swapLazyScope). */
|
|
6
|
+
export interface LazyScopeHost {
|
|
7
|
+
parentScope: Scope;
|
|
8
|
+
scope: Scope | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let lazyHost: LazyScopeHost | null = null;
|
|
12
|
+
|
|
5
13
|
export function getCurrentScope(): Scope {
|
|
6
|
-
if (
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
14
|
+
if (_currentScope === null) {
|
|
15
|
+
if (lazyHost === null) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
"[PraxisJS] jsx() called outside of a render context. Make sure to call render() with a factory function.",
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
_currentScope = lazyHost.scope ??= lazyHost.parentScope.fork();
|
|
10
21
|
}
|
|
11
22
|
return _currentScope;
|
|
12
23
|
}
|
|
13
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Makes `host` the scope source while no scope is current: its child scope is forked from
|
|
27
|
+
* `host.parentScope` the first time code asks for the current scope, so a run that registers
|
|
28
|
+
* nothing (a reactive text binding) creates no scope at all. Returns the previous host, which the
|
|
29
|
+
* caller must restore in a `finally`, together with the current scope (set it to `host.scope`).
|
|
30
|
+
*/
|
|
31
|
+
export function swapLazyScope(host: LazyScopeHost | null): LazyScopeHost | null {
|
|
32
|
+
const prev = lazyHost;
|
|
33
|
+
lazyHost = host;
|
|
34
|
+
return prev;
|
|
35
|
+
}
|
|
36
|
+
|
|
14
37
|
export function runInScope<T>(scope: Scope, fn: () => T): T {
|
|
15
38
|
const prev = _currentScope;
|
|
16
39
|
_currentScope = scope;
|
|
@@ -20,3 +43,11 @@ export function runInScope<T>(scope: Scope, fn: () => T): T {
|
|
|
20
43
|
_currentScope = prev;
|
|
21
44
|
}
|
|
22
45
|
}
|
|
46
|
+
|
|
47
|
+
// Same as runInScope without a closure per call, for hot paths that create many scopes in a row.
|
|
48
|
+
// Callers must restore the returned scope in a `finally`.
|
|
49
|
+
export function swapScope(scope: Scope | null): Scope | null {
|
|
50
|
+
const prev = _currentScope;
|
|
51
|
+
_currentScope = scope;
|
|
52
|
+
return prev;
|
|
53
|
+
}
|