@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 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 !== 'false' && typeof hydrateRoot === 'function') {
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
- constructor({ renderSlots, pageService, resourcesRegistry, context, reactRender, htmlPageSchema, polyfillCondition, htmlAttrs, modern, }: {
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
- const requestInfo = {
821
- ip: requestManager.getClientIp(),
822
- requestId: requestManager.getHeader('x-request-id'),
823
- url: requestManager.getUrl(),
824
- };
825
- log.error({ event: 'page-render-error', error, requestInfo });
826
- // Assuming that there was an error when rendering the page, try to render again with ErrorBoundary
827
- context.dispatch(setPageErrorEvent(error));
828
- html = await htmlBuilder.flow();
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
- reply.status(error.httpStatus || error.status || 500);
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
- const requestInfo = {
857
- ip: requestManager.getClientIp(),
858
- requestId: requestManager.getHeader('x-request-id'),
859
- url: requestManager.getUrl(),
860
- };
861
- log.error({ event: 'page-render-error', error, requestInfo });
862
- // Assuming that there was an error when rendering the page, try to render again with ErrorBoundary
863
- context.dispatch(setPageErrorEvent(error));
864
- html = await htmlBuilder.flow();
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
- reply.status(error.httpStatus || error.status || 500);
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.10.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.4.24",
25
- "@tinkoff/layout-factory": "0.2.31",
26
- "@tinkoff/url": "0.7.39",
27
- "@tinkoff/user-agent": "0.4.33",
28
- "@tramvai/module-client-hints": "2.10.2",
29
- "@tramvai/module-router": "2.10.2",
30
- "@tramvai/react": "2.10.2",
31
- "@tramvai/safe-strings": "0.4.5",
32
- "@tramvai/tokens-render": "2.10.2",
33
- "@tramvai/experiments": "2.10.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.7.45",
38
+ "@tinkoff/dippy": "0.8.2",
39
39
  "@tinkoff/utils": "^2.1.2",
40
- "@tinkoff/react-hooks": "0.0.27",
41
- "@tramvai/cli": "2.10.2",
42
- "@tramvai/core": "2.10.2",
43
- "@tramvai/module-common": "2.10.2",
44
- "@tramvai/state": "2.10.2",
45
- "@tramvai/test-helpers": "2.10.2",
46
- "@tramvai/tokens-common": "2.10.2",
47
- "@tramvai/tokens-router": "2.10.2",
48
- "@tramvai/tokens-server-private": "2.10.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",