-
Notifications
You must be signed in to change notification settings - Fork 300
/
Copy pathframework-lib.js
193 lines (152 loc) · 5.52 KB
/
framework-lib.js
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
"use strict";
const assert = require("assert");
const { createElement } = require("preact");
const { default: AppContext } = require("../dist/node/app-context");
const prts = require("preact-render-to-string");
const { Provider } = require("redux-bundler-preact");
class FrameworkLib {
constructor(ref) {
this.ref = ref;
this._prepared = false;
}
async handlePrepare() {
this._prepared = true;
const { subApp, subAppServer, options } = this.ref;
assert(!subApp.useReactRouter, "react router is not yet supported for preact");
this.StartComponent = subAppServer.StartComponent || subApp.Component;
if (this.StartComponent && options.serverSideRendering === true) {
if (subApp.__redux) {
return await this.prepareReduxData();
} else {
return await this.prepareData();
}
}
return false;
}
async handleSSR() {
if (!this._prepared) {
await this.handlePrepare();
await this.realizeReduxStore();
}
const packReduxData =
this.ref.subAppServer.packReduxData || this.ref.subApp.packReduxData;
if (packReduxData) {
this.initialStateStr = JSON.stringify(packReduxData(this.store));
}
return this.handleSSRSync();
}
handleSSRSync() {
const { subApp, options } = this.ref;
assert(this._prepared, "subapp's data must've been prepared to run handleSSRSync");
if (!this.StartComponent) {
return `<!-- serverSideRendering ${subApp.name} has no StartComponent -->`;
} else if (options.serverSideRendering === true) {
if (subApp.__redux) {
// we can't realize the store here because signaling store ready is async
assert(!this.store.realize, "BUG: redux store is not yet realized");
assert(Provider, "subapp-pbundle: redux-bundler-preact Provider not available");
// finally render the element with Redux Provider and the store created
return this.renderTo(
createElement(Provider, { store: this.store }, this.createTopComponent()),
this.ref.options
);
} else {
return this.doSSR();
}
}
return "";
}
renderTo(element, options) {
assert(!options.streaming, "render to stream is not yet supported for preact");
assert(!options.suspenseSsr, "suspense is not yet supported for preact");
if (options.hydrateServerData) {
return prts.render(element);
} else {
return prts.render(element);
}
}
createTopComponent(initialProps) {
const { request } = this.ref.context.user;
const { subApp } = this.ref;
const TopComponent = createElement(this.StartComponent, initialProps);
return createElement(
AppContext.Provider,
{ value: { isSsr: true, subApp, ssr: { request } } },
TopComponent
);
}
async prepareData() {
const { subApp, subAppServer, context } = this.ref;
const { request } = context.user;
// even though we don't know what data model the component is using, but if it
// has a prepare callback, we will just call it to get initial props to pass
// to the component when rendering it
const prepare = subAppServer.prepare || subApp.prepare;
if (prepare) {
this._initialProps = await prepare({ request, context });
}
return this._initialProps;
}
doSSR() {
return this.renderTo(this.createTopComponent(this._initialProps), this.ref.options);
}
async prepareReduxData() {
const { subApp, subAppServer, context } = this.ref;
const { request } = context.user;
// if sub app has reduxReducers or reduxCreateStore then assume it's using
// redux data model. prepare initial state and store to render it.
let reduxData;
// see if app has a prepare callback, on the server side first, and then the
// app itself, and call it. assume the object it returns would contain the
// initial redux state data.
const prepare = subAppServer.prepare || subApp.prepare;
if (prepare) {
reduxData = await prepare({ request, context });
}
if (!reduxData) {
reduxData = { initialState: {} };
} else {
this.store = reduxData.store;
}
this.initialState = reduxData.initialState || reduxData;
// if subapp didn't request to skip sending initial state, then stringify it
// and attach it to the index html.
if (subAppServer.attachInitialState !== false) {
this.initialStateStr = JSON.stringify(this.initialState);
} else {
this.initialStateStr = "";
}
return await this.prepareReduxStore();
}
async prepareReduxStore() {
const { subApp, context } = this.ref;
// next we take the initial state and create redux store from it
if (!this.store && subApp.reduxCreateStore) {
const storeContainer =
context.user.xarcReduxStoreContainer || (context.user.xarcReduxStoreContainer = {});
this.store = await subApp.reduxCreateStore(this.initialState, storeContainer);
}
assert(
this.store,
`redux subapp ${subApp.name} didn't provide store, reduxCreateStore, or redux bundles`
);
if (!this.store.realize) {
await this.signalStoreReady();
}
return this.store;
}
async realizeReduxStore() {
if (this.store && this.store.realize) {
this.store = this.store.realize();
await this.signalStoreReady();
}
}
async signalStoreReady() {
const reduxStoreReady =
this.ref.subAppServer.reduxStoreReady || this.ref.subApp.reduxStoreReady;
if (reduxStoreReady) {
await reduxStoreReady({ store: this.store });
}
}
}
module.exports = FrameworkLib;