-
Notifications
You must be signed in to change notification settings - Fork 0
/
transport.tsx
135 lines (121 loc) · 4.23 KB
/
transport.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import { CompType, autoId } from '@connectv/html';
import { callTrace } from '../../shared/trace';
import { ComponentThis } from '../../static';
import { recipientPromise } from '../../static/promise/recipient-promise';
import { createInfo, attachInfo, TransportInfo } from './transport-info';
/**
*
* @param component
* @returns all attached `TransportInfo` on the given component, if any.
*
*/
export function getCompTransportInfo(component: CompType<any, any>) {
return (component as any).__transport_info;
}
/**
*
* Attaches given `TransportInfo` on given component.
*
* @param component
* @param info
*
*/
export function attachCompTransportInfo(component: CompType<any, any>, info: TransportInfo) {
(component as any).__transport_info = { ...info, resolved: false };
}
/**
*
* Creates a _transport reference_. A _transport reference_ can be used to conduct
* **broad transport**, i.e. you can have the content that is supposed to be rendered
* on the client side also replace some server-side-rendered content:
*
* ```tsx
* function myComp(_, renderer) {
* const tr = transportRef();
*
* return <fragment>
* <StaticComponent _transport={tr}/> // --> rendered on server-side
* <div data-transport={tr}/> // --> rendered on server-side
* <TransportComponent _transport={tr}/> // --> rendered on client-side, replaces the others.
* </fragment>
* }
* ```
*
* @returns a _transport reference_
*
*
*/
export function transportRef() { return autoId(); }
/**
*
* Creates a transport component based on given (original) component.
* On the server-side, the transport component ensures _CLIENT-SIDE_ rendering
* of original component on the same spot in the DOM tree with same properties.
* On the client-side, the transport component is identical to the original component.
*
* example:
*
* ```tsx
* import { state } from '@connectv/core';
* import { transport } from '@connectv/sdh/transport';
*
* export function Counter(_, renderer) {
* const count = state(0);
* return <div onclick={() => count.value++}>You clicked {count} times!</div>;
* }
*
* export const $Counter = transport(Counter);
* ```
*
* In this example, you cannot use `Counter` component on server-side rendering since it needs
* to bind to user clicks (trying to render it actually results in an error). However, you can
* utilize `$Counter` instead, and it will ensure that `Counter` is rendered on the same locations
* on the DOM tree that you rendered `$Counter` on, on the client-side.
*
* @note You MUST export both the original component and the transport component, from the same file.
* This is how the original component is then imported (alongside with any possible dependencies)
* into client bundles.
*
* @note Any properties passed to the transport component will be used in rendering of the original
* component. However, you CANNOT pass any child elements to the transport component.
*
*/
export function transport(component: any) {
const trace = callTrace();
if (!trace) return component; // --> unable to get trace info, perhaps running on client.
const info = createInfo(component.name, trace);
const comp = function(this: ComponentThis, props: any, renderer: any) {
const id = props._transport || transportRef();
const script = <script id={id}>
(function(){'{'}
function load(){'{'}
if (window.__sdh_transport){'{'}
window.__sdh_transport("{id}", "{info.hash}", {
Promise.all(
Object.keys(props).filter(key => key !== '_transport').map(
async key => [key, await this.expose.in(key, recipientPromise<RawValue>())]
)
)
.then(entries => entries.reduce((obj, [key, value]) => {
obj[key as any] = value;
return obj;
}, {} as any))
.then(JSON.stringify)
});
{
props._transport?
`document.querySelectorAll('[data-transport="${props._transport}"]').forEach(function(node){node.remove()});`
:''
}
}
};
if (document.readyState == 'complete') load();
else window.addEventListener('load', load);
})()
</script>;
attachInfo(script, info);
return script;
};
attachCompTransportInfo(comp, info);
return comp;
}