@praxisjs/runtime 0.5.5 → 0.6.0
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/__tests__/hydration.test.d.ts +2 -0
- package/dist/__tests__/hydration.test.d.ts.map +1 -0
- package/dist/__tests__/hydration.test.js +299 -0
- package/dist/__tests__/hydration.test.js.map +1 -0
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +5 -0
- package/dist/element.js.map +1 -1
- package/dist/hydration-context.d.ts +21 -0
- package/dist/hydration-context.d.ts.map +1 -0
- package/dist/hydration-context.js +40 -0
- package/dist/hydration-context.js.map +1 -0
- package/dist/hydration.d.ts +18 -0
- package/dist/hydration.d.ts.map +1 -0
- package/dist/hydration.js +67 -0
- package/dist/hydration.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -10
- package/dist/index.js.map +1 -1
- package/dist/portal.d.ts.map +1 -1
- package/dist/portal.js +8 -0
- package/dist/portal.js.map +1 -1
- package/dist/reactive.d.ts.map +1 -1
- package/dist/reactive.js +10 -0
- package/dist/reactive.js.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/hydration.test.ts +360 -0
- package/src/element.ts +5 -0
- package/src/hydration-context.ts +53 -0
- package/src/hydration.ts +70 -0
- package/src/index.ts +57 -11
- package/src/portal.ts +8 -0
- package/src/reactive.ts +10 -0
package/src/index.ts
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
import { mountChildren } from "./children";
|
|
2
2
|
import { runInScope } from "./context";
|
|
3
|
+
import { reconcile } from "./hydration";
|
|
4
|
+
import { runInRecording } from "./hydration-context";
|
|
3
5
|
import { Scope } from "./scope";
|
|
4
6
|
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* so `jsx()` can access the current scope via `getCurrentScope()`.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* render(() => <App />, document.getElementById('app'));
|
|
13
|
-
*/
|
|
14
|
-
export function render(
|
|
7
|
+
/** Written by `@praxisjs/ssg` onto the mount container in its prerendered HTML. */
|
|
8
|
+
const SSG_MARKER = "data-praxis-ssg";
|
|
9
|
+
|
|
10
|
+
function mountCreate(
|
|
15
11
|
factory: () => Node | Node[] | null,
|
|
16
12
|
container: HTMLElement,
|
|
17
13
|
): () => void {
|
|
18
14
|
const rootScope = new Scope();
|
|
19
|
-
|
|
20
15
|
container.innerHTML = "";
|
|
21
16
|
|
|
22
17
|
runInScope(rootScope, () => {
|
|
@@ -30,6 +25,57 @@ export function render(
|
|
|
30
25
|
};
|
|
31
26
|
}
|
|
32
27
|
|
|
28
|
+
function hydrate(
|
|
29
|
+
factory: () => Node | Node[] | null,
|
|
30
|
+
container: HTMLElement,
|
|
31
|
+
): () => void {
|
|
32
|
+
const rootScope = new Scope();
|
|
33
|
+
const scratch = document.createDocumentFragment();
|
|
34
|
+
|
|
35
|
+
// Build normally — same code as mountCreate, just into a detached scratch
|
|
36
|
+
// node instead of the real container, so it can be walked in document
|
|
37
|
+
// order afterwards (its own construction order is bottom-up, not useful
|
|
38
|
+
// for matching against the server-rendered DOM directly).
|
|
39
|
+
runInRecording(() => {
|
|
40
|
+
runInScope(rootScope, () => {
|
|
41
|
+
const result = factory();
|
|
42
|
+
mountChildren(scratch, result, rootScope);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
reconcile(container, Array.from(scratch.childNodes));
|
|
47
|
+
|
|
48
|
+
return () => {
|
|
49
|
+
rootScope.dispose();
|
|
50
|
+
container.innerHTML = "";
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Renders a component tree into a container element.
|
|
56
|
+
*
|
|
57
|
+
* The factory function is called once inside the root scope,
|
|
58
|
+
* so `jsx()` can access the current scope via `getCurrentScope()`.
|
|
59
|
+
*
|
|
60
|
+
* If `container` carries the marker `@praxisjs/ssg` stamps onto prerendered
|
|
61
|
+
* HTML, the tree is built normally into a detached scratch node and then
|
|
62
|
+
* reconciled against the existing DOM (real elements are kept and have their
|
|
63
|
+
* props/listeners replayed onto them; text/comments and any mismatched nodes
|
|
64
|
+
* are recreated) instead of clearing the container and mounting fresh.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* render(() => <App />, document.getElementById('app'));
|
|
68
|
+
*/
|
|
69
|
+
export function render(
|
|
70
|
+
factory: () => Node | Node[] | null,
|
|
71
|
+
container: HTMLElement,
|
|
72
|
+
): () => void {
|
|
73
|
+
if (container.hasAttribute(SSG_MARKER)) {
|
|
74
|
+
return hydrate(factory, container);
|
|
75
|
+
}
|
|
76
|
+
return mountCreate(factory, container);
|
|
77
|
+
}
|
|
78
|
+
|
|
33
79
|
export { Scope } from "./scope";
|
|
34
80
|
export { runInScope, getCurrentScope } from "./context";
|
|
35
81
|
export { mountElement } from "./element";
|
package/src/portal.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StatelessComponent } from "@praxisjs/core";
|
|
2
|
+
import { isServerRenderPass } from "@praxisjs/core/internal";
|
|
2
3
|
|
|
3
4
|
import { mountChildren } from "./children";
|
|
4
5
|
import { getCurrentScope } from "./context";
|
|
@@ -12,6 +13,13 @@ export class Portal extends StatelessComponent<PortalProps> {
|
|
|
12
13
|
static readonly __isStateless = true;
|
|
13
14
|
|
|
14
15
|
render() {
|
|
16
|
+
// Portals escape to an arbitrary external target (default document.body),
|
|
17
|
+
// which would need a hydration cursor shared across every portal mounted at
|
|
18
|
+
// that same target — out of scope for v1. Skip during the server render pass
|
|
19
|
+
// (target content isn't part of the static HTML); the client mounts normally,
|
|
20
|
+
// create-mode, right after hydration completes — no duplication.
|
|
21
|
+
if (isServerRenderPass()) return null;
|
|
22
|
+
|
|
15
23
|
const scope = getCurrentScope();
|
|
16
24
|
const target = resolvePortalTarget(this.props.to);
|
|
17
25
|
if (!target) return null;
|
package/src/reactive.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { runInScope } from "./context";
|
|
2
|
+
import { isRecording, registerReactiveNode } from "./hydration-context";
|
|
2
3
|
import { Scope } from "./scope";
|
|
3
4
|
|
|
4
5
|
function collectNodes(value: unknown, out: Node[]): void {
|
|
@@ -37,6 +38,15 @@ export function mountReactive(
|
|
|
37
38
|
const newNodes: Node[] = [];
|
|
38
39
|
collectNodes(result, newNodes);
|
|
39
40
|
|
|
41
|
+
// During a hydration build pass, remember that each of these fresh nodes
|
|
42
|
+
// belongs to *this* array — reconcile() mutates `newNodes` in place (by
|
|
43
|
+
// reference, same array `currentNodes` ends up pointing at) if it decides
|
|
44
|
+
// to keep a real element instead of one of them, so future diffs here
|
|
45
|
+
// stay correct.
|
|
46
|
+
if (isRecording()) {
|
|
47
|
+
for (const n of newNodes) registerReactiveNode(n, newNodes);
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
const anchor = end.parentNode ?? parent;
|
|
41
51
|
|
|
42
52
|
if (currentNodes.length > 0) {
|