@praxisjs/runtime 0.2.16 → 0.2.17
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 +16 -0
- package/dist/element.js +2 -2
- package/dist/element.js.map +1 -1
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +37 -19
- package/dist/reactive.js.map +1 -1
- package/package.json +4 -4
- package/src/element.ts +2 -2
- package/src/reactive.ts +31 -18
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @praxisjs/runtime
|
|
2
2
|
|
|
3
|
+
## 0.2.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9aeec6a: Optimize DOM patching in the runtime for lower allocation and fewer reflows.
|
|
8
|
+
|
|
9
|
+
**element.ts** — prop iteration now uses `for...in` instead of `Object.entries()`, avoiding the intermediate key-value array allocation per element mount.
|
|
10
|
+
|
|
11
|
+
**reactive.ts** — `normalizeToNodes` (returned a new array on every call) is replaced by `collectNodes` (accumulates into a caller-supplied array), removing one allocation per reactive update. Node insertion is batched through `nodesToFragment`: when multiple nodes are produced, they are appended to a `DocumentFragment` and inserted in a single `insertBefore` call. Bulk removal of existing nodes uses `Range.deleteContents()` instead of individual `removeChild` calls, reducing the number of reflow-triggering operations to one.
|
|
12
|
+
|
|
13
|
+
No changes to the public API.
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [74d414a]
|
|
16
|
+
- @praxisjs/core@1.6.0
|
|
17
|
+
- @praxisjs/decorators@1.0.1
|
|
18
|
+
|
|
3
19
|
## 0.2.16
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/element.js
CHANGED
|
@@ -3,10 +3,10 @@ import { createElement } from "./dom/create";
|
|
|
3
3
|
import { applyProp } from "./dom/props";
|
|
4
4
|
export function mountElement(tag, props, scope) {
|
|
5
5
|
const el = createElement(tag);
|
|
6
|
-
for (const
|
|
6
|
+
for (const key in props) {
|
|
7
7
|
if (key === "children")
|
|
8
8
|
continue;
|
|
9
|
-
applyProp(el, key,
|
|
9
|
+
applyProp(el, key, props[key], scope);
|
|
10
10
|
}
|
|
11
11
|
if (props.children !== undefined) {
|
|
12
12
|
mountChildren(el, props.children, scope);
|
package/dist/element.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"element.js","sourceRoot":"","sources":["../src/element.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAIxC,MAAM,UAAU,YAAY,CAC1B,GAAW,EACX,KAA8B,EAC9B,KAAY;IAEZ,MAAM,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAE9B,KAAK,MAAM,
|
|
1
|
+
{"version":3,"file":"element.js","sourceRoot":"","sources":["../src/element.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAIxC,MAAM,UAAU,YAAY,CAC1B,GAAW,EACX,KAA8B,EAC9B,KAAY;IAEZ,MAAM,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAE9B,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,IAAI,GAAG,KAAK,UAAU;YAAE,SAAS;QACjC,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IACxC,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QACjC,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC3C,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
package/dist/reactive.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reactive.d.ts","sourceRoot":"","sources":["../src/reactive.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"reactive.d.ts","sourceRoot":"","sources":["../src/reactive.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAmBrC,wBAAgB,aAAa,CAC3B,MAAM,EAAE,IAAI,EACZ,EAAE,EAAE,MAAM,OAAO,EACjB,WAAW,EAAE,KAAK,GACjB,IAAI,CAoCN"}
|
package/dist/reactive.js
CHANGED
|
@@ -1,36 +1,54 @@
|
|
|
1
1
|
import { runInScope } from "./context";
|
|
2
|
-
function
|
|
2
|
+
function collectNodes(value, out) {
|
|
3
3
|
if (value === null || value === undefined || value === false)
|
|
4
|
-
return
|
|
5
|
-
if (value instanceof Node)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
return;
|
|
5
|
+
if (value instanceof Node) {
|
|
6
|
+
out.push(value);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (Array.isArray(value)) {
|
|
10
|
+
for (const v of value)
|
|
11
|
+
collectNodes(v, out);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
9
14
|
if (typeof value === "string" || typeof value === "number") {
|
|
10
|
-
|
|
15
|
+
out.push(document.createTextNode(String(value)));
|
|
16
|
+
return;
|
|
11
17
|
}
|
|
12
|
-
|
|
18
|
+
}
|
|
19
|
+
function nodesToFragment(nodes) {
|
|
20
|
+
if (nodes.length === 1)
|
|
21
|
+
return nodes[0];
|
|
22
|
+
const fragment = document.createDocumentFragment();
|
|
23
|
+
for (const n of nodes)
|
|
24
|
+
fragment.appendChild(n);
|
|
25
|
+
return fragment;
|
|
13
26
|
}
|
|
14
27
|
export function mountReactive(parent, fn, parentScope) {
|
|
15
|
-
const end = document.createComment("
|
|
28
|
+
const end = document.createComment("");
|
|
16
29
|
parent.appendChild(end);
|
|
17
30
|
let currentNodes = [];
|
|
18
31
|
let childScope = parentScope.fork();
|
|
19
32
|
parentScope.effect(() => {
|
|
20
33
|
childScope.dispose();
|
|
21
34
|
childScope = parentScope.fork();
|
|
22
|
-
for (const n of currentNodes) {
|
|
23
|
-
n.parentNode?.removeChild(n);
|
|
24
|
-
}
|
|
25
|
-
currentNodes = [];
|
|
26
35
|
const result = runInScope(childScope, fn);
|
|
27
|
-
const newNodes =
|
|
28
|
-
|
|
29
|
-
// reference may be a DocumentFragment that was emptied after its
|
|
30
|
-
// children were transferred to the real DOM by mountComponent.
|
|
36
|
+
const newNodes = [];
|
|
37
|
+
collectNodes(result, newNodes);
|
|
31
38
|
const anchor = end.parentNode ?? parent;
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
if (currentNodes.length > 0) {
|
|
40
|
+
if (currentNodes.length === 1) {
|
|
41
|
+
anchor.removeChild(currentNodes[0]);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const range = document.createRange();
|
|
45
|
+
range.setStartBefore(currentNodes[0]);
|
|
46
|
+
range.setEndAfter(currentNodes[currentNodes.length - 1]);
|
|
47
|
+
range.deleteContents();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (newNodes.length > 0) {
|
|
51
|
+
anchor.insertBefore(nodesToFragment(newNodes), end);
|
|
34
52
|
}
|
|
35
53
|
currentNodes = newNodes;
|
|
36
54
|
});
|
package/dist/reactive.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reactive.js","sourceRoot":"","sources":["../src/reactive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAIvC,SAAS,
|
|
1
|
+
{"version":3,"file":"reactive.js","sourceRoot":"","sources":["../src/reactive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAIvC,SAAS,YAAY,CAAC,KAAc,EAAE,GAAW;IAC/C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO;IACrE,IAAI,KAAK,YAAY,IAAI,EAAE,CAAC;QAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAAC,OAAO;IAAC,CAAC;IACvD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAAC,KAAK,MAAM,CAAC,IAAI,KAAK;YAAE,YAAY,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAAC,OAAO;IAAC,CAAC;IAClF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC3D,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACjD,OAAO;IACT,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,KAAa;IACpC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,QAAQ,CAAC,sBAAsB,EAAE,CAAC;IACnD,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAC/C,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,MAAY,EACZ,EAAiB,EACjB,WAAkB;IAElB,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;IACvC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAExB,IAAI,YAAY,GAAW,EAAE,CAAC;IAC9B,IAAI,UAAU,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC;IAEpC,WAAW,CAAC,MAAM,CAAC,GAAG,EAAE;QACtB,UAAU,CAAC,OAAO,EAAE,CAAC;QACrB,UAAU,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC;QAEhC,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAW,EAAE,CAAC;QAC5B,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAE/B,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,IAAI,MAAM,CAAC;QAExC,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC7B,MAAkB,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;YACnD,CAAC;iBAAM,CAAC;gBACN,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;gBACrC,KAAK,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;gBACtC,KAAK,CAAC,WAAW,CAAC,YAAY,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;gBACzD,KAAK,CAAC,cAAc,EAAE,CAAC;YACzB,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC;QACtD,CAAC;QAED,YAAY,GAAG,QAAQ,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AACnD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisjs/runtime",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
"typescript": "^5.9.3"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@praxisjs/core": "1.
|
|
18
|
-
"@praxisjs/
|
|
19
|
-
"@praxisjs/
|
|
17
|
+
"@praxisjs/core": "1.6.0",
|
|
18
|
+
"@praxisjs/shared": "0.2.0",
|
|
19
|
+
"@praxisjs/decorators": "1.0.1"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build": "tsc",
|
package/src/element.ts
CHANGED
|
@@ -11,9 +11,9 @@ export function mountElement(
|
|
|
11
11
|
): HTMLElement | SVGElement {
|
|
12
12
|
const el = createElement(tag);
|
|
13
13
|
|
|
14
|
-
for (const
|
|
14
|
+
for (const key in props) {
|
|
15
15
|
if (key === "children") continue;
|
|
16
|
-
applyProp(el, key,
|
|
16
|
+
applyProp(el, key, props[key], scope);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
if (props.children !== undefined) {
|
package/src/reactive.ts
CHANGED
|
@@ -2,14 +2,21 @@ import { runInScope } from "./context";
|
|
|
2
2
|
|
|
3
3
|
import type { Scope } from "./scope";
|
|
4
4
|
|
|
5
|
-
function
|
|
6
|
-
if (value === null || value === undefined || value === false) return
|
|
7
|
-
if (value instanceof Node)
|
|
8
|
-
if (Array.isArray(value))
|
|
5
|
+
function collectNodes(value: unknown, out: Node[]): void {
|
|
6
|
+
if (value === null || value === undefined || value === false) return;
|
|
7
|
+
if (value instanceof Node) { out.push(value); return; }
|
|
8
|
+
if (Array.isArray(value)) { for (const v of value) collectNodes(v, out); return; }
|
|
9
9
|
if (typeof value === "string" || typeof value === "number") {
|
|
10
|
-
|
|
10
|
+
out.push(document.createTextNode(String(value)));
|
|
11
|
+
return;
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function nodesToFragment(nodes: Node[]): Node {
|
|
16
|
+
if (nodes.length === 1) return nodes[0];
|
|
17
|
+
const fragment = document.createDocumentFragment();
|
|
18
|
+
for (const n of nodes) fragment.appendChild(n);
|
|
19
|
+
return fragment;
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
export function mountReactive(
|
|
@@ -17,7 +24,7 @@ export function mountReactive(
|
|
|
17
24
|
fn: () => unknown,
|
|
18
25
|
parentScope: Scope,
|
|
19
26
|
): void {
|
|
20
|
-
const end = document.createComment("
|
|
27
|
+
const end = document.createComment("");
|
|
21
28
|
parent.appendChild(end);
|
|
22
29
|
|
|
23
30
|
let currentNodes: Node[] = [];
|
|
@@ -27,21 +34,27 @@ export function mountReactive(
|
|
|
27
34
|
childScope.dispose();
|
|
28
35
|
childScope = parentScope.fork();
|
|
29
36
|
|
|
30
|
-
for (const n of currentNodes) {
|
|
31
|
-
n.parentNode?.removeChild(n);
|
|
32
|
-
}
|
|
33
|
-
currentNodes = [];
|
|
34
|
-
|
|
35
37
|
const result = runInScope(childScope, fn);
|
|
36
|
-
const newNodes =
|
|
38
|
+
const newNodes: Node[] = [];
|
|
39
|
+
collectNodes(result, newNodes);
|
|
37
40
|
|
|
38
|
-
// Use end.parentNode instead of the original `parent` — the parent
|
|
39
|
-
// reference may be a DocumentFragment that was emptied after its
|
|
40
|
-
// children were transferred to the real DOM by mountComponent.
|
|
41
41
|
const anchor = end.parentNode ?? parent;
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
|
|
43
|
+
if (currentNodes.length > 0) {
|
|
44
|
+
if (currentNodes.length === 1) {
|
|
45
|
+
(anchor as Element).removeChild(currentNodes[0]);
|
|
46
|
+
} else {
|
|
47
|
+
const range = document.createRange();
|
|
48
|
+
range.setStartBefore(currentNodes[0]);
|
|
49
|
+
range.setEndAfter(currentNodes[currentNodes.length - 1]);
|
|
50
|
+
range.deleteContents();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (newNodes.length > 0) {
|
|
55
|
+
anchor.insertBefore(nodesToFragment(newNodes), end);
|
|
44
56
|
}
|
|
57
|
+
|
|
45
58
|
currentNodes = newNodes;
|
|
46
59
|
});
|
|
47
60
|
|