@tramvai/module-render 2.70.1 → 2.72.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/lib/browser.js +9 -233
- package/lib/client/index.browser.js +48 -0
- package/lib/client/renderer.browser.js +50 -0
- package/lib/react/index.browser.js +11 -0
- package/lib/react/index.es.js +11 -0
- package/lib/react/index.js +15 -0
- package/lib/react/pageErrorBoundary.browser.js +23 -0
- package/lib/react/pageErrorBoundary.es.js +23 -0
- package/lib/react/pageErrorBoundary.js +27 -0
- package/lib/react/root.browser.js +58 -0
- package/lib/react/root.es.js +58 -0
- package/lib/react/root.js +62 -0
- package/lib/resourcesInliner/externalFilesHelper.es.js +17 -0
- package/lib/resourcesInliner/externalFilesHelper.js +26 -0
- package/lib/resourcesInliner/fileProcessor.es.js +31 -0
- package/lib/resourcesInliner/fileProcessor.js +40 -0
- package/lib/resourcesInliner/resourcesInliner.es.js +204 -0
- package/lib/resourcesInliner/resourcesInliner.js +213 -0
- package/lib/resourcesInliner/tokens.es.js +15 -0
- package/lib/resourcesInliner/tokens.js +20 -0
- package/lib/resourcesRegistry/index.es.js +28 -0
- package/lib/resourcesRegistry/index.js +36 -0
- package/lib/server/PageBuilder.es.js +93 -0
- package/lib/server/PageBuilder.js +102 -0
- package/lib/server/ReactRenderServer.es.js +90 -0
- package/lib/server/ReactRenderServer.js +98 -0
- package/lib/server/blocks/bundleResource/bundleResource.es.js +62 -0
- package/lib/server/blocks/bundleResource/bundleResource.js +71 -0
- package/lib/server/blocks/polyfill.es.js +35 -0
- package/lib/server/blocks/polyfill.js +39 -0
- package/lib/{server_inline.inline.es.js → server/blocks/preload/onload.inline.es.js} +1 -1
- package/lib/{server_inline.inline.js → server/blocks/preload/onload.inline.js} +2 -0
- package/lib/server/blocks/preload/preloadBlock.es.js +21 -0
- package/lib/server/blocks/preload/preloadBlock.js +30 -0
- package/lib/server/blocks/utils/fetchWebpackStats.es.js +88 -0
- package/lib/server/blocks/utils/fetchWebpackStats.js +115 -0
- package/lib/server/blocks/utils/flushFiles.es.js +33 -0
- package/lib/server/blocks/utils/flushFiles.js +44 -0
- package/lib/server/blocks/utils/requireFunc.es.js +5 -0
- package/lib/server/blocks/utils/requireFunc.js +9 -0
- package/lib/server/constants/performance.es.js +3 -0
- package/lib/server/constants/performance.js +7 -0
- package/lib/server/htmlPageSchema.es.js +33 -0
- package/lib/server/htmlPageSchema.js +37 -0
- package/lib/server/utils.es.js +16 -0
- package/lib/server/utils.js +20 -0
- package/lib/server.es.js +18 -859
- package/lib/server.js +33 -909
- package/lib/shared/LayoutModule.browser.js +40 -0
- package/lib/shared/LayoutModule.es.js +40 -0
- package/lib/shared/LayoutModule.js +42 -0
- package/lib/shared/pageErrorStore.browser.js +19 -0
- package/lib/shared/pageErrorStore.es.js +19 -0
- package/lib/shared/pageErrorStore.js +26 -0
- package/lib/shared/providers.browser.js +18 -0
- package/lib/shared/providers.es.js +18 -0
- package/lib/shared/providers.js +22 -0
- package/package.json +23 -24
package/lib/server.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var tslib = require('tslib');
|
|
6
|
-
var react
|
|
7
|
-
var server
|
|
6
|
+
var react = require('react');
|
|
7
|
+
var server = require('react-dom/server');
|
|
8
8
|
var core = require('@tramvai/core');
|
|
9
9
|
var tokensCommon = require('@tramvai/tokens-common');
|
|
10
10
|
var tokensRouter = require('@tramvai/tokens-router');
|
|
@@ -12,894 +12,18 @@ var moduleClientHints = require('@tramvai/module-client-hints');
|
|
|
12
12
|
var tokensRender = require('@tramvai/tokens-render');
|
|
13
13
|
var dippy = require('@tinkoff/dippy');
|
|
14
14
|
var tokensServerPrivate = require('@tramvai/tokens-server-private');
|
|
15
|
-
var react = require('@tramvai/react');
|
|
15
|
+
var react$1 = require('@tramvai/react');
|
|
16
16
|
var url = require('@tinkoff/url');
|
|
17
17
|
var userAgent = require('@tinkoff/user-agent');
|
|
18
|
-
var
|
|
19
|
-
var
|
|
20
|
-
var
|
|
21
|
-
var
|
|
22
|
-
var
|
|
23
|
-
var
|
|
24
|
-
var
|
|
25
|
-
var
|
|
26
|
-
var
|
|
27
|
-
var has = require('@tinkoff/utils/object/has');
|
|
28
|
-
var last = require('@tinkoff/utils/array/last');
|
|
29
|
-
var experiments = require('@tramvai/experiments');
|
|
30
|
-
var uniq = require('@tinkoff/utils/array/uniq');
|
|
31
|
-
var path = require('path');
|
|
32
|
-
var each = require('@tinkoff/utils/array/each');
|
|
33
|
-
var path$1 = require('@tinkoff/utils/object/path');
|
|
34
|
-
var inline_inline = require('./server_inline.inline.js');
|
|
35
|
-
var stream = require('stream');
|
|
36
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
37
|
-
var state = require('@tramvai/state');
|
|
38
|
-
var moduleRouter = require('@tramvai/module-router');
|
|
39
|
-
var layoutFactory = require('@tinkoff/layout-factory');
|
|
40
|
-
|
|
41
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
42
|
-
|
|
43
|
-
function _interopNamespace(e) {
|
|
44
|
-
if (e && e.__esModule) return e;
|
|
45
|
-
var n = Object.create(null);
|
|
46
|
-
if (e) {
|
|
47
|
-
Object.keys(e).forEach(function (k) {
|
|
48
|
-
if (k !== 'default') {
|
|
49
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
50
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
51
|
-
enumerable: true,
|
|
52
|
-
get: function () { return e[k]; }
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
n["default"] = e;
|
|
58
|
-
return n;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
var isUndefined__default = /*#__PURE__*/_interopDefaultLegacy(isUndefined);
|
|
62
|
-
var isEmpty__default = /*#__PURE__*/_interopDefaultLegacy(isEmpty);
|
|
63
|
-
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
|
|
64
|
-
var startsWith__default = /*#__PURE__*/_interopDefaultLegacy(startsWith);
|
|
65
|
-
var toArray__default = /*#__PURE__*/_interopDefaultLegacy(toArray);
|
|
66
|
-
var flatten__default = /*#__PURE__*/_interopDefaultLegacy(flatten);
|
|
67
|
-
var has__default = /*#__PURE__*/_interopDefaultLegacy(has);
|
|
68
|
-
var last__default = /*#__PURE__*/_interopDefaultLegacy(last);
|
|
69
|
-
var uniq__default = /*#__PURE__*/_interopDefaultLegacy(uniq);
|
|
70
|
-
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
71
|
-
var each__default = /*#__PURE__*/_interopDefaultLegacy(each);
|
|
72
|
-
var path__default = /*#__PURE__*/_interopDefaultLegacy(path$1);
|
|
73
|
-
|
|
74
|
-
const thirtySeconds = 1000 * 30;
|
|
75
|
-
const getFileContentLength = async (url) => {
|
|
76
|
-
const info = await fetch__default["default"](url, { method: 'HEAD', timeout: thirtySeconds });
|
|
77
|
-
return info.headers.get('content-length');
|
|
78
|
-
};
|
|
79
|
-
const getFile = async (url) => {
|
|
80
|
-
const fileResponse = await fetch__default["default"](url, { timeout: thirtySeconds });
|
|
81
|
-
if (fileResponse.ok) {
|
|
82
|
-
const file = await fileResponse.text();
|
|
83
|
-
return file;
|
|
84
|
-
}
|
|
85
|
-
return undefined;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const URL_OCCURRENCES_RE = /(url\((['"]?))(.*?)(\2\))/gi;
|
|
89
|
-
const isAbsoluteUrl = (resourceUrl) => ['http://', 'https://', '//'].some((prefix) => startsWith__default["default"](prefix, resourceUrl));
|
|
90
|
-
const toHttpsUrl = (resourceUrl) => {
|
|
91
|
-
if (resourceUrl.indexOf('//localhost') !== -1) {
|
|
92
|
-
return resourceUrl;
|
|
93
|
-
}
|
|
94
|
-
if (startsWith__default["default"]('http://', resourceUrl)) {
|
|
95
|
-
return resourceUrl.replace('http://', 'https://');
|
|
96
|
-
}
|
|
97
|
-
if (startsWith__default["default"]('//', resourceUrl)) {
|
|
98
|
-
return resourceUrl.replace('//', 'https://');
|
|
99
|
-
}
|
|
100
|
-
return resourceUrl;
|
|
101
|
-
};
|
|
102
|
-
const urlReplacerCreator = (resourceUrl) => (str, leftGroup, _, extractedUrl, rightGroup) => {
|
|
103
|
-
return isAbsoluteUrl(extractedUrl)
|
|
104
|
-
? str
|
|
105
|
-
: `${leftGroup}${url.resolve(toHttpsUrl(resourceUrl), extractedUrl)}${rightGroup}`;
|
|
106
|
-
};
|
|
107
|
-
const processFile = (resource, file) => {
|
|
108
|
-
if (resource.type === tokensRender.ResourceType.style) {
|
|
109
|
-
return file.replace(URL_OCCURRENCES_RE, urlReplacerCreator(resource.payload));
|
|
110
|
-
}
|
|
111
|
-
return file;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const INTERNAL_CACHE_SIZE = 50;
|
|
115
|
-
const ASSETS_PREFIX = process.env.NODE_ENV === 'development' &&
|
|
116
|
-
(process.env.ASSETS_PREFIX === 'static' || !process.env.ASSETS_PREFIX)
|
|
117
|
-
? `http://localhost:${process.env.PORT_STATIC}/dist/`
|
|
118
|
-
: process.env.ASSETS_PREFIX;
|
|
119
|
-
const getInlineType = (type) => {
|
|
120
|
-
switch (type) {
|
|
121
|
-
case tokensRender.ResourceType.style:
|
|
122
|
-
return tokensRender.ResourceType.inlineStyle;
|
|
123
|
-
case tokensRender.ResourceType.script:
|
|
124
|
-
return tokensRender.ResourceType.inlineScript;
|
|
125
|
-
default:
|
|
126
|
-
return type;
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
const getResourceUrl = (resource) => {
|
|
130
|
-
if (isEmpty__default["default"](resource.payload) || !url.isAbsoluteUrl(resource.payload)) {
|
|
131
|
-
return undefined;
|
|
132
|
-
}
|
|
133
|
-
return resource.payload.startsWith('//')
|
|
134
|
-
? `https://${resource.payload.substr(2)}`
|
|
135
|
-
: resource.payload;
|
|
136
|
-
};
|
|
137
|
-
class ResourcesInliner {
|
|
138
|
-
constructor({ resourcesRegistryCache, resourceInlineThreshold, logger }) {
|
|
139
|
-
this.internalFilesCache = new Map();
|
|
140
|
-
this.runningRequests = new Set();
|
|
141
|
-
this.scheduleFileLoad = async (resource, resourceInlineThreshold) => {
|
|
142
|
-
const url = getResourceUrl(resource);
|
|
143
|
-
const requestKey = `file${url}`;
|
|
144
|
-
const filesCache = this.getFilesCache(url);
|
|
145
|
-
const result = filesCache.get(url);
|
|
146
|
-
if (result) {
|
|
147
|
-
return result;
|
|
148
|
-
}
|
|
149
|
-
if (!this.runningRequests.has(requestKey)) {
|
|
150
|
-
this.runningRequests.add(url);
|
|
151
|
-
try {
|
|
152
|
-
const file = await getFile(url);
|
|
153
|
-
if (file === undefined) {
|
|
154
|
-
this.resourcesRegistryCache.disabledUrlsCache.set(url, true);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
const size = file.length;
|
|
158
|
-
if (size < resourceInlineThreshold) {
|
|
159
|
-
filesCache.set(url, processFile(resource, file));
|
|
160
|
-
}
|
|
161
|
-
this.resourcesRegistryCache.sizeCache.set(url, size);
|
|
162
|
-
}
|
|
163
|
-
catch (error) {
|
|
164
|
-
this.log.warn({
|
|
165
|
-
event: 'file-load-failed',
|
|
166
|
-
url,
|
|
167
|
-
error,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
finally {
|
|
171
|
-
this.runningRequests.delete(requestKey);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
this.scheduleFileSizeLoad = async (resource, resourceInlineThreshold) => {
|
|
176
|
-
const url = getResourceUrl(resource);
|
|
177
|
-
const requestKey = `size${url}`;
|
|
178
|
-
const result = this.resourcesRegistryCache.sizeCache.get(url);
|
|
179
|
-
if (result) {
|
|
180
|
-
return result;
|
|
181
|
-
}
|
|
182
|
-
if (!this.runningRequests.has(requestKey)) {
|
|
183
|
-
this.runningRequests.add(requestKey);
|
|
184
|
-
try {
|
|
185
|
-
const contentLength = await getFileContentLength(url);
|
|
186
|
-
const size = isUndefined__default["default"](contentLength) ? 0 : +contentLength;
|
|
187
|
-
if (size) {
|
|
188
|
-
this.resourcesRegistryCache.sizeCache.set(url, size);
|
|
189
|
-
}
|
|
190
|
-
if (size < resourceInlineThreshold) {
|
|
191
|
-
this.scheduleFileLoad(resource, resourceInlineThreshold);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
catch (error) {
|
|
195
|
-
this.log.warn({
|
|
196
|
-
event: 'file-content-length-load-failed',
|
|
197
|
-
url,
|
|
198
|
-
error,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
finally {
|
|
202
|
-
this.runningRequests.delete(requestKey);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
this.resourcesRegistryCache = resourcesRegistryCache;
|
|
207
|
-
this.resourceInlineThreshold = resourceInlineThreshold;
|
|
208
|
-
this.log = logger('resources-inliner');
|
|
209
|
-
}
|
|
210
|
-
getFilesCache(url) {
|
|
211
|
-
if (url.startsWith(ASSETS_PREFIX)) {
|
|
212
|
-
// internal resources are resources generated by the current app itself
|
|
213
|
-
// these kind of resources are pretty static and won't be changed while app is running
|
|
214
|
-
// so we can cache it with bare Map and do not care about how to cleanup cache from outdated entries
|
|
215
|
-
return this.internalFilesCache;
|
|
216
|
-
}
|
|
217
|
-
return this.resourcesRegistryCache.filesCache;
|
|
218
|
-
}
|
|
219
|
-
// check that resource's preload-link should be added to render
|
|
220
|
-
shouldAddResource(resource) {
|
|
221
|
-
if (resource.type !== tokensRender.ResourceType.preloadLink) {
|
|
222
|
-
// only checking preload-links
|
|
223
|
-
return true;
|
|
224
|
-
}
|
|
225
|
-
const url = getResourceUrl(resource);
|
|
226
|
-
if (isUndefined__default["default"](url)) {
|
|
227
|
-
// if url is undefined that file is not in cache
|
|
228
|
-
return true;
|
|
229
|
-
}
|
|
230
|
-
// if file is residing in cache that means it will be inlined in page render
|
|
231
|
-
// therefore no need to have preload-link for the inlined resource
|
|
232
|
-
return !this.getFilesCache(url).has(url);
|
|
233
|
-
}
|
|
234
|
-
// method for check is passed resource should be inlined in HTML-page
|
|
235
|
-
shouldInline(resource) {
|
|
236
|
-
var _a;
|
|
237
|
-
if (!(((_a = this.resourceInlineThreshold) === null || _a === void 0 ? void 0 : _a.types) || []).includes(resource.type)) {
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
const resourceInlineThreshold = this.resourceInlineThreshold.threshold;
|
|
241
|
-
if (isUndefined__default["default"](resourceInlineThreshold)) {
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
const url = getResourceUrl(resource);
|
|
245
|
-
if (isUndefined__default["default"](url) || this.resourcesRegistryCache.disabledUrlsCache.has(url)) {
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
const filesCache = this.getFilesCache(url);
|
|
249
|
-
if (filesCache.has(url)) {
|
|
250
|
-
return true;
|
|
251
|
-
}
|
|
252
|
-
if (filesCache === this.internalFilesCache &&
|
|
253
|
-
this.internalFilesCache.size >= INTERNAL_CACHE_SIZE) {
|
|
254
|
-
// if we've exceeded limits for the internal resources cache ignore any new entries
|
|
255
|
-
return false;
|
|
256
|
-
}
|
|
257
|
-
if (!this.resourcesRegistryCache.sizeCache.has(url)) {
|
|
258
|
-
this.scheduleFileSizeLoad(resource, resourceInlineThreshold);
|
|
259
|
-
return false;
|
|
260
|
-
}
|
|
261
|
-
const size = this.resourcesRegistryCache.sizeCache.get(url);
|
|
262
|
-
if (size > resourceInlineThreshold) {
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
265
|
-
this.scheduleFileLoad(resource, resourceInlineThreshold);
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
inlineResource(resource) {
|
|
269
|
-
const url = getResourceUrl(resource);
|
|
270
|
-
if (isUndefined__default["default"](url)) {
|
|
271
|
-
// usually, it should not happen but anyway check it for safety
|
|
272
|
-
return [resource];
|
|
273
|
-
}
|
|
274
|
-
const text = this.getFilesCache(url).get(url);
|
|
275
|
-
if (isEmpty__default["default"](text)) {
|
|
276
|
-
return [resource];
|
|
277
|
-
}
|
|
278
|
-
const result = [];
|
|
279
|
-
if (process.env.NODE_ENV === 'development') {
|
|
280
|
-
// html comment for debugging inlining in dev mode
|
|
281
|
-
result.push({
|
|
282
|
-
slot: resource.slot,
|
|
283
|
-
type: tokensRender.ResourceType.asIs,
|
|
284
|
-
payload: `<!-- Inlined file ${url} -->`,
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
result.push({
|
|
288
|
-
...resource,
|
|
289
|
-
type: getInlineType(resource.type),
|
|
290
|
-
payload: text,
|
|
291
|
-
});
|
|
292
|
-
if (resource.type === tokensRender.ResourceType.style) {
|
|
293
|
-
// If we don't add data-href then extract-css-chunks-webpack-plugin
|
|
294
|
-
// will add link to resources to the html head (https://github.com/faceyspacey/extract-css-chunks-webpack-plugin/blob/master/src/index.js#L346)
|
|
295
|
-
// wherein link in case of css files plugin will look for a link tag, but we add a style tag
|
|
296
|
-
// so we can't use tag from above and have to generate new one
|
|
297
|
-
result.push({
|
|
298
|
-
slot: resource.slot,
|
|
299
|
-
type: tokensRender.ResourceType.style,
|
|
300
|
-
payload: null,
|
|
301
|
-
attrs: {
|
|
302
|
-
'data-href': resource.payload,
|
|
303
|
-
},
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
return result;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* @description
|
|
312
|
-
* Инлайнер ресурсов - используется на сервере для регистрации файлов, которые должны быть вставлены
|
|
313
|
-
* в итоговую html-страницу в виде ссылки на файл или заинлайнеными полностью
|
|
314
|
-
*/
|
|
315
|
-
const RESOURCE_INLINER = dippy.createToken('resourceInliner');
|
|
316
|
-
/**
|
|
317
|
-
* @description
|
|
318
|
-
* Кэш загруженных ресурсов.
|
|
319
|
-
*/
|
|
320
|
-
const RESOURCES_REGISTRY_CACHE = dippy.createToken('resourcesRegistryCache');
|
|
321
|
-
|
|
322
|
-
class ResourcesRegistry {
|
|
323
|
-
constructor({ resourceInliner }) {
|
|
324
|
-
this.resources = new Set();
|
|
325
|
-
this.resourceInliner = resourceInliner;
|
|
326
|
-
}
|
|
327
|
-
register(resourceOrResources) {
|
|
328
|
-
toArray__default["default"](resourceOrResources).forEach((resource) => {
|
|
329
|
-
this.resources.add(resource);
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
getPageResources() {
|
|
333
|
-
return Array.from(this.resources.values())
|
|
334
|
-
.reduce((acc, resource) => {
|
|
335
|
-
if (this.resourceInliner.shouldInline(resource)) {
|
|
336
|
-
Array.prototype.push.apply(acc, this.resourceInliner.inlineResource(resource));
|
|
337
|
-
}
|
|
338
|
-
else {
|
|
339
|
-
acc.push(resource);
|
|
340
|
-
}
|
|
341
|
-
return acc;
|
|
342
|
-
}, [])
|
|
343
|
-
.filter((resource) => this.resourceInliner.shouldAddResource(resource));
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const PRELOAD_JS = '__preloadJS';
|
|
348
|
-
|
|
349
|
-
const isJs = (file) => /\.js$/.test(file) && !/\.hot-update\.js$/.test(file);
|
|
350
|
-
const isCss = (file) => /\.css$/.test(file);
|
|
351
|
-
const getFilesByType = (files) => {
|
|
352
|
-
const scripts = files.filter(isJs);
|
|
353
|
-
const styles = files.filter(isCss);
|
|
354
|
-
return {
|
|
355
|
-
scripts,
|
|
356
|
-
styles,
|
|
357
|
-
};
|
|
358
|
-
};
|
|
359
|
-
const flushFiles = (chunks, webpackStats, { ignoreDependencies = false, } = {}) => {
|
|
360
|
-
// при использовании namedChunkGroups во все entry-файлы как зависимость попадает runtimeChunk
|
|
361
|
-
// что при повторных вызовах flushChunks вызовет дублирование подключения manifest.js
|
|
362
|
-
// из-за чего приложение может запускаться несколько раз
|
|
363
|
-
// без поля namedChunkGroups flushChunks вернет только сами ассеты для чанков, без зависимостей
|
|
364
|
-
const { assetsByChunkName, namedChunkGroups } = webpackStats;
|
|
365
|
-
const resolvedChunks = [];
|
|
366
|
-
for (const chunk of chunks) {
|
|
367
|
-
if (!ignoreDependencies && (namedChunkGroups === null || namedChunkGroups === void 0 ? void 0 : namedChunkGroups[chunk])) {
|
|
368
|
-
resolvedChunks.push(...namedChunkGroups[chunk].chunks);
|
|
369
|
-
}
|
|
370
|
-
else {
|
|
371
|
-
resolvedChunks.push(chunk);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
const files = flatten__default["default"](uniq__default["default"](resolvedChunks).map((chunk) => assetsByChunkName[chunk]));
|
|
375
|
-
return getFilesByType(files);
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
const requireFunc =
|
|
379
|
-
// @ts-ignore
|
|
380
|
-
typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require;
|
|
381
|
-
|
|
382
|
-
let appConfig;
|
|
383
|
-
try {
|
|
384
|
-
appConfig = require('@tramvai/cli/lib/external/config').appConfig;
|
|
385
|
-
}
|
|
386
|
-
catch (e) { }
|
|
387
|
-
let fetchStats = async () => {
|
|
388
|
-
throw new Error(`Unknown environment`);
|
|
389
|
-
};
|
|
390
|
-
if (process.env.NODE_ENV === 'development') {
|
|
391
|
-
fetchStats = async () => {
|
|
392
|
-
const { modern: configModern, staticHost, staticPort, output } = appConfig;
|
|
393
|
-
const getUrl = (filename) => `http://${staticHost}:${staticPort}/${output.client}/${filename}`;
|
|
394
|
-
const request = await fetch__default["default"](getUrl(configModern ? 'stats.modern.json' : 'stats.json'));
|
|
395
|
-
const stats = await request.json();
|
|
396
|
-
// static - популярная заглушка в env.development.js файлах, надо игнорировать, как было раньше
|
|
397
|
-
const hasAssetsPrefix = process.env.ASSETS_PREFIX && process.env.ASSETS_PREFIX !== 'static';
|
|
398
|
-
const publicPath = hasAssetsPrefix ? process.env.ASSETS_PREFIX : stats.publicPath;
|
|
399
|
-
return {
|
|
400
|
-
...stats,
|
|
401
|
-
publicPath,
|
|
402
|
-
};
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
if (process.env.NODE_ENV === 'test') {
|
|
406
|
-
fetchStats = () => {
|
|
407
|
-
// mock for unit-testing as there is no real static return something just to make server render work
|
|
408
|
-
return Promise.resolve({ publicPath: 'http://localhost:4000/', assetsByChunkName: {} });
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
if (process.env.NODE_ENV === 'production') {
|
|
412
|
-
const SEARCH_PATHS = [process.cwd(), __dirname];
|
|
413
|
-
const webpackStats = (fileName) => {
|
|
414
|
-
let stats;
|
|
415
|
-
for (const dir of SEARCH_PATHS) {
|
|
416
|
-
try {
|
|
417
|
-
const statsPath = path__namespace.resolve(dir, fileName);
|
|
418
|
-
stats = requireFunc(statsPath);
|
|
419
|
-
break;
|
|
420
|
-
}
|
|
421
|
-
catch (e) {
|
|
422
|
-
// ignore errors as this function is used to load stats for several optional destinations
|
|
423
|
-
// and these destinations may not have stats file
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
if (!stats) {
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
if (!process.env.ASSETS_PREFIX) {
|
|
430
|
-
if (process.env.STATIC_PREFIX) {
|
|
431
|
-
throw new Error('Required env variable "ASSETS_PREFIX" is not set. Instead of using "STATIC_PREFIX" env please define "ASSETS_PREFIX: STATIC_PREFIX + /compiled"');
|
|
432
|
-
}
|
|
433
|
-
throw new Error('Required env variable "ASSETS_PREFIX" is not set');
|
|
434
|
-
}
|
|
435
|
-
return {
|
|
436
|
-
...stats,
|
|
437
|
-
publicPath: process.env.ASSETS_PREFIX,
|
|
438
|
-
};
|
|
439
|
-
};
|
|
440
|
-
const statsLegacy = webpackStats('stats.json');
|
|
441
|
-
const statsModern = webpackStats('stats.modern.json') || statsLegacy;
|
|
442
|
-
if (!statsLegacy) {
|
|
443
|
-
throw new Error(`Cannot find stats.json.
|
|
444
|
-
It should be placed in one of the next places:
|
|
445
|
-
${SEARCH_PATHS.join('\n\t')}
|
|
446
|
-
In case it happens on deployment:
|
|
447
|
-
- In case you are using two independent jobs for building app
|
|
448
|
-
- Either do not split build command by two independent jobs and use one common job with "tramvai build" command without --buildType
|
|
449
|
-
- Or copy stats.json (and stats.modern.json if present) file from client build output to server output by yourself in your CI
|
|
450
|
-
- Otherwise report issue to tramvai team
|
|
451
|
-
In case it happens locally:
|
|
452
|
-
- prefer to use command "tramvai start-prod" to test prod-build locally
|
|
453
|
-
- copy stats.json next to built server.js file
|
|
454
|
-
`);
|
|
455
|
-
}
|
|
456
|
-
fetchStats = (modern) => {
|
|
457
|
-
const stats = modern ? statsModern : statsLegacy;
|
|
458
|
-
return Promise.resolve(stats);
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
const fetchWebpackStats = async ({ modern, } = {}) => {
|
|
462
|
-
return fetchStats(modern);
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
const bundleResource = async ({ bundle, modern, extractor, pageComponent, }) => {
|
|
466
|
-
// for file-system pages preload page chunk against bundle chunk
|
|
467
|
-
const chunkNameFromBundle = experiments.isFileSystemPageComponent(pageComponent)
|
|
468
|
-
? experiments.fileSystemPageToWebpackChunkName(pageComponent)
|
|
469
|
-
: last__default["default"](bundle.split('/'));
|
|
470
|
-
const webpackStats = await fetchWebpackStats({ modern });
|
|
471
|
-
const { publicPath, assetsByChunkName } = webpackStats;
|
|
472
|
-
const bundles = has__default["default"]('common-chunk', assetsByChunkName)
|
|
473
|
-
? ['common-chunk', chunkNameFromBundle]
|
|
474
|
-
: [chunkNameFromBundle];
|
|
475
|
-
const lazyChunks = extractor.getMainAssets().map((entry) => entry.chunk);
|
|
476
|
-
const { scripts: baseScripts } = flushFiles(['vendor'], webpackStats, {
|
|
477
|
-
ignoreDependencies: true,
|
|
478
|
-
});
|
|
479
|
-
const { scripts, styles } = flushFiles([...bundles, ...lazyChunks, 'platform'], webpackStats);
|
|
480
|
-
const genHref = (href) => `${publicPath}${href}`;
|
|
481
|
-
const result = [];
|
|
482
|
-
if (process.env.NODE_ENV === 'production' ||
|
|
483
|
-
(process.env.ASSETS_PREFIX && process.env.ASSETS_PREFIX !== 'static')) {
|
|
484
|
-
result.push({
|
|
485
|
-
type: tokensRender.ResourceType.inlineScript,
|
|
486
|
-
slot: tokensRender.ResourceSlot.HEAD_CORE_SCRIPTS,
|
|
487
|
-
payload: `window.ap = ${`"${process.env.ASSETS_PREFIX}"`};`,
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
styles.map((style) => result.push({
|
|
491
|
-
type: tokensRender.ResourceType.style,
|
|
492
|
-
slot: tokensRender.ResourceSlot.HEAD_CORE_STYLES,
|
|
493
|
-
payload: genHref(style),
|
|
494
|
-
attrs: {
|
|
495
|
-
'data-critical': 'true',
|
|
496
|
-
onload: `${PRELOAD_JS}()`,
|
|
497
|
-
},
|
|
498
|
-
}));
|
|
499
|
-
baseScripts.map((script) => result.push({
|
|
500
|
-
type: tokensRender.ResourceType.script,
|
|
501
|
-
slot: tokensRender.ResourceSlot.HEAD_CORE_SCRIPTS,
|
|
502
|
-
payload: genHref(script),
|
|
503
|
-
attrs: {
|
|
504
|
-
'data-critical': 'true',
|
|
505
|
-
},
|
|
506
|
-
}));
|
|
507
|
-
scripts.map((script) => result.push({
|
|
508
|
-
type: tokensRender.ResourceType.script,
|
|
509
|
-
slot: tokensRender.ResourceSlot.HEAD_CORE_SCRIPTS,
|
|
510
|
-
payload: genHref(script),
|
|
511
|
-
attrs: {
|
|
512
|
-
'data-critical': 'true',
|
|
513
|
-
},
|
|
514
|
-
}));
|
|
515
|
-
return result;
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
const polyfillResources = async ({ condition, modern, }) => {
|
|
519
|
-
const webpackStats = await fetchWebpackStats({ modern });
|
|
520
|
-
const { publicPath } = webpackStats;
|
|
521
|
-
// получает файл полифилла из stats.json\stats.modern.json.
|
|
522
|
-
// В зависимости от версии браузера будет использован полифилл из legacy или modern сборки,
|
|
523
|
-
// т.к. полифиллы для них могут отличаться на основании преобразований `@babel/preset-env`
|
|
524
|
-
const { scripts: polyfillScripts } = flushFiles(['polyfill'], webpackStats, {
|
|
525
|
-
ignoreDependencies: true,
|
|
526
|
-
});
|
|
527
|
-
const genHref = (href) => `${publicPath}${href}`;
|
|
528
|
-
const result = [];
|
|
529
|
-
polyfillScripts.forEach((script) => {
|
|
530
|
-
const href = genHref(script);
|
|
531
|
-
result.push({
|
|
532
|
-
type: tokensRender.ResourceType.inlineScript,
|
|
533
|
-
slot: tokensRender.ResourceSlot.HEAD_POLYFILLS,
|
|
534
|
-
payload: `(function (){
|
|
535
|
-
var con;
|
|
536
|
-
try {
|
|
537
|
-
con = ${condition};
|
|
538
|
-
} catch (e) {
|
|
539
|
-
con = true;
|
|
540
|
-
}
|
|
541
|
-
if (con) { document.write('<script defer="defer" charset="utf-8" data-critical="true" crossorigin="anonymous" src="${href}"><\\/script>')}
|
|
542
|
-
})()`,
|
|
543
|
-
});
|
|
544
|
-
});
|
|
545
|
-
return result;
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
const addPreloadForCriticalJS = (pageResources) => {
|
|
549
|
-
const jsUrls = [];
|
|
550
|
-
each__default["default"]((res) => {
|
|
551
|
-
if (res.type === 'script' && path__default["default"](['attrs', 'data-critical'], res)) {
|
|
552
|
-
jsUrls.push(res.payload);
|
|
553
|
-
}
|
|
554
|
-
}, pageResources);
|
|
555
|
-
return {
|
|
556
|
-
type: tokensRender.ResourceType.inlineScript,
|
|
557
|
-
slot: tokensRender.ResourceSlot.HEAD_PERFORMANCE,
|
|
558
|
-
payload: `window.${PRELOAD_JS}=(${inline_inline.onload})([${jsUrls.map((url) => `"${url}"`).join(',')}])`,
|
|
559
|
-
};
|
|
560
|
-
};
|
|
561
|
-
|
|
562
|
-
const formatAttributes = (htmlAttrs, target) => {
|
|
563
|
-
if (!htmlAttrs) {
|
|
564
|
-
return '';
|
|
565
|
-
}
|
|
566
|
-
const targetAttrs = htmlAttrs.filter((item) => item.target === target);
|
|
567
|
-
const collectedAttrs = targetAttrs.reduce((acc, item) => ({ ...acc, ...item.attrs }), {});
|
|
568
|
-
const attrsString = Object.keys(collectedAttrs).reduce((acc, name) => {
|
|
569
|
-
if (collectedAttrs[name] === true) {
|
|
570
|
-
return `${acc} ${name}`;
|
|
571
|
-
}
|
|
572
|
-
return `${acc} ${name}="${collectedAttrs[name]}"`;
|
|
573
|
-
}, '');
|
|
574
|
-
return attrsString.trim();
|
|
575
|
-
};
|
|
576
|
-
|
|
577
|
-
/* eslint-disable sort-class-members/sort-class-members */
|
|
578
|
-
const mapResourcesToSlots = (resources) => resources.reduce((acc, resource) => {
|
|
579
|
-
const { slot } = resource;
|
|
580
|
-
if (Array.isArray(acc[slot])) {
|
|
581
|
-
acc[slot].push(resource);
|
|
582
|
-
}
|
|
583
|
-
else {
|
|
584
|
-
acc[slot] = [resource];
|
|
585
|
-
}
|
|
586
|
-
return acc;
|
|
587
|
-
}, {});
|
|
588
|
-
class PageBuilder {
|
|
589
|
-
constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, renderFlowAfter, logger, }) {
|
|
590
|
-
this.htmlAttrs = htmlAttrs;
|
|
591
|
-
this.renderSlots = flatten__default["default"](renderSlots || []);
|
|
592
|
-
this.pageService = pageService;
|
|
593
|
-
this.context = context;
|
|
594
|
-
this.resourcesRegistry = resourcesRegistry;
|
|
595
|
-
this.reactRender = reactRender;
|
|
596
|
-
this.htmlPageSchema = htmlPageSchema;
|
|
597
|
-
this.polyfillCondition = polyfillCondition;
|
|
598
|
-
this.modern = modern;
|
|
599
|
-
this.renderFlowAfter = renderFlowAfter || [];
|
|
600
|
-
this.log = logger('page-builder');
|
|
601
|
-
}
|
|
602
|
-
async flow() {
|
|
603
|
-
const stats = await fetchWebpackStats({ modern: this.modern });
|
|
604
|
-
const extractor = new server.ChunkExtractor({ stats, entrypoints: [] });
|
|
605
|
-
// first we render the application, because we need to extract information about the data used by the components
|
|
606
|
-
await this.renderApp(extractor);
|
|
607
|
-
await Promise.all(this.renderFlowAfter.map((callback) => callback().catch((error) => {
|
|
608
|
-
this.log.warn({ event: 'render-flow-after-error', callback, error });
|
|
609
|
-
})));
|
|
610
|
-
this.dehydrateState();
|
|
611
|
-
// load information and dependency for the current bundle and page
|
|
612
|
-
await this.fetchChunksInfo(extractor);
|
|
613
|
-
this.preloadBlock();
|
|
614
|
-
return this.generateHtml();
|
|
615
|
-
}
|
|
616
|
-
dehydrateState() {
|
|
617
|
-
this.resourcesRegistry.register({
|
|
618
|
-
type: tokensRender.ResourceType.asIs,
|
|
619
|
-
slot: tokensRender.ResourceSlot.BODY_END,
|
|
620
|
-
// String much better than big object, source https://v8.dev/blog/cost-of-javascript-2019#json
|
|
621
|
-
payload: `<script id="__TRAMVAI_STATE__" type="application/json">${safeStrings.safeStringify(this.context.dehydrate().dispatcher)}</script>`,
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
async fetchChunksInfo(extractor) {
|
|
625
|
-
const { modern } = this;
|
|
626
|
-
const { bundle, pageComponent } = this.pageService.getConfig();
|
|
627
|
-
this.resourcesRegistry.register(await bundleResource({ bundle, modern, extractor, pageComponent }));
|
|
628
|
-
this.resourcesRegistry.register(await polyfillResources({
|
|
629
|
-
condition: this.polyfillCondition,
|
|
630
|
-
modern,
|
|
631
|
-
}));
|
|
632
|
-
}
|
|
633
|
-
preloadBlock() {
|
|
634
|
-
const preloadResources = addPreloadForCriticalJS(this.resourcesRegistry.getPageResources());
|
|
635
|
-
this.resourcesRegistry.register(preloadResources);
|
|
636
|
-
}
|
|
637
|
-
generateHtml() {
|
|
638
|
-
const resultSlotHandlers = mapResourcesToSlots([
|
|
639
|
-
...this.renderSlots,
|
|
640
|
-
...this.resourcesRegistry.getPageResources(),
|
|
641
|
-
]);
|
|
642
|
-
return htmlpagebuilder.buildPage({
|
|
643
|
-
slotHandlers: resultSlotHandlers,
|
|
644
|
-
description: this.htmlPageSchema,
|
|
645
|
-
});
|
|
646
|
-
}
|
|
647
|
-
async renderApp(extractor) {
|
|
648
|
-
const html = await this.reactRender.render(extractor);
|
|
649
|
-
this.renderSlots = this.renderSlots.concat({
|
|
650
|
-
type: tokensRender.ResourceType.asIs,
|
|
651
|
-
slot: tokensRender.ResourceSlot.REACT_RENDER,
|
|
652
|
-
payload: `<div ${formatAttributes(this.htmlAttrs, 'app')}>${html}</div>`,
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
/* eslint-enable sort-class-members/sort-class-members */
|
|
657
|
-
|
|
658
|
-
const { REACT_RENDER, HEAD_CORE_SCRIPTS, HEAD_DYNAMIC_SCRIPTS, HEAD_META, HEAD_POLYFILLS, HEAD_CORE_STYLES, HEAD_PERFORMANCE, HEAD_ANALYTICS, BODY_START, BODY_END, HEAD_ICONS, BODY_TAIL_ANALYTICS, BODY_TAIL, } = tokensRender.ResourceSlot;
|
|
659
|
-
const htmlPageSchemaFactory = ({ htmlAttrs, }) => {
|
|
660
|
-
return [
|
|
661
|
-
htmlpagebuilder.staticRender('<!DOCTYPE html>'),
|
|
662
|
-
htmlpagebuilder.staticRender(`<html ${formatAttributes(htmlAttrs, 'html')}>`),
|
|
663
|
-
htmlpagebuilder.staticRender('<head>'),
|
|
664
|
-
htmlpagebuilder.staticRender('<meta charset="UTF-8">'),
|
|
665
|
-
htmlpagebuilder.dynamicRender(HEAD_META),
|
|
666
|
-
htmlpagebuilder.dynamicRender(HEAD_PERFORMANCE),
|
|
667
|
-
htmlpagebuilder.dynamicRender(HEAD_CORE_STYLES),
|
|
668
|
-
htmlpagebuilder.dynamicRender(HEAD_POLYFILLS),
|
|
669
|
-
htmlpagebuilder.dynamicRender(HEAD_DYNAMIC_SCRIPTS),
|
|
670
|
-
htmlpagebuilder.dynamicRender(HEAD_CORE_SCRIPTS),
|
|
671
|
-
htmlpagebuilder.dynamicRender(HEAD_ANALYTICS),
|
|
672
|
-
htmlpagebuilder.dynamicRender(HEAD_ICONS),
|
|
673
|
-
htmlpagebuilder.staticRender('</head>'),
|
|
674
|
-
htmlpagebuilder.staticRender(`<body ${formatAttributes(htmlAttrs, 'body')}>`),
|
|
675
|
-
htmlpagebuilder.dynamicRender(BODY_START),
|
|
676
|
-
// react app
|
|
677
|
-
htmlpagebuilder.dynamicRender(REACT_RENDER),
|
|
678
|
-
htmlpagebuilder.dynamicRender(BODY_END),
|
|
679
|
-
htmlpagebuilder.dynamicRender(BODY_TAIL_ANALYTICS),
|
|
680
|
-
htmlpagebuilder.dynamicRender(BODY_TAIL),
|
|
681
|
-
htmlpagebuilder.staticRender('</body>'),
|
|
682
|
-
htmlpagebuilder.staticRender('</html>'),
|
|
683
|
-
];
|
|
684
|
-
};
|
|
685
|
-
|
|
686
|
-
function serializeError(error) {
|
|
687
|
-
return {
|
|
688
|
-
...error,
|
|
689
|
-
message: error.message,
|
|
690
|
-
stack: error.stack,
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
function deserializeError(serializedError) {
|
|
694
|
-
const error = new Error(serializedError.message);
|
|
695
|
-
Object.assign(error, serializedError);
|
|
696
|
-
return error;
|
|
697
|
-
}
|
|
698
|
-
const setPageErrorEvent = state.createEvent('setPageError');
|
|
699
|
-
const initialState = null;
|
|
700
|
-
const PageErrorStore = state.createReducer('pageError', initialState).on(setPageErrorEvent, (state, error) => error && serializeError(error));
|
|
701
|
-
|
|
702
|
-
const PageErrorBoundary = (props) => {
|
|
703
|
-
const { children } = props;
|
|
704
|
-
const pageService = react.useDi(tokensRouter.PAGE_SERVICE_TOKEN);
|
|
705
|
-
const url = moduleRouter.useUrl();
|
|
706
|
-
const serializedError = state.useStore(PageErrorStore);
|
|
707
|
-
const error = react$1.useMemo(() => {
|
|
708
|
-
return serializedError && deserializeError(serializedError);
|
|
709
|
-
}, [serializedError]);
|
|
710
|
-
const errorHandlers = react.useDi({ token: react.ERROR_BOUNDARY_TOKEN, optional: true });
|
|
711
|
-
const fallbackFromDi = react.useDi({ token: react.ERROR_BOUNDARY_FALLBACK_COMPONENT_TOKEN, optional: true });
|
|
712
|
-
const fallback = pageService.resolveComponentFromConfig('errorBoundary');
|
|
713
|
-
return (jsxRuntime.jsx(react.UniversalErrorBoundary, { url: url, error: error, errorHandlers: errorHandlers, fallback: fallback, fallbackFromDi: fallbackFromDi, children: children }));
|
|
714
|
-
};
|
|
715
|
-
|
|
716
|
-
/**
|
|
717
|
-
* Result component structure:
|
|
718
|
-
*
|
|
719
|
-
* <Root>
|
|
720
|
-
* <RootComponent>
|
|
721
|
-
* <LayoutComponent>
|
|
722
|
-
* <NestedLayoutComponent>
|
|
723
|
-
* <ErrorBoundaryComponent>
|
|
724
|
-
* <PageComponent />
|
|
725
|
-
* </ErrorBoundaryComponent>
|
|
726
|
-
* </NestedLayoutComponent>
|
|
727
|
-
* </LayoutComponent>
|
|
728
|
-
* </RootComponent>
|
|
729
|
-
* </Root>
|
|
730
|
-
*
|
|
731
|
-
* All components separated for a few reasons:
|
|
732
|
-
* - Page subtree can be rendered independently when Layout and Nested Layout the same
|
|
733
|
-
* - Nested Layout can be rerendered only on its changes
|
|
734
|
-
* - Layout can be rendered only on its changes
|
|
735
|
-
*/
|
|
736
|
-
const LayoutRenderComponent = ({ children }) => {
|
|
737
|
-
const pageService = moduleRouter.usePageService();
|
|
738
|
-
const LayoutComponent = pageService.resolveComponentFromConfig('layout');
|
|
739
|
-
const HeaderComponent = pageService.resolveComponentFromConfig('header');
|
|
740
|
-
const FooterComponent = pageService.resolveComponentFromConfig('footer');
|
|
741
|
-
const layout = react$1.useMemo(() => (jsxRuntime.jsx(LayoutComponent, { Header: HeaderComponent, Footer: FooterComponent, children: children })), [LayoutComponent, HeaderComponent, FooterComponent, children]);
|
|
742
|
-
return layout;
|
|
743
|
-
};
|
|
744
|
-
const NestedLayoutRenderComponent = ({ children }) => {
|
|
745
|
-
const pageService = moduleRouter.usePageService();
|
|
746
|
-
const NestedLayoutComponent = pageService.resolveComponentFromConfig('nestedLayout');
|
|
747
|
-
const nestedLayout = react$1.useMemo(() => jsxRuntime.jsx(NestedLayoutComponent, { children: children }), [NestedLayoutComponent, children]);
|
|
748
|
-
return nestedLayout;
|
|
749
|
-
};
|
|
750
|
-
const PageRenderComponent = () => {
|
|
751
|
-
const pageService = moduleRouter.usePageService();
|
|
752
|
-
const { pageComponent } = pageService.getConfig();
|
|
753
|
-
let PageComponent = pageService.getComponent(pageComponent);
|
|
754
|
-
if (!PageComponent) {
|
|
755
|
-
PageComponent = () => {
|
|
756
|
-
throw new Error(`Page component '${pageComponent}' not found`);
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
const page = react$1.useMemo(() => (jsxRuntime.jsx(PageErrorBoundary, { children: jsxRuntime.jsx(PageComponent, {}) })), [PageComponent]);
|
|
760
|
-
return page;
|
|
761
|
-
};
|
|
762
|
-
const Root = () => {
|
|
763
|
-
const pageRenderComponent = react$1.useMemo(() => jsxRuntime.jsx(PageRenderComponent, {}), []);
|
|
764
|
-
const nestedLayoutRenderComponent = react$1.useMemo(() => jsxRuntime.jsx(NestedLayoutRenderComponent, { children: pageRenderComponent }), [pageRenderComponent]);
|
|
765
|
-
return jsxRuntime.jsx(LayoutRenderComponent, { children: nestedLayoutRenderComponent });
|
|
766
|
-
};
|
|
767
|
-
|
|
768
|
-
function renderReact({ di }, context) {
|
|
769
|
-
const serverState = typeof window !== 'undefined' ? context.getState() : undefined;
|
|
770
|
-
return (jsxRuntime.jsx(state.Provider, { context: context, serverState: serverState, children: jsxRuntime.jsx(react.DIContext.Provider, { value: di, children: jsxRuntime.jsx(Root, {}) }) }));
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
const RENDER_TIMEOUT = 500;
|
|
774
|
-
class HtmlWritable extends stream.Writable {
|
|
775
|
-
constructor() {
|
|
776
|
-
super(...arguments);
|
|
777
|
-
this.chunks = [];
|
|
778
|
-
this.html = '';
|
|
779
|
-
}
|
|
780
|
-
getHtml() {
|
|
781
|
-
return this.html;
|
|
782
|
-
}
|
|
783
|
-
_write(chunk, encoding, callback) {
|
|
784
|
-
this.chunks.push(chunk);
|
|
785
|
-
callback();
|
|
786
|
-
}
|
|
787
|
-
_final(callback) {
|
|
788
|
-
this.html = Buffer.concat(this.chunks).toString();
|
|
789
|
-
callback();
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
class ReactRenderServer {
|
|
793
|
-
// eslint-disable-next-line sort-class-members/sort-class-members
|
|
794
|
-
constructor({ context, customRender, extendRender, di, renderMode, logger }) {
|
|
795
|
-
this.context = context;
|
|
796
|
-
this.customRender = customRender;
|
|
797
|
-
this.extendRender = extendRender;
|
|
798
|
-
this.di = di;
|
|
799
|
-
this.renderMode = renderMode;
|
|
800
|
-
this.log = logger('module-render');
|
|
801
|
-
}
|
|
802
|
-
render(extractor) {
|
|
803
|
-
var _a;
|
|
804
|
-
let renderResult = renderReact({ di: this.di }, this.context);
|
|
805
|
-
each__default["default"]((render) => {
|
|
806
|
-
renderResult = render(renderResult);
|
|
807
|
-
}, (_a = this.extendRender) !== null && _a !== void 0 ? _a : []);
|
|
808
|
-
renderResult = extractor.collectChunks(renderResult);
|
|
809
|
-
if (this.customRender) {
|
|
810
|
-
return this.customRender(renderResult);
|
|
811
|
-
}
|
|
812
|
-
if (process.env.__TRAMVAI_CONCURRENT_FEATURES && this.renderMode === 'streaming') {
|
|
813
|
-
return new Promise((resolve, reject) => {
|
|
814
|
-
const { renderToPipeableStream } = require('react-dom/server');
|
|
815
|
-
const htmlWritable = new HtmlWritable();
|
|
816
|
-
htmlWritable.on('finish', () => {
|
|
817
|
-
resolve(htmlWritable.getHtml());
|
|
818
|
-
});
|
|
819
|
-
const start = Date.now();
|
|
820
|
-
const { log } = this;
|
|
821
|
-
log.info({
|
|
822
|
-
event: 'streaming-render:start',
|
|
823
|
-
});
|
|
824
|
-
const { pipe, abort } = renderToPipeableStream(renderResult, {
|
|
825
|
-
onAllReady() {
|
|
826
|
-
log.info({
|
|
827
|
-
event: 'streaming-render:complete',
|
|
828
|
-
duration: Date.now() - start,
|
|
829
|
-
});
|
|
830
|
-
// here `write` will be called only once
|
|
831
|
-
pipe(htmlWritable);
|
|
832
|
-
},
|
|
833
|
-
onError(error) {
|
|
834
|
-
// error can be inside Suspense boundaries, this is not critical, continue rendering.
|
|
835
|
-
// for criticall errors, this callback will be called with `onShellError`,
|
|
836
|
-
// so this is a best place to error logging
|
|
837
|
-
log.error({
|
|
838
|
-
event: 'streaming-render:error',
|
|
839
|
-
error,
|
|
840
|
-
});
|
|
841
|
-
},
|
|
842
|
-
onShellError(error) {
|
|
843
|
-
// always critical error, abort rendering
|
|
844
|
-
reject(error);
|
|
845
|
-
},
|
|
846
|
-
});
|
|
847
|
-
setTimeout(() => {
|
|
848
|
-
abort();
|
|
849
|
-
reject(new Error('React renderToPipeableStream timeout exceeded'));
|
|
850
|
-
}, RENDER_TIMEOUT);
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
const { renderToString } = require('react-dom/server');
|
|
854
|
-
return Promise.resolve(renderToString(renderResult));
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
const RenderChildrenComponent = ({ children }) => children;
|
|
859
|
-
let LayoutModule = class LayoutModule {
|
|
860
|
-
};
|
|
861
|
-
LayoutModule = tslib.__decorate([
|
|
862
|
-
core.Module({
|
|
863
|
-
providers: [
|
|
864
|
-
{
|
|
865
|
-
provide: tokensRender.DEFAULT_LAYOUT_COMPONENT,
|
|
866
|
-
useFactory: ({ layoutOptions }) => {
|
|
867
|
-
const options = layoutFactory.composeLayoutOptions(layoutOptions);
|
|
868
|
-
return layoutFactory.createLayout(options);
|
|
869
|
-
},
|
|
870
|
-
deps: {
|
|
871
|
-
layoutOptions: { token: tokensRender.LAYOUT_OPTIONS, optional: true },
|
|
872
|
-
},
|
|
873
|
-
},
|
|
874
|
-
{
|
|
875
|
-
provide: 'componentDefaultList',
|
|
876
|
-
multi: true,
|
|
877
|
-
useFactory: (components) => ({
|
|
878
|
-
...components,
|
|
879
|
-
nestedLayoutDefault: RenderChildrenComponent,
|
|
880
|
-
}),
|
|
881
|
-
deps: {
|
|
882
|
-
layoutDefault: tokensRender.DEFAULT_LAYOUT_COMPONENT,
|
|
883
|
-
footerDefault: { token: tokensRender.DEFAULT_FOOTER_COMPONENT, optional: true },
|
|
884
|
-
headerDefault: { token: tokensRender.DEFAULT_HEADER_COMPONENT, optional: true },
|
|
885
|
-
errorBoundaryDefault: { token: tokensRender.DEFAULT_ERROR_BOUNDARY_COMPONENT, optional: true },
|
|
886
|
-
},
|
|
887
|
-
},
|
|
888
|
-
],
|
|
889
|
-
})
|
|
890
|
-
], LayoutModule);
|
|
891
|
-
|
|
892
|
-
const providers = [
|
|
893
|
-
core.provide({
|
|
894
|
-
provide: tokensCommon.COMBINE_REDUCERS,
|
|
895
|
-
multi: true,
|
|
896
|
-
useValue: PageErrorStore,
|
|
897
|
-
}),
|
|
898
|
-
core.provide({
|
|
899
|
-
provide: tokensRender.TRAMVAI_RENDER_MODE,
|
|
900
|
-
useValue: 'ssr',
|
|
901
|
-
}),
|
|
902
|
-
];
|
|
18
|
+
var resourcesInliner = require('./resourcesInliner/resourcesInliner.js');
|
|
19
|
+
var tokens = require('./resourcesInliner/tokens.js');
|
|
20
|
+
var index = require('./resourcesRegistry/index.js');
|
|
21
|
+
var PageBuilder = require('./server/PageBuilder.js');
|
|
22
|
+
var htmlPageSchema = require('./server/htmlPageSchema.js');
|
|
23
|
+
var ReactRenderServer = require('./server/ReactRenderServer.js');
|
|
24
|
+
var LayoutModule = require('./shared/LayoutModule.js');
|
|
25
|
+
var providers = require('./shared/providers.js');
|
|
26
|
+
var pageErrorStore = require('./shared/pageErrorStore.js');
|
|
903
27
|
|
|
904
28
|
var RenderModule_1;
|
|
905
29
|
const REQUEST_TTL = 5 * 60 * 1000;
|
|
@@ -921,18 +45,18 @@ exports.RenderModule = RenderModule_1 = class RenderModule {
|
|
|
921
45
|
};
|
|
922
46
|
exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
923
47
|
core.Module({
|
|
924
|
-
imports: [moduleClientHints.ClientHintsModule, LayoutModule],
|
|
48
|
+
imports: [moduleClientHints.ClientHintsModule, LayoutModule.LayoutModule],
|
|
925
49
|
providers: [
|
|
926
|
-
...providers,
|
|
50
|
+
...providers.providers,
|
|
927
51
|
core.provide({
|
|
928
52
|
provide: tokensRender.RESOURCES_REGISTRY,
|
|
929
|
-
useClass: ResourcesRegistry,
|
|
53
|
+
useClass: index.ResourcesRegistry,
|
|
930
54
|
deps: {
|
|
931
|
-
resourceInliner: RESOURCE_INLINER,
|
|
55
|
+
resourceInliner: tokens.RESOURCE_INLINER,
|
|
932
56
|
},
|
|
933
57
|
}),
|
|
934
58
|
core.provide({
|
|
935
|
-
provide: RESOURCES_REGISTRY_CACHE,
|
|
59
|
+
provide: tokens.RESOURCES_REGISTRY_CACHE,
|
|
936
60
|
scope: dippy.Scope.SINGLETON,
|
|
937
61
|
useFactory: ({ createCache }) => {
|
|
938
62
|
return {
|
|
@@ -946,11 +70,11 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
946
70
|
},
|
|
947
71
|
}),
|
|
948
72
|
core.provide({
|
|
949
|
-
provide: RESOURCE_INLINER,
|
|
73
|
+
provide: tokens.RESOURCE_INLINER,
|
|
950
74
|
scope: dippy.Scope.SINGLETON,
|
|
951
|
-
useClass: ResourcesInliner,
|
|
75
|
+
useClass: resourcesInliner.ResourcesInliner,
|
|
952
76
|
deps: {
|
|
953
|
-
resourcesRegistryCache: RESOURCES_REGISTRY_CACHE,
|
|
77
|
+
resourcesRegistryCache: tokens.RESOURCES_REGISTRY_CACHE,
|
|
954
78
|
resourceInlineThreshold: { token: tokensRender.RESOURCE_INLINE_OPTIONS, optional: true },
|
|
955
79
|
logger: tokensCommon.LOGGER_TOKEN,
|
|
956
80
|
},
|
|
@@ -968,7 +92,7 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
968
92
|
// assuming that there was an error when rendering the page, try to render again with ErrorBoundary
|
|
969
93
|
try {
|
|
970
94
|
log.info({ event: 'render-page-boundary-start' });
|
|
971
|
-
context.dispatch(setPageErrorEvent(error));
|
|
95
|
+
context.dispatch(pageErrorStore.setPageErrorEvent(error));
|
|
972
96
|
html = await htmlBuilder.flow();
|
|
973
97
|
log.info({ event: 'render-page-boundary-success' });
|
|
974
98
|
}
|
|
@@ -979,7 +103,7 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
979
103
|
throw error;
|
|
980
104
|
}
|
|
981
105
|
}
|
|
982
|
-
const pageRenderError = context.getState(PageErrorStore);
|
|
106
|
+
const pageRenderError = context.getState(pageErrorStore.PageErrorStore);
|
|
983
107
|
// log send-server-error only after successful Page Boundary render,
|
|
984
108
|
// otherwise this event will be logged in default error handler
|
|
985
109
|
if (pageRenderError) {
|
|
@@ -993,7 +117,7 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
993
117
|
log.error({
|
|
994
118
|
event: 'send-server-error',
|
|
995
119
|
message: 'Page render error, switch to page boundary',
|
|
996
|
-
error: deserializeError(pageRenderError),
|
|
120
|
+
error: pageErrorStore.deserializeError(pageRenderError),
|
|
997
121
|
requestInfo,
|
|
998
122
|
});
|
|
999
123
|
}
|
|
@@ -1018,7 +142,7 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
1018
142
|
}),
|
|
1019
143
|
core.provide({
|
|
1020
144
|
provide: 'htmlBuilder',
|
|
1021
|
-
useClass: PageBuilder,
|
|
145
|
+
useClass: PageBuilder.PageBuilder,
|
|
1022
146
|
deps: {
|
|
1023
147
|
reactRender: 'reactRender',
|
|
1024
148
|
pageService: tokensRouter.PAGE_SERVICE_TOKEN,
|
|
@@ -1035,7 +159,7 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
1035
159
|
}),
|
|
1036
160
|
core.provide({
|
|
1037
161
|
provide: 'reactRender',
|
|
1038
|
-
useClass: ReactRenderServer,
|
|
162
|
+
useClass: ReactRenderServer.ReactRenderServer,
|
|
1039
163
|
deps: {
|
|
1040
164
|
context: tokensCommon.CONTEXT_TOKEN,
|
|
1041
165
|
customRender: { token: tokensRender.CUSTOM_RENDER, optional: true },
|
|
@@ -1047,7 +171,7 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
1047
171
|
}),
|
|
1048
172
|
core.provide({
|
|
1049
173
|
provide: 'htmlPageSchema',
|
|
1050
|
-
useFactory: htmlPageSchemaFactory,
|
|
174
|
+
useFactory: htmlPageSchema.htmlPageSchemaFactory,
|
|
1051
175
|
deps: {
|
|
1052
176
|
htmlAttrs: tokensRender.HTML_ATTRS,
|
|
1053
177
|
},
|
|
@@ -1097,7 +221,7 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
1097
221
|
let body;
|
|
1098
222
|
try {
|
|
1099
223
|
log.info({ event: 'render-root-boundary-start' });
|
|
1100
|
-
body = server
|
|
224
|
+
body = server.renderToString(react.createElement(RootErrorBoundary, { error, url: url.parse(request.url) }));
|
|
1101
225
|
log.info({ event: 'render-root-boundary-success' });
|
|
1102
226
|
const status = error.status || error.httpStatus || 500;
|
|
1103
227
|
// log send-server-error only after successful Root Boundary render,
|
|
@@ -1127,7 +251,7 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
1127
251
|
};
|
|
1128
252
|
},
|
|
1129
253
|
deps: {
|
|
1130
|
-
RootErrorBoundary: { token: react.ROOT_ERROR_BOUNDARY_COMPONENT_TOKEN, optional: true },
|
|
254
|
+
RootErrorBoundary: { token: react$1.ROOT_ERROR_BOUNDARY_COMPONENT_TOKEN, optional: true },
|
|
1131
255
|
logger: tokensCommon.LOGGER_TOKEN,
|
|
1132
256
|
},
|
|
1133
257
|
}),
|
|
@@ -1162,12 +286,12 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
1162
286
|
})
|
|
1163
287
|
], exports.RenderModule);
|
|
1164
288
|
|
|
289
|
+
exports.ReactRenderServer = ReactRenderServer.ReactRenderServer;
|
|
290
|
+
exports.PageErrorStore = pageErrorStore.PageErrorStore;
|
|
291
|
+
exports.deserializeError = pageErrorStore.deserializeError;
|
|
292
|
+
exports.serializeError = pageErrorStore.serializeError;
|
|
293
|
+
exports.setPageErrorEvent = pageErrorStore.setPageErrorEvent;
|
|
1165
294
|
exports.DEFAULT_POLYFILL_CONDITION = DEFAULT_POLYFILL_CONDITION;
|
|
1166
|
-
exports.PageErrorStore = PageErrorStore;
|
|
1167
|
-
exports.ReactRenderServer = ReactRenderServer;
|
|
1168
|
-
exports.deserializeError = deserializeError;
|
|
1169
|
-
exports.serializeError = serializeError;
|
|
1170
|
-
exports.setPageErrorEvent = setPageErrorEvent;
|
|
1171
295
|
Object.keys(tokensRender).forEach(function (k) {
|
|
1172
296
|
if (k !== 'default' && !exports.hasOwnProperty(k)) Object.defineProperty(exports, k, {
|
|
1173
297
|
enumerable: true,
|