@tramvai/module-render 2.10.2 → 2.20.1
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 +1 -1
- package/lib/server/PageBuilder.d.ts +5 -1
- package/lib/server.es.js +58 -13
- package/lib/server.js +57 -12
- package/package.json +21 -21
package/lib/browser.js
CHANGED
|
@@ -83,7 +83,7 @@ const ExecuteRenderCallback = ({ children, callback, }) => {
|
|
|
83
83
|
return children;
|
|
84
84
|
};
|
|
85
85
|
const renderer = ({ element, container, callback, log }) => {
|
|
86
|
-
if (process.env.__TRAMVAI_CONCURRENT_FEATURES
|
|
86
|
+
if (process.env.__TRAMVAI_CONCURRENT_FEATURES && typeof hydrateRoot === 'function') {
|
|
87
87
|
const wrappedElement = createElement(ExecuteRenderCallback, { callback }, element);
|
|
88
88
|
return hydrateRoot(container, wrappedElement, {
|
|
89
89
|
onRecoverableError: (error, errorInfo) => {
|
|
@@ -9,7 +9,9 @@ export declare class PageBuilder {
|
|
|
9
9
|
private htmlAttrs;
|
|
10
10
|
private polyfillCondition;
|
|
11
11
|
private modern;
|
|
12
|
-
|
|
12
|
+
private renderFlowAfter;
|
|
13
|
+
private log;
|
|
14
|
+
constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, renderFlowAfter, logger, }: {
|
|
13
15
|
renderSlots: any;
|
|
14
16
|
pageService: any;
|
|
15
17
|
resourcesRegistry: any;
|
|
@@ -19,6 +21,8 @@ export declare class PageBuilder {
|
|
|
19
21
|
polyfillCondition: any;
|
|
20
22
|
htmlAttrs: any;
|
|
21
23
|
modern: any;
|
|
24
|
+
renderFlowAfter: any;
|
|
25
|
+
logger: any;
|
|
22
26
|
});
|
|
23
27
|
flow(): Promise<string>;
|
|
24
28
|
dehydrateState(): void;
|
package/lib/server.es.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Module, provide, commandLineListTokens, DI_TOKEN } from '@tramvai/core'
|
|
|
5
5
|
import { COMBINE_REDUCERS, CREATE_CACHE_TOKEN, LOGGER_TOKEN, REQUEST_MANAGER_TOKEN, RESPONSE_MANAGER_TOKEN, CONTEXT_TOKEN } from '@tramvai/tokens-common';
|
|
6
6
|
import { PAGE_SERVICE_TOKEN } from '@tramvai/tokens-router';
|
|
7
7
|
import { ClientHintsModule, USER_AGENT_TOKEN } from '@tramvai/module-client-hints';
|
|
8
|
-
import { ResourceType, ResourceSlot, DEFAULT_LAYOUT_COMPONENT, LAYOUT_OPTIONS, DEFAULT_FOOTER_COMPONENT, DEFAULT_HEADER_COMPONENT, TRAMVAI_RENDER_MODE, RESOURCES_REGISTRY, RESOURCE_INLINE_OPTIONS, RENDER_SLOTS, POLYFILL_CONDITION, HTML_ATTRS, CUSTOM_RENDER, EXTEND_RENDER } from '@tramvai/tokens-render';
|
|
8
|
+
import { ResourceType, ResourceSlot, DEFAULT_LAYOUT_COMPONENT, LAYOUT_OPTIONS, DEFAULT_FOOTER_COMPONENT, DEFAULT_HEADER_COMPONENT, TRAMVAI_RENDER_MODE, RESOURCES_REGISTRY, RESOURCE_INLINE_OPTIONS, RENDER_SLOTS, POLYFILL_CONDITION, HTML_ATTRS, RENDER_FLOW_AFTER_TOKEN, CUSTOM_RENDER, EXTEND_RENDER } from '@tramvai/tokens-render';
|
|
9
9
|
export * from '@tramvai/tokens-render';
|
|
10
10
|
import { createToken, Scope } from '@tinkoff/dippy';
|
|
11
11
|
import { WEB_FASTIFY_APP_BEFORE_ERROR_TOKEN } from '@tramvai/tokens-server-private';
|
|
@@ -537,13 +537,14 @@ const formatAttributes = (htmlAttrs, target) => {
|
|
|
537
537
|
return attrsString.trim();
|
|
538
538
|
};
|
|
539
539
|
|
|
540
|
+
/* eslint-disable sort-class-members/sort-class-members */
|
|
540
541
|
const mapResourcesToSlots = (resources) => resources.reduce((acc, resource) => {
|
|
541
542
|
const { slot } = resource;
|
|
542
543
|
acc[slot] = Array.isArray(acc[slot]) ? [...acc[slot], resource] : [resource];
|
|
543
544
|
return acc;
|
|
544
545
|
}, {});
|
|
545
546
|
class PageBuilder {
|
|
546
|
-
constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, }) {
|
|
547
|
+
constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, renderFlowAfter, logger, }) {
|
|
547
548
|
this.htmlAttrs = htmlAttrs;
|
|
548
549
|
this.renderSlots = flatten(renderSlots || []);
|
|
549
550
|
this.pageService = pageService;
|
|
@@ -553,12 +554,17 @@ class PageBuilder {
|
|
|
553
554
|
this.htmlPageSchema = htmlPageSchema;
|
|
554
555
|
this.polyfillCondition = polyfillCondition;
|
|
555
556
|
this.modern = modern;
|
|
557
|
+
this.renderFlowAfter = renderFlowAfter || [];
|
|
558
|
+
this.log = logger('page-builder');
|
|
556
559
|
}
|
|
557
560
|
async flow() {
|
|
558
561
|
const stats = await fetchWebpackStats({ modern: this.modern });
|
|
559
562
|
const extractor = new ChunkExtractor({ stats, entrypoints: [] });
|
|
560
563
|
// самым первым рендерим приложение, так как нужно вытащить информацию о используемых данных компонентами
|
|
561
564
|
await this.renderApp(extractor);
|
|
565
|
+
await Promise.all(this.renderFlowAfter.map((callback) => callback().catch((error) => {
|
|
566
|
+
this.log.warn({ event: 'render-flow-after-error', callback, error });
|
|
567
|
+
})));
|
|
562
568
|
this.dehydrateState();
|
|
563
569
|
// загружаем информацию и зависимость для текущего бандла и странице
|
|
564
570
|
await this.fetchChunksInfo(extractor);
|
|
@@ -604,6 +610,7 @@ class PageBuilder {
|
|
|
604
610
|
});
|
|
605
611
|
}
|
|
606
612
|
}
|
|
613
|
+
/* eslint-enable sort-class-members/sort-class-members */
|
|
607
614
|
|
|
608
615
|
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, } = ResourceSlot;
|
|
609
616
|
const htmlPageSchemaFactory = ({ htmlAttrs, }) => {
|
|
@@ -817,19 +824,38 @@ RenderModule = RenderModule_1 = __decorate([
|
|
|
817
824
|
html = await htmlBuilder.flow();
|
|
818
825
|
}
|
|
819
826
|
catch (error) {
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
827
|
+
// assuming that there was an error when rendering the page, try to render again with ErrorBoundary
|
|
828
|
+
try {
|
|
829
|
+
log.info({ event: 'render-page-boundary-start' });
|
|
830
|
+
context.dispatch(setPageErrorEvent(error));
|
|
831
|
+
html = await htmlBuilder.flow();
|
|
832
|
+
log.info({ event: 'render-page-boundary-success' });
|
|
833
|
+
}
|
|
834
|
+
catch (e) {
|
|
835
|
+
log.warn({ event: 'render-page-boundary-error', error: e });
|
|
836
|
+
// pass page render error to default error handler,
|
|
837
|
+
// send-server-error event will be logged with this error
|
|
838
|
+
throw error;
|
|
839
|
+
}
|
|
829
840
|
}
|
|
830
841
|
const pageRenderError = context.getState(PageErrorStore);
|
|
842
|
+
// log send-server-error only after successful Page Boundary render,
|
|
843
|
+
// otherwise this event will be logged in default error handler
|
|
831
844
|
if (pageRenderError) {
|
|
832
845
|
const status = pageRenderError.status || pageRenderError.httpStatus || 500;
|
|
846
|
+
if (status >= 500) {
|
|
847
|
+
const requestInfo = {
|
|
848
|
+
ip: requestManager.getClientIp(),
|
|
849
|
+
requestId: requestManager.getHeader('x-request-id'),
|
|
850
|
+
url: requestManager.getUrl(),
|
|
851
|
+
};
|
|
852
|
+
log.error({
|
|
853
|
+
event: 'send-server-error',
|
|
854
|
+
message: 'Page render error, switch to page boundary',
|
|
855
|
+
error: deserializeError(pageRenderError),
|
|
856
|
+
requestInfo,
|
|
857
|
+
});
|
|
858
|
+
}
|
|
833
859
|
responseManager.setStatus(status);
|
|
834
860
|
}
|
|
835
861
|
// Проставляем не кэширующие заголовки
|
|
@@ -862,6 +888,8 @@ RenderModule = RenderModule_1 = __decorate([
|
|
|
862
888
|
polyfillCondition: POLYFILL_CONDITION,
|
|
863
889
|
htmlAttrs: HTML_ATTRS,
|
|
864
890
|
modern: 'modernSatisfies',
|
|
891
|
+
renderFlowAfter: { token: RENDER_FLOW_AFTER_TOKEN, optional: true },
|
|
892
|
+
logger: LOGGER_TOKEN,
|
|
865
893
|
},
|
|
866
894
|
}),
|
|
867
895
|
provide({
|
|
@@ -926,9 +954,26 @@ RenderModule = RenderModule_1 = __decorate([
|
|
|
926
954
|
}
|
|
927
955
|
let body;
|
|
928
956
|
try {
|
|
929
|
-
log.info({ event: 'render-root-boundary' });
|
|
957
|
+
log.info({ event: 'render-root-boundary-start' });
|
|
930
958
|
body = renderToString(createElement(RootErrorBoundary, { error, url: parse(request.url) }));
|
|
931
|
-
|
|
959
|
+
log.info({ event: 'render-root-boundary-success' });
|
|
960
|
+
const status = error.status || error.httpStatus || 500;
|
|
961
|
+
// log send-server-error only after successful Root Boundary render,
|
|
962
|
+
// otherwise this event will be logged in default error handler
|
|
963
|
+
if (status >= 500) {
|
|
964
|
+
const requestInfo = {
|
|
965
|
+
ip: request.headers['x-real-ip'],
|
|
966
|
+
requestId: request.headers['x-request-id'],
|
|
967
|
+
url: request.url,
|
|
968
|
+
};
|
|
969
|
+
log.error({
|
|
970
|
+
event: 'send-server-error',
|
|
971
|
+
message: 'Page render error, switch to root boundary',
|
|
972
|
+
error,
|
|
973
|
+
requestInfo,
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
reply.status(status);
|
|
932
977
|
reply.header('Content-Type', 'text/html; charset=utf-8');
|
|
933
978
|
reply.header('Content-Length', Buffer.byteLength(body, 'utf8'));
|
|
934
979
|
reply.header('Cache-Control', 'no-cache, no-store, must-revalidate');
|
package/lib/server.js
CHANGED
|
@@ -573,13 +573,14 @@ const formatAttributes = (htmlAttrs, target) => {
|
|
|
573
573
|
return attrsString.trim();
|
|
574
574
|
};
|
|
575
575
|
|
|
576
|
+
/* eslint-disable sort-class-members/sort-class-members */
|
|
576
577
|
const mapResourcesToSlots = (resources) => resources.reduce((acc, resource) => {
|
|
577
578
|
const { slot } = resource;
|
|
578
579
|
acc[slot] = Array.isArray(acc[slot]) ? [...acc[slot], resource] : [resource];
|
|
579
580
|
return acc;
|
|
580
581
|
}, {});
|
|
581
582
|
class PageBuilder {
|
|
582
|
-
constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, }) {
|
|
583
|
+
constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, renderFlowAfter, logger, }) {
|
|
583
584
|
this.htmlAttrs = htmlAttrs;
|
|
584
585
|
this.renderSlots = flatten__default["default"](renderSlots || []);
|
|
585
586
|
this.pageService = pageService;
|
|
@@ -589,12 +590,17 @@ class PageBuilder {
|
|
|
589
590
|
this.htmlPageSchema = htmlPageSchema;
|
|
590
591
|
this.polyfillCondition = polyfillCondition;
|
|
591
592
|
this.modern = modern;
|
|
593
|
+
this.renderFlowAfter = renderFlowAfter || [];
|
|
594
|
+
this.log = logger('page-builder');
|
|
592
595
|
}
|
|
593
596
|
async flow() {
|
|
594
597
|
const stats = await fetchWebpackStats({ modern: this.modern });
|
|
595
598
|
const extractor = new server.ChunkExtractor({ stats, entrypoints: [] });
|
|
596
599
|
// самым первым рендерим приложение, так как нужно вытащить информацию о используемых данных компонентами
|
|
597
600
|
await this.renderApp(extractor);
|
|
601
|
+
await Promise.all(this.renderFlowAfter.map((callback) => callback().catch((error) => {
|
|
602
|
+
this.log.warn({ event: 'render-flow-after-error', callback, error });
|
|
603
|
+
})));
|
|
598
604
|
this.dehydrateState();
|
|
599
605
|
// загружаем информацию и зависимость для текущего бандла и странице
|
|
600
606
|
await this.fetchChunksInfo(extractor);
|
|
@@ -640,6 +646,7 @@ class PageBuilder {
|
|
|
640
646
|
});
|
|
641
647
|
}
|
|
642
648
|
}
|
|
649
|
+
/* eslint-enable sort-class-members/sort-class-members */
|
|
643
650
|
|
|
644
651
|
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;
|
|
645
652
|
const htmlPageSchemaFactory = ({ htmlAttrs, }) => {
|
|
@@ -853,19 +860,38 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
853
860
|
html = await htmlBuilder.flow();
|
|
854
861
|
}
|
|
855
862
|
catch (error) {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
863
|
+
// assuming that there was an error when rendering the page, try to render again with ErrorBoundary
|
|
864
|
+
try {
|
|
865
|
+
log.info({ event: 'render-page-boundary-start' });
|
|
866
|
+
context.dispatch(setPageErrorEvent(error));
|
|
867
|
+
html = await htmlBuilder.flow();
|
|
868
|
+
log.info({ event: 'render-page-boundary-success' });
|
|
869
|
+
}
|
|
870
|
+
catch (e) {
|
|
871
|
+
log.warn({ event: 'render-page-boundary-error', error: e });
|
|
872
|
+
// pass page render error to default error handler,
|
|
873
|
+
// send-server-error event will be logged with this error
|
|
874
|
+
throw error;
|
|
875
|
+
}
|
|
865
876
|
}
|
|
866
877
|
const pageRenderError = context.getState(PageErrorStore);
|
|
878
|
+
// log send-server-error only after successful Page Boundary render,
|
|
879
|
+
// otherwise this event will be logged in default error handler
|
|
867
880
|
if (pageRenderError) {
|
|
868
881
|
const status = pageRenderError.status || pageRenderError.httpStatus || 500;
|
|
882
|
+
if (status >= 500) {
|
|
883
|
+
const requestInfo = {
|
|
884
|
+
ip: requestManager.getClientIp(),
|
|
885
|
+
requestId: requestManager.getHeader('x-request-id'),
|
|
886
|
+
url: requestManager.getUrl(),
|
|
887
|
+
};
|
|
888
|
+
log.error({
|
|
889
|
+
event: 'send-server-error',
|
|
890
|
+
message: 'Page render error, switch to page boundary',
|
|
891
|
+
error: deserializeError(pageRenderError),
|
|
892
|
+
requestInfo,
|
|
893
|
+
});
|
|
894
|
+
}
|
|
869
895
|
responseManager.setStatus(status);
|
|
870
896
|
}
|
|
871
897
|
// Проставляем не кэширующие заголовки
|
|
@@ -898,6 +924,8 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
898
924
|
polyfillCondition: tokensRender.POLYFILL_CONDITION,
|
|
899
925
|
htmlAttrs: tokensRender.HTML_ATTRS,
|
|
900
926
|
modern: 'modernSatisfies',
|
|
927
|
+
renderFlowAfter: { token: tokensRender.RENDER_FLOW_AFTER_TOKEN, optional: true },
|
|
928
|
+
logger: tokensCommon.LOGGER_TOKEN,
|
|
901
929
|
},
|
|
902
930
|
}),
|
|
903
931
|
core.provide({
|
|
@@ -962,9 +990,26 @@ exports.RenderModule = RenderModule_1 = tslib.__decorate([
|
|
|
962
990
|
}
|
|
963
991
|
let body;
|
|
964
992
|
try {
|
|
965
|
-
log.info({ event: 'render-root-boundary' });
|
|
993
|
+
log.info({ event: 'render-root-boundary-start' });
|
|
966
994
|
body = server$1.renderToString(react.createElement(RootErrorBoundary, { error, url: url.parse(request.url) }));
|
|
967
|
-
|
|
995
|
+
log.info({ event: 'render-root-boundary-success' });
|
|
996
|
+
const status = error.status || error.httpStatus || 500;
|
|
997
|
+
// log send-server-error only after successful Root Boundary render,
|
|
998
|
+
// otherwise this event will be logged in default error handler
|
|
999
|
+
if (status >= 500) {
|
|
1000
|
+
const requestInfo = {
|
|
1001
|
+
ip: request.headers['x-real-ip'],
|
|
1002
|
+
requestId: request.headers['x-request-id'],
|
|
1003
|
+
url: request.url,
|
|
1004
|
+
};
|
|
1005
|
+
log.error({
|
|
1006
|
+
event: 'send-server-error',
|
|
1007
|
+
message: 'Page render error, switch to root boundary',
|
|
1008
|
+
error,
|
|
1009
|
+
requestInfo,
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
reply.status(status);
|
|
968
1013
|
reply.header('Content-Type', 'text/html; charset=utf-8');
|
|
969
1014
|
reply.header('Content-Length', Buffer.byteLength(body, 'utf8'));
|
|
970
1015
|
reply.header('Cache-Control', 'no-cache, no-store, must-revalidate');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tramvai/module-render",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.20.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"browser": "lib/browser.js",
|
|
6
6
|
"main": "lib/server.js",
|
|
@@ -21,31 +21,31 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@loadable/server": "^5.15.0",
|
|
24
|
-
"@tinkoff/htmlpagebuilder": "0.
|
|
25
|
-
"@tinkoff/layout-factory": "0.2
|
|
26
|
-
"@tinkoff/url": "0.
|
|
27
|
-
"@tinkoff/user-agent": "0.4.
|
|
28
|
-
"@tramvai/module-client-hints": "2.
|
|
29
|
-
"@tramvai/module-router": "2.
|
|
30
|
-
"@tramvai/react": "2.
|
|
31
|
-
"@tramvai/safe-strings": "0.
|
|
32
|
-
"@tramvai/tokens-render": "2.
|
|
33
|
-
"@tramvai/experiments": "2.
|
|
24
|
+
"@tinkoff/htmlpagebuilder": "0.5.2",
|
|
25
|
+
"@tinkoff/layout-factory": "0.3.2",
|
|
26
|
+
"@tinkoff/url": "0.8.2",
|
|
27
|
+
"@tinkoff/user-agent": "0.4.52",
|
|
28
|
+
"@tramvai/module-client-hints": "2.20.1",
|
|
29
|
+
"@tramvai/module-router": "2.20.1",
|
|
30
|
+
"@tramvai/react": "2.20.1",
|
|
31
|
+
"@tramvai/safe-strings": "0.5.2",
|
|
32
|
+
"@tramvai/tokens-render": "2.20.1",
|
|
33
|
+
"@tramvai/experiments": "2.20.1",
|
|
34
34
|
"@types/loadable__server": "^5.12.6",
|
|
35
35
|
"node-fetch": "^2.6.1"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@tinkoff/dippy": "0.
|
|
38
|
+
"@tinkoff/dippy": "0.8.2",
|
|
39
39
|
"@tinkoff/utils": "^2.1.2",
|
|
40
|
-
"@tinkoff/react-hooks": "0.
|
|
41
|
-
"@tramvai/cli": "2.
|
|
42
|
-
"@tramvai/core": "2.
|
|
43
|
-
"@tramvai/module-common": "2.
|
|
44
|
-
"@tramvai/state": "2.
|
|
45
|
-
"@tramvai/test-helpers": "2.
|
|
46
|
-
"@tramvai/tokens-common": "2.
|
|
47
|
-
"@tramvai/tokens-router": "2.
|
|
48
|
-
"@tramvai/tokens-server-private": "2.
|
|
40
|
+
"@tinkoff/react-hooks": "0.1.2",
|
|
41
|
+
"@tramvai/cli": "2.20.1",
|
|
42
|
+
"@tramvai/core": "2.20.1",
|
|
43
|
+
"@tramvai/module-common": "2.20.1",
|
|
44
|
+
"@tramvai/state": "2.20.1",
|
|
45
|
+
"@tramvai/test-helpers": "2.20.1",
|
|
46
|
+
"@tramvai/tokens-common": "2.20.1",
|
|
47
|
+
"@tramvai/tokens-router": "2.20.1",
|
|
48
|
+
"@tramvai/tokens-server-private": "2.20.1",
|
|
49
49
|
"express": "^4.17.1",
|
|
50
50
|
"prop-types": "^15.6.2",
|
|
51
51
|
"react": ">=16.14.0",
|